import js from "@eslint/js"; import react from "@eslint-react/eslint-plugin"; import query from "@tanstack/eslint-plugin-query"; import vitest from "@vitest/eslint-plugin"; import { defineConfig, globalIgnores } from "eslint/config"; import { importX } from "eslint-plugin-import-x"; import hooks from "eslint-plugin-react-hooks"; import refresh from "eslint-plugin-react-refresh"; import storybook from "eslint-plugin-storybook"; import testingLibrary from "eslint-plugin-testing-library"; import unicorn from "eslint-plugin-unicorn"; 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/**", ]), { files: ["**/*.{js,mjs,cjs,ts,tsx}"], extends: [js.configs.recommended], linterOptions: { reportUnusedDisableDirectives: "error" }, }, { files: ["**/*.{js,mjs,cjs}", "**/*.config.ts", ".storybook/main.ts"], languageOptions: { globals: globals.node }, }, { files: ["**/*.{ts,tsx}"], extends: [tseslint.configs.recommendedTypeChecked], languageOptions: { parserOptions: { projectService: true, tsconfigRootDir: import.meta.dirname, }, }, rules: { "@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"], ], 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", }, }, { files: ["src/**/*.tsx", "packages/*/src/**/*.tsx"], ignores: ["**/*.test.tsx", "**/*.stories.tsx", "src/test/**"], extends: [refresh.configs.vite], }, { files: ["**/*.test.{ts,tsx}"], extends: [vitest.configs.recommended], 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", }, }, { 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", }, }, );