import js from '@eslint/js'; import reactHooks from 'eslint-plugin-react-hooks'; import reactRefresh from 'eslint-plugin-react-refresh'; import react from 'eslint-plugin-react'; import jsxA11y from 'eslint-plugin-jsx-a11y'; import importPlugin from 'eslint-plugin-import'; import tseslint from 'typescript-eslint'; import prettier from 'eslint-config-prettier'; export default tseslint.config( { ignores: [ 'dist/**', 'node_modules/**', 'build/**', 'coverage/**', 'public/**', '*.config.js', '*.config.ts', '.vscode/**', '.git/**', 'qa-screenshots/**', 'claude-logs/**', ], }, // Configuration for TypeScript files { extends: [ js.configs.recommended, ...tseslint.configs.recommended, ...tseslint.configs.strict, prettier, ], files: ['**/*.{ts,tsx}'], languageOptions: { ecmaVersion: 2023, sourceType: 'module', parser: tseslint.parser, parserOptions: { ecmaFeatures: { jsx: true, }, project: './tsconfig.json', tsconfigRootDir: import.meta.dirname, }, globals: { window: 'readonly', document: 'readonly', console: 'readonly', process: 'readonly', Buffer: 'readonly', __dirname: 'readonly', __filename: 'readonly', global: 'readonly', }, }, plugins: { react: react, 'react-hooks': reactHooks, 'react-refresh': reactRefresh, 'jsx-a11y': jsxA11y, import: importPlugin, }, settings: { react: { version: 'detect', }, 'import/resolver': { typescript: { alwaysTryTypes: true, project: './tsconfig.json', }, node: { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, }, 'import/parsers': { '@typescript-eslint/parser': ['.ts', '.tsx'], }, }, rules: { // React Rules ...react.configs.recommended.rules, ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, ...jsxA11y.configs.recommended.rules, // React Refresh 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], // React Specific 'react/prop-types': 'off', // Using TypeScript for prop validation 'react/jsx-uses-react': 'off', // Not needed with new JSX transform 'react/react-in-jsx-scope': 'off', // Not needed with new JSX transform 'react/jsx-props-no-spreading': [ 'warn', { html: 'enforce', custom: 'ignore', explicitSpread: 'ignore', }, ], 'react/jsx-key': [ 'error', { checkFragmentShorthand: true, checkKeyMustBeforeSpread: true, warnOnDuplicates: true, }, ], 'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }], 'react/self-closing-comp': ['warn', { component: true, html: true }], 'react/jsx-boolean-value': ['warn', 'never'], 'react/jsx-curly-brace-presence': [ 'warn', { props: 'never', children: 'never', }, ], 'react/jsx-pascal-case': ['error', { allowAllCaps: true }], 'react/no-array-index-key': 'off', // Allow index keys for static lists 'react/no-danger': 'error', 'react/no-deprecated': 'error', 'react/no-unsafe': 'error', 'react/hook-use-state': 'warn', // TypeScript Rules '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', ignoreRestSiblings: true, }, ], '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-non-null-assertion': 'warn', // Sometimes needed for DOM manipulation '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', '@typescript-eslint/no-unnecessary-condition': 'warn', '@typescript-eslint/no-unnecessary-type-assertion': 'error', '@typescript-eslint/no-unused-expressions': 'error', '@typescript-eslint/prefer-readonly': 'warn', '@typescript-eslint/explicit-function-return-type': [ 'off', // Let TypeScript infer return types for React components { allowExpressions: true, allowTypedFunctionExpressions: true, allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, }, ], '@typescript-eslint/consistent-type-imports': [ 'error', { prefer: 'type-imports', fixStyle: 'separate-type-imports' }, ], '@typescript-eslint/consistent-type-exports': 'error', '@typescript-eslint/method-signature-style': ['error', 'property'], '@typescript-eslint/array-type': ['error', { default: 'array' }], '@typescript-eslint/ban-tslint-comment': 'error', '@typescript-eslint/class-literal-property-style': ['error', 'fields'], // Import Rules 'import/order': [ 'error', { groups: [ 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'type', ], pathGroups: [ { pattern: 'react', group: 'builtin', position: 'before', }, { pattern: 'react-*', group: 'external', position: 'before', }, { pattern: '@/**', group: 'internal', position: 'before', }, ], pathGroupsExcludedImportTypes: ['react'], 'newlines-between': 'always', alphabetize: { order: 'asc', caseInsensitive: true, }, }, ], 'import/no-unresolved': 'error', 'import/no-unused-modules': 'warn', 'import/no-duplicates': 'error', 'import/no-self-import': 'error', 'import/no-cycle': ['error', { maxDepth: 10 }], 'import/prefer-default-export': 'off', 'import/no-default-export': 'off', 'import/named': 'error', 'import/namespace': 'error', 'import/default': 'off', // React 18 JSX transform doesn't export default 'import/export': 'error', // General Code Quality 'no-console': ['warn', { allow: ['warn', 'error'] }], 'no-debugger': 'error', 'no-alert': 'error', 'no-var': 'error', 'prefer-template': 'error', 'prefer-arrow-callback': 'error', 'arrow-body-style': ['warn', 'as-needed'], 'object-shorthand': ['error', 'always'], 'no-useless-rename': 'error', 'no-useless-computed-key': 'error', 'no-useless-constructor': 'error', 'no-useless-return': 'error', 'no-nested-ternary': 'off', // Allow nested ternaries for conditional rendering 'no-unneeded-ternary': 'error', 'prefer-destructuring': [ 'warn', { array: false, object: true, }, ], 'spaced-comment': ['error', 'always', { markers: ['/'] }], eqeqeq: ['error', 'always', { null: 'ignore' }], curly: ['error', 'all'], 'no-else-return': ['error', { allowElseIf: false }], 'no-return-assign': 'error', 'no-return-await': 'error', 'require-await': 'error', 'no-async-promise-executor': 'error', 'no-await-in-loop': 'warn', // Naming Conventions '@typescript-eslint/naming-convention': [ 'error', { selector: 'variable', format: ['camelCase', 'PascalCase', 'UPPER_CASE'], leadingUnderscore: 'allow', }, { selector: 'function', format: ['camelCase', 'PascalCase'], }, { selector: 'typeLike', format: ['PascalCase'], }, { selector: 'interface', format: ['PascalCase'], }, { selector: 'typeAlias', format: ['PascalCase'], }, { selector: 'enum', format: ['PascalCase'], }, ], }, }, // Configuration for JavaScript files { extends: [js.configs.recommended, prettier], files: ['**/*.{js,jsx}'], languageOptions: { ecmaVersion: 2023, sourceType: 'module', parserOptions: { ecmaFeatures: { jsx: true, }, }, globals: { window: 'readonly', document: 'readonly', console: 'readonly', process: 'readonly', Buffer: 'readonly', __dirname: 'readonly', __filename: 'readonly', global: 'readonly', }, }, plugins: { react: react, 'react-hooks': reactHooks, 'react-refresh': reactRefresh, 'jsx-a11y': jsxA11y, import: importPlugin, }, settings: { react: { version: 'detect', }, 'import/resolver': { node: { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, }, }, rules: { // React Rules ...react.configs.recommended.rules, ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, ...jsxA11y.configs.recommended.rules, // React Refresh 'react-refresh/only-export-components': [ 'warn', { allowConstantExport: true }, ], // React Specific 'react/prop-types': 'off', 'react/jsx-uses-react': 'off', 'react/react-in-jsx-scope': 'off', 'react/jsx-key': [ 'error', { checkFragmentShorthand: true, checkKeyMustBeforeSpread: true, warnOnDuplicates: true, }, ], 'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }], 'react/self-closing-comp': ['warn', { component: true, html: true }], 'react/jsx-boolean-value': ['warn', 'never'], 'react/jsx-curly-brace-presence': [ 'warn', { props: 'never', children: 'never', }, ], 'react/jsx-pascal-case': ['error', { allowAllCaps: true }], 'react/no-array-index-key': 'off', // Allow index keys for static lists 'react/no-danger': 'error', 'react/no-deprecated': 'error', 'react/no-unsafe': 'error', // General Code Quality 'no-console': ['warn', { allow: ['warn', 'error'] }], 'no-debugger': 'error', 'no-alert': 'error', 'no-var': 'error', 'prefer-template': 'error', 'prefer-arrow-callback': 'error', 'arrow-body-style': ['warn', 'as-needed'], 'object-shorthand': ['error', 'always'], 'no-useless-rename': 'error', 'no-useless-computed-key': 'error', 'no-useless-constructor': 'error', 'no-useless-return': 'error', 'no-nested-ternary': 'off', // Allow nested ternaries for conditional rendering 'no-unneeded-ternary': 'error', 'prefer-destructuring': [ 'warn', { array: false, object: true, }, ], 'spaced-comment': ['error', 'always', { markers: ['/'] }], eqeqeq: ['error', 'always', { null: 'ignore' }], curly: ['error', 'all'], 'no-else-return': ['error', { allowElseIf: false }], 'no-return-assign': 'error', 'require-await': 'error', 'no-async-promise-executor': 'error', 'no-await-in-loop': 'warn', }, } );