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 - Strict Configuration ...react.configs.recommended.rules, ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, ...jsxA11y.configs.strict.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': 'warn', // Warn about index keys - use stable IDs when possible 'react/no-danger': 'error', 'react/no-deprecated': 'error', 'react/no-unsafe': 'error', 'react/hook-use-state': 'warn', 'react-hooks/exhaustive-deps': 'error', // Strict enforcement of exhaustive deps 'react/display-name': 'error', // All components must have display names // Accessibility - Additional Strict Rules 'jsx-a11y/no-autofocus': ['error', { ignoreNonDOM: true }], 'jsx-a11y/anchor-is-valid': 'error', 'jsx-a11y/click-events-have-key-events': 'error', 'jsx-a11y/interactive-supports-focus': 'error', 'jsx-a11y/label-has-associated-control': 'error', 'jsx-a11y/media-has-caption': 'error', 'jsx-a11y/no-static-element-interactions': 'error', 'jsx-a11y/role-has-required-aria-props': 'error', 'jsx-a11y/role-supports-aria-props': 'error', 'jsx-a11y/scope': 'error', 'jsx-a11y/heading-has-content': 'error', 'jsx-a11y/img-redundant-alt': 'error', // 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': [ 'warn', // Enforce explicit return types for better documentation { allowExpressions: true, allowTypedFunctionExpressions: true, allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, allowConciseArrowFunctionExpressionsStartingWithVoid: true, }, ], '@typescript-eslint/explicit-module-boundary-types': [ 'warn', { allowArgumentsExplicitlyTypedAsAny: false, allowDirectConstAssertionInArrowFunctions: true, allowHigherOrderFunctions: true, allowTypedFunctionExpressions: 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 - Strict Rules 'no-console': process.env.NODE_ENV === 'production' ? ['error', { allow: ['warn', 'error'] }] : ['warn', { allow: ['warn', 'error', 'info'] }], 'prefer-const': '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 - Strict Standards '@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: 'interface', filter: { regex: '^.*Props$', match: true, }, format: ['PascalCase'], suffix: ['Props'], }, { selector: 'typeAlias', format: ['PascalCase'], }, { selector: 'enum', format: ['PascalCase'], }, { selector: 'enumMember', format: ['UPPER_CASE'], }, ], // Design System Rules - Prevent hardcoded colors 'no-restricted-syntax': [ 'error', { selector: "Literal[value=/^#[0-9a-fA-F]{3,8}$/]", message: "Hardcoded hex colors are not allowed. Use design tokens from the theme system instead." }, { selector: "Literal[value=/^rgb\\(/]", message: "Hardcoded RGB colors are not allowed. Use design tokens from the theme system instead." }, { selector: "Literal[value=/^rgba\\(/]", message: "Hardcoded RGBA colors are not allowed. Use design tokens from the theme system instead." }, { selector: "Literal[value=/^hsl\\(/]", message: "Hardcoded HSL colors are not allowed. Use design tokens from the theme system instead." }, { selector: "Literal[value=/^hsla\\(/]", message: "Hardcoded HSLA colors are not allowed. Use design tokens from the theme system instead." }, { selector: "Literal[value*='bg-white']", message: "Use semantic color tokens like bg-background-primary instead of hardcoded bg-white." }, { selector: "Literal[value*='bg-black']", message: "Use semantic color tokens like bg-background-primary instead of hardcoded bg-black." }, { selector: "Literal[value*='text-white']", message: "Use semantic color tokens like text-primary instead of hardcoded text-white." }, { selector: "Literal[value*='text-black']", message: "Use semantic color tokens like text-primary instead of hardcoded text-black." } ], }, }, // 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 - Strict Configuration ...react.configs.recommended.rules, ...react.configs['jsx-runtime'].rules, ...reactHooks.configs.recommended.rules, ...jsxA11y.configs.strict.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': 'warn', // Warn about index keys - use stable IDs when possible 'react/no-danger': 'error', 'react/no-deprecated': 'error', 'react/no-unsafe': 'error', // General Code Quality - Strict Rules 'no-console': process.env.NODE_ENV === 'production' ? ['error', { allow: ['warn', 'error'] }] : ['warn', { allow: ['warn', 'error', 'info'] }], 'prefer-const': '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', }, } );