140 lines
5.2 KiB
JavaScript
140 lines
5.2 KiB
JavaScript
import { defineConfig, globalIgnores } from "eslint/config";
|
|
import js from "@eslint/js";
|
|
import reactHooks from "eslint-plugin-react-hooks";
|
|
import sonarjs from "eslint-plugin-sonarjs";
|
|
import unusedImports from "eslint-plugin-unused-imports";
|
|
import i18next from "eslint-plugin-i18next";
|
|
import tseslint from "typescript-eslint";
|
|
import { noLiteralStringOptions } from "./eslint.i18n.options.mjs";
|
|
import {
|
|
e2eSleepGuardFiles,
|
|
e2eSleepPlugin,
|
|
SLEEP_EXEMPT_FILES,
|
|
} from "./eslint-rules/no-unsanctioned-sleep.mjs";
|
|
import { taskLinksPlugin } from "./eslint-rules/no-task-link-bypass.mjs";
|
|
|
|
const eslintConfig = defineConfig([
|
|
{
|
|
linterOptions: {
|
|
reportUnusedDisableDirectives: "off",
|
|
},
|
|
},
|
|
js.configs.recommended,
|
|
...tseslint.configs.recommended,
|
|
globalIgnores([
|
|
".next/**",
|
|
"out/**",
|
|
"build/**",
|
|
"dist/**",
|
|
// Test artifacts (Playwright):
|
|
"**/test-results/**",
|
|
"**/playwright-report/**",
|
|
]),
|
|
{
|
|
plugins: {
|
|
"react-hooks": reactHooks,
|
|
sonarjs,
|
|
"unused-imports": unusedImports,
|
|
},
|
|
rules: {
|
|
"no-control-regex": "off",
|
|
"no-empty": "off",
|
|
"no-undef": "off",
|
|
"no-unsafe-finally": "off",
|
|
"max-lines": ["warn", { max: 600, skipBlankLines: true, skipComments: true }],
|
|
"max-lines-per-function": ["warn", { max: 100, skipBlankLines: true, skipComments: true }],
|
|
complexity: ["warn", 15],
|
|
"max-depth": ["warn", 4],
|
|
"max-params": ["warn", 5],
|
|
"no-nested-ternary": "warn",
|
|
"sonarjs/cognitive-complexity": ["warn", 20],
|
|
"sonarjs/no-duplicate-string": ["warn", { threshold: 4 }],
|
|
"sonarjs/no-identical-functions": "warn",
|
|
"unused-imports/no-unused-imports": "warn",
|
|
"@typescript-eslint/no-unused-vars": [
|
|
"warn",
|
|
{ varsIgnorePattern: "^_", argsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" },
|
|
],
|
|
},
|
|
},
|
|
// Hardcoded user-facing strings. An ERROR, REPO-WIDE.
|
|
//
|
|
// This was scoped to `i18nGuardFiles` while the migration was in flight: a
|
|
// repo-wide error would have broken every unrelated PR that landed a literal
|
|
// in un-migrated code, which is what made the first attempt unmergeable. That
|
|
// reason has expired. Measured across all 2560 source files the rule reports
|
|
// ZERO violations, so widening it costs nothing today and permanently removes
|
|
// "the file was not on the list" as a way for copy to ship untranslated.
|
|
//
|
|
// `i18nGuardFiles` is kept: it is the migration's record, the scope
|
|
// `pnpm run lint:i18n` previews, and a ratchet `check-guard-allowlist.mjs`
|
|
// still enforces. It is no longer what bounds this rule.
|
|
{
|
|
files: ["**/*.ts", "**/*.tsx"],
|
|
// Test files build fixtures out of literal strings on purpose; guarding them
|
|
// would force every `label="Tasks"` in a test through the catalog. The
|
|
// `*.test-helpers` / `*.test-utils` pair are imported only by tests and are
|
|
// the same class.
|
|
ignores: [
|
|
"**/*.test.ts",
|
|
"**/*.test.tsx",
|
|
"**/*.test-helpers.ts",
|
|
"**/*.test-helpers.tsx",
|
|
"**/*.test-utils.ts",
|
|
"**/*.test-utils.tsx",
|
|
"e2e/**",
|
|
],
|
|
plugins: { i18next },
|
|
rules: { "i18next/no-literal-string": ["error", noLiteralStringOptions] },
|
|
},
|
|
// First-party task workbench URLs and anchors have one URL authority and one
|
|
// click transport. Tests and the authority module itself are excluded so the
|
|
// rule checks callers rather than its own implementation and fixtures.
|
|
{
|
|
files: ["**/*.ts", "**/*.tsx"],
|
|
ignores: [
|
|
"**/*.test.ts",
|
|
"**/*.test.tsx",
|
|
"**/*.test-helpers.ts",
|
|
"**/*.test-helpers.tsx",
|
|
"**/*.test-utils.ts",
|
|
"**/*.test-utils.tsx",
|
|
"e2e/**",
|
|
"lib/links.ts",
|
|
],
|
|
plugins: { "task-links": taskLinksPlugin },
|
|
rules: { "task-links/no-task-link-bypass": "error" },
|
|
},
|
|
// E2E tests (Playwright): disable React hooks rules since Playwright's `use()` and
|
|
// `test.extend()` patterns are falsely flagged, and relax test-specific limits.
|
|
{
|
|
files: ["e2e/**/*.ts"],
|
|
rules: {
|
|
"react-hooks/rules-of-hooks": "off",
|
|
"react-hooks/exhaustive-deps": "off",
|
|
"max-lines-per-function": "off",
|
|
"max-lines": "off",
|
|
"sonarjs/no-duplicate-string": "off",
|
|
},
|
|
},
|
|
// Unconditional wall-clock sleeps in E2E specs. An ERROR across all of `e2e/`.
|
|
// This was a narrow allowlist while the causal-waits conversion was running —
|
|
// `pnpm lint` is `eslint --max-warnings 0`, so a repo-wide registration at ANY
|
|
// severity (warning included) would have failed the build on the sleeps that
|
|
// predated the rule and blocked every unrelated PR. The conversion reached
|
|
// zero, so `e2eSleepGuardFiles` has graduated to the whole tree and this now
|
|
// fails only a PR that ADDS a sleep.
|
|
// `scripts/check-new-e2e-sleeps.mjs` remains as the second line of defence: it
|
|
// judges only the lines a change added, and covers anything `ignores` carves
|
|
// out. Never narrow the guard to make a build pass; see the list's own comment.
|
|
{
|
|
files: e2eSleepGuardFiles,
|
|
// `causal-waits.ts` implements both banned forms — see SLEEP_EXEMPT_FILES.
|
|
ignores: SLEEP_EXEMPT_FILES,
|
|
plugins: { "e2e-sleeps": e2eSleepPlugin },
|
|
rules: { "e2e-sleeps/no-unsanctioned-sleep": "error" },
|
|
},
|
|
]);
|
|
|
|
export default eslintConfig;
|