import globals from "globals"; import pluginJs from "@eslint/js"; import pluginReact from "eslint-plugin-react"; import pluginReactHooks from "eslint-plugin-react-hooks"; import pluginUnusedImports from "eslint-plugin-unused-imports"; import tseslint from "typescript-eslint"; /** * The rule set, written once and applied to JavaScript and TypeScript alike. * * Shared rather than duplicated because the two blocks below differ in exactly * one thing — which parser reads the file — and a rule that applied to `.jsx` * but not to its `.tsx` successor would make the TypeScript migration look like * it was tidying the code up. It is not; it renames files and adds types. */ const rules = { "no-unused-vars": "off", "react/jsx-uses-vars": "error", "react/jsx-uses-react": "error", "unused-imports/no-unused-imports": "error", "unused-imports/no-unused-vars": [ "warn", { vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_", }, ], "react/prop-types": "off", "react/react-in-jsx-scope": "off", "react/no-unknown-property": [ "error", { ignore: ["cmdk-input-wrapper", "toast-close"] }, ], "react-hooks/rules-of-hooks": "error", }; const plugins = { react: pluginReact, "react-hooks": pluginReactHooks, "unused-imports": pluginUnusedImports, }; const settings = { react: { version: "detect" } }; /** * Which files are linted. * * Unchanged from what this config has always covered — `src/lib` and * `src/components/ui` stay out of it — with `ts` and `tsx` added to every * pattern. That addition is the point: ESLint matches on extension, so the * moment a `.jsx` file became `.tsx` it would have dropped out of the run * silently, and `eslint .` would have gone on exiting 0 while linting less and * less of the codebase. A lint that passes because it checked nothing is worse * than one that fails. */ const directories = ["src/components", "src/pages", "src/layouts", "src/hooks"]; const ignores = ["src/lib/**/*", "src/components/ui/**/*"]; const filesWith = (extensions) => directories.map((directory) => `${directory}/**/*.{${extensions}}`); export default [ { files: filesWith("js,mjs,cjs,jsx"), ignores, ...pluginJs.configs.recommended, ...pluginReact.configs.flat.recommended, languageOptions: { globals: globals.browser, parserOptions: { ecmaVersion: 2022, sourceType: "module", ecmaFeatures: { jsx: true }, }, }, settings, plugins, rules, }, /** * The same block for TypeScript, with the TypeScript parser. * * A separate block rather than one widened glob, so that nothing about how * the existing JavaScript is parsed or reported changes on the day this * lands. `typescript-eslint`'s parser accepts plain JavaScript too, and * merging the two would have been shorter — but it would also have quietly * re-parsed 200-odd existing files, and this phase is meant to be provably * inert. * * Deliberately NOT type-aware (no `projectService`): type errors are * `npm run typecheck`'s job, and asking ESLint to build a program as well * would make every lint run pay for it twice. */ { files: filesWith("ts,tsx,mts,cts"), ignores, ...pluginReact.configs.flat.recommended, languageOptions: { globals: globals.browser, parser: tseslint.parser, parserOptions: { ecmaVersion: 2022, sourceType: "module", ecmaFeatures: { jsx: true }, }, }, settings, plugins: { ...plugins, "@typescript-eslint": tseslint.plugin }, rules: { ...rules, /* TypeScript resolves identifiers itself and reports the ones it cannot, with better messages and without ESLint's browser/node globals list needing to be right. Leaving the core rule on would report every `type` and `interface` name as undefined. */ "no-undef": "off", }, }, ];