import { defineConfig, globalIgnores } from 'eslint/config' import nextVitals from 'eslint-config-next/core-web-vitals' import nextTs from 'eslint-config-next/typescript' import tailwindcss from 'eslint-plugin-better-tailwindcss' const eslintConfig = defineConfig([ ...nextVitals, ...nextTs, // Tailwind CSS correctness rules (v4-compatible) { plugins: { 'better-tailwindcss': tailwindcss }, settings: { 'better-tailwindcss': { entryPoint: 'src/app/globals.css', }, }, rules: { // warn only: false positives from tailwindcss-animate and prose classes 'better-tailwindcss/no-unknown-classes': 'warn', 'better-tailwindcss/no-conflicting-classes': 'error', 'better-tailwindcss/no-duplicate-classes': 'warn', }, }, // Disable Tailwind rules for test files (intentional fake class names) { files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx'], rules: { 'better-tailwindcss/no-unknown-classes': 'off', }, }, // Configure jsx-a11y rules without redefining the plugin // (eslint-config-next already includes jsx-a11y plugin) { rules: { // Accessibility - strict mode per PRD Section 6 'jsx-a11y/alt-text': 'error', 'jsx-a11y/anchor-has-content': 'error', 'jsx-a11y/anchor-is-valid': 'error', 'jsx-a11y/aria-activedescendant-has-tabindex': 'error', 'jsx-a11y/aria-props': 'error', 'jsx-a11y/aria-proptypes': 'error', 'jsx-a11y/aria-role': 'error', 'jsx-a11y/aria-unsupported-elements': 'error', 'jsx-a11y/autocomplete-valid': 'error', 'jsx-a11y/click-events-have-key-events': 'error', 'jsx-a11y/control-has-associated-label': 'off', 'jsx-a11y/heading-has-content': 'error', 'jsx-a11y/html-has-lang': 'error', 'jsx-a11y/iframe-has-title': 'error', 'jsx-a11y/img-redundant-alt': 'error', 'jsx-a11y/interactive-supports-focus': 'error', 'jsx-a11y/label-has-associated-control': 'error', 'jsx-a11y/media-has-caption': 'error', 'jsx-a11y/mouse-events-have-key-events': 'error', 'jsx-a11y/no-access-key': 'error', 'jsx-a11y/no-autofocus': ['error', { ignoreNonDOM: true }], 'jsx-a11y/no-distracting-elements': 'error', 'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error', 'jsx-a11y/no-noninteractive-element-interactions': 'error', 'jsx-a11y/no-noninteractive-element-to-interactive-role': 'error', 'jsx-a11y/no-noninteractive-tabindex': 'error', 'jsx-a11y/no-redundant-roles': '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/tabindex-no-positive': 'error', }, }, { rules: { // TypeScript strict per CLAUDE.md '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, ], }, }, // Override default ignores of eslint-config-next. globalIgnores([ '.next/**', 'dist/**', 'out/**', 'build/**', 'next-env.d.ts', 'node_modules/**', 'playwright-report/**', 'test-results/**', ]), ]) export default eslintConfig