chore: finish frontend ESLint migration #18
Enable every plugin recommended preset and replace Biome with ESLint formatting. Preserve API contracts and JSX whitespace; verify source, tests, builds and tooling.
This commit is contained in:
+63
-77
@@ -1,131 +1,117 @@
|
||||
import js from "@eslint/js";
|
||||
import react from "@eslint-react/eslint-plugin";
|
||||
import js from "@eslint/js";
|
||||
import stylistic from "@stylistic/eslint-plugin";
|
||||
import query from "@tanstack/eslint-plugin-query";
|
||||
import vitest from "@vitest/eslint-plugin";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
|
||||
import { importX } from "eslint-plugin-import-x";
|
||||
import jestDom from "eslint-plugin-jest-dom";
|
||||
import perfectionist from "eslint-plugin-perfectionist";
|
||||
import hooks from "eslint-plugin-react-hooks";
|
||||
import refresh from "eslint-plugin-react-refresh";
|
||||
import regexp from "eslint-plugin-regexp";
|
||||
import security from "eslint-plugin-security";
|
||||
import sonarjs from "eslint-plugin-sonarjs";
|
||||
import storybook from "eslint-plugin-storybook";
|
||||
import testingLibrary from "eslint-plugin-testing-library";
|
||||
import unicorn from "eslint-plugin-unicorn";
|
||||
import { defineConfig, globalIgnores } from "eslint/config";
|
||||
import globals from "globals";
|
||||
import tseslint from "typescript-eslint";
|
||||
|
||||
export default defineConfig(
|
||||
globalIgnores([
|
||||
"**/dist/**",
|
||||
"dist-ssr/**",
|
||||
"storybook-static/**",
|
||||
"coverage/**",
|
||||
".vitest/**",
|
||||
".scannerwork/**",
|
||||
"src/shared/api/generated/**",
|
||||
"src/shared/api/spec/**",
|
||||
"**/dist/**", "dist-ssr/**", "storybook-static/**", "coverage/**",
|
||||
".vitest/**", ".scannerwork/**", "src/shared/api/generated/**", "src/shared/api/spec/**",
|
||||
]),
|
||||
{
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
importX.flatConfigs.recommended,
|
||||
unicorn.configs.recommended,
|
||||
sonarjs.configs.recommended,
|
||||
regexp.configs.recommended,
|
||||
security.configs.recommended,
|
||||
perfectionist.configs["recommended-natural"],
|
||||
stylistic.configs.customize({ indent: "tab", jsx: true, quotes: "double", semi: true }),
|
||||
],
|
||||
files: ["**/*.{js,mjs,cjs,ts,tsx}"],
|
||||
extends: [js.configs.recommended],
|
||||
linterOptions: { reportUnusedDisableDirectives: "error" },
|
||||
rules: {
|
||||
// A comma disambiguates generic arrow functions from JSX in TSX.
|
||||
"@stylistic/comma-dangle": ["error", {
|
||||
arrays: "always-multiline", enums: "always-multiline", exports: "always-multiline",
|
||||
functions: "always-multiline", generics: "ignore", imports: "always-multiline",
|
||||
objects: "always-multiline", tuples: "always-multiline",
|
||||
}],
|
||||
// Keep text/interpolation together: splitting can change rendered whitespace.
|
||||
"@stylistic/jsx-one-expression-per-line": ["error", { allow: "non-jsx" }],
|
||||
"@stylistic/no-tabs": ["error", { allowIndentationTabs: true }],
|
||||
"perfectionist/sort-classes": ["error", { groups: ["static-method", "property", "method", "unknown"], type: "natural" }],
|
||||
// Preserve the repository's component, hook and config file naming conventions.
|
||||
"unicorn/filename-case": ["error", { cases: { camelCase: true, kebabCase: true, pascalCase: true } }],
|
||||
// React uses Props/Ref names, including the recommended React ref-name rule.
|
||||
"unicorn/name-replacements": ["error", { replacements: { props: false, ref: false } }],
|
||||
// React, Mantine and Fetch accept null at their documented call boundaries.
|
||||
"unicorn/no-null": ["error", { checkArguments: false }],
|
||||
},
|
||||
settings: {
|
||||
"import-x/resolver-next": [createTypeScriptImportResolver({ project: "tsconfig.json" })],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.{js,mjs,cjs}", "**/*.config.ts", ".storybook/main.ts"],
|
||||
languageOptions: { globals: globals.node },
|
||||
},
|
||||
{
|
||||
extends: [tseslint.configs.recommendedTypeChecked, importX.flatConfigs.typescript],
|
||||
files: ["**/*.{ts,tsx}"],
|
||||
extends: [tseslint.configs.recommendedTypeChecked],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname } },
|
||||
rules: {
|
||||
// React ignores event handler return values; floating promises are still checked in bodies.
|
||||
"@typescript-eslint/no-misused-promises": ["error", { checksVoidReturn: { attributes: false } }],
|
||||
"@typescript-eslint/switch-exhaustiveness-check": "error",
|
||||
// React ignores handler return values; their bodies still check floating promises.
|
||||
"@typescript-eslint/no-misused-promises": [
|
||||
"error",
|
||||
{ checksVoidReturn: { attributes: false } },
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
"src/**/*.{ts,tsx}",
|
||||
"packages/*/src/**/*.{ts,tsx}",
|
||||
".storybook/preview.tsx",
|
||||
],
|
||||
extends: [
|
||||
react.configs["recommended-type-checked"],
|
||||
react.configs["disable-naming-convention"],
|
||||
hooks.configs.flat.recommended,
|
||||
query.configs["flat/recommended"],
|
||||
],
|
||||
extends: [react.configs["recommended-type-checked"], hooks.configs.flat.recommended, query.configs["flat/recommended"]],
|
||||
files: ["src/**/*.{ts,tsx}", "packages/*/src/**/*.{ts,tsx}", ".storybook/preview.tsx"],
|
||||
languageOptions: { globals: globals.browser },
|
||||
plugins: { "import-x": importX, unicorn },
|
||||
rules: {
|
||||
// The official hooks plugin owns Compiler and hook diagnostics.
|
||||
"@eslint-react/error-boundaries": "off",
|
||||
"@eslint-react/exhaustive-deps": "off",
|
||||
"@eslint-react/purity": "off",
|
||||
"@eslint-react/rules-of-hooks": "off",
|
||||
"@eslint-react/set-state-in-effect": "off",
|
||||
"@eslint-react/set-state-in-render": "off",
|
||||
"@eslint-react/static-components": "off",
|
||||
"@eslint-react/unsupported-syntax": "off",
|
||||
"@eslint-react/use-memo": "off",
|
||||
// useContext remains supported; a lint rollout does not require an API migration.
|
||||
"@eslint-react/no-use-context": "off",
|
||||
"import-x/no-default-export": "error",
|
||||
"import-x/exports-last": "error",
|
||||
// Biome owns formatting, import sorting and magic numbers during coexistence.
|
||||
"unicorn/no-array-fill-with-reference-type": "error",
|
||||
"unicorn/no-await-in-promise-methods": "error",
|
||||
"unicorn/no-single-promise-in-promise-methods": "error",
|
||||
"unicorn/prefer-array-some": "error",
|
||||
"unicorn/prefer-includes": "error",
|
||||
"import-x/no-default-export": "error",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: [refresh.configs.vite],
|
||||
files: ["src/**/*.tsx", "packages/*/src/**/*.tsx"],
|
||||
ignores: ["**/*.test.tsx", "**/*.stories.tsx", "src/test/**"],
|
||||
extends: [refresh.configs.vite],
|
||||
},
|
||||
{
|
||||
files: ["**/*.test.{ts,tsx}"],
|
||||
files: ["src/**/*.{ts,tsx}", "packages/*/src/**/*.{ts,tsx}"],
|
||||
ignores: ["**/*.test.{ts,tsx}", "**/*.stories.tsx", "src/test/**"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-magic-numbers": ["error", {
|
||||
ignore: [-1, 0, 1, "-1n", "0n", "1n"], ignoreArrayIndexes: true, ignoreDefaultValues: true,
|
||||
ignoreNumericLiteralTypes: true, ignoreTypeIndexes: true,
|
||||
}],
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: [vitest.configs.recommended],
|
||||
files: ["**/*.test.{ts,tsx}"],
|
||||
rules: {
|
||||
"vitest/no-focused-tests": "error",
|
||||
"vitest/valid-expect": ["error", { maxArgs: 2 }],
|
||||
// it.each cases select different expected outcomes and recovery setup steps.
|
||||
"vitest/no-conditional-expect": "off",
|
||||
// Async test doubles preserve the public Promise contract without artificial awaits.
|
||||
"@typescript-eslint/require-await": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
extends: [testingLibrary.configs["flat/react"], jestDom.configs["flat/recommended"]],
|
||||
files: ["**/*.test.tsx"],
|
||||
plugins: { "testing-library": testingLibrary },
|
||||
// Start with async correctness. DOM structure/geometry tests need direct node access.
|
||||
rules: {
|
||||
"testing-library/await-async-events": "error",
|
||||
"testing-library/await-async-queries": "error",
|
||||
"testing-library/await-async-utils": "error",
|
||||
"testing-library/no-await-sync-events": "error",
|
||||
"testing-library/no-await-sync-queries": "error",
|
||||
"testing-library/no-promise-in-fire-event": "error",
|
||||
"testing-library/no-wait-for-side-effects": "error",
|
||||
"testing-library/no-wait-for-snapshot": "error",
|
||||
"testing-library/prefer-find-by": "error",
|
||||
},
|
||||
},
|
||||
storybook.configs["flat/recommended"],
|
||||
{
|
||||
files: ["**/*.stories.tsx", ".storybook/preview.tsx"],
|
||||
rules: {
|
||||
"import-x/no-default-export": "off",
|
||||
"import-x/exports-last": "off",
|
||||
},
|
||||
rules: { "import-x/exports-last": "off", "import-x/no-default-export": "off" },
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user