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 { 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/**", ]), { 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}"], 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}"], 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", }, }, { 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 }, rules: { "import-x/exports-last": "error", "import-x/no-default-export": "error", }, }, { extends: [refresh.configs.vite], files: ["src/**/*.tsx", "packages/*/src/**/*.tsx"], ignores: ["**/*.test.tsx", "**/*.stories.tsx", "src/test/**"], }, { 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 }], }, }, { extends: [testingLibrary.configs["flat/react"], jestDom.configs["flat/recommended"]], files: ["**/*.test.tsx"], }, storybook.configs["flat/recommended"], { files: ["**/*.stories.tsx", ".storybook/preview.tsx"], rules: { "import-x/exports-last": "off", "import-x/no-default-export": "off" }, }, );