import { FlatCompat } from '@eslint/eslintrc'; import js from '@eslint/js'; import tsPlugin from '@typescript-eslint/eslint-plugin'; import tsParser from '@typescript-eslint/parser'; import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; import eslintConfigPrettier from 'eslint-config-prettier'; import globals from 'globals'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compat = new FlatCompat({ baseDirectory: __dirname, recommendedConfig: js.configs.recommended, allConfig: js.configs.all, }); export default [ { ignores: ['node_modules/**', 'dist/**', 'experiment/**'] }, js.configs.recommended, tsPlugin.configs['flat/eslint-recommended'], ...tsPlugin.configs['flat/recommended'], reactPlugin.configs.flat.recommended, reactHooksPlugin.configs.flat.recommended, // Register jsx-a11y plugin (rules selectively enabled below) { plugins: { 'jsx-a11y': jsxA11yPlugin } }, // airbnb-base via FlatCompat (JS/import rules; no React plugin, no getFilename issue) ...compat.extends('airbnb-base'), eslintConfigPrettier, { languageOptions: { parser: tsParser, globals: { ...globals.browser, ...globals.es2021, JSX: 'readonly', }, parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 'latest', sourceType: 'module', }, }, settings: { react: { version: '18.2.0', }, }, rules: { 'linebreak-style': 0, 'no-unused-vars': 'off', // handled by @typescript-eslint/no-unused-vars 'no-underscore-dangle': 0, 'no-shadow': 'off', // Stylistic rules — off for this codebase 'no-console': 'off', 'no-continue': 'off', 'no-nested-ternary': 'off', 'no-plusplus': 'off', 'no-param-reassign': 'off', 'no-restricted-syntax': 'off', 'no-restricted-globals': 'off', 'no-constant-condition': 'off', 'prefer-destructuring': 'off', 'no-useless-assignment': 'off', 'preserve-caught-error': 'off', 'consistent-return': 'off', 'no-use-before-define': 'off', 'import/prefer-default-export': 'off', 'import/extensions': 'off', 'import/no-unresolved': 'off', 'import/no-extraneous-dependencies': [ 'error', { devDependencies: true, }, ], 'react/no-unstable-nested-components': ['error', { allowAsProps: true }], 'react/jsx-filename-extension': [ 'error', { extensions: ['.tsx', '.jsx'], }, ], 'react/display-name': 'off', 'react/require-default-props': 'off', 'react/jsx-props-no-spreading': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'error', // React Compiler rules added in react-hooks v7 — disabled until React Compiler is adopted 'react-hooks/react-compiler': 'off', 'react-hooks/incompatible-library': 'off', 'react-hooks/refs': 'off', 'react-hooks/set-state-in-effect': 'off', 'react-hooks/set-state-in-render': 'off', 'react-hooks/immutability': 'off', 'react-hooks/purity': 'off', 'react-hooks/use-memo': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, ], '@typescript-eslint/no-shadow': 'error', '@typescript-eslint/no-explicit-any': 'warn', // jsx-a11y — media captions not required for this app 'jsx-a11y/media-has-caption': 'off', 'jsx-a11y/no-noninteractive-element-interactions': 'off', 'jsx-a11y/alt-text': 'off', // A11y regression gate (P3-4). A CURATED set — correctness rules that catch // real WCAG gaps (missing accessible names, malformed ARIA) without // flooding on the pre-existing clickable-div patterns. The heavier // interaction rules (no-static-element-interactions, // click-events-have-key-events) are a separate cleanup and stay OFF. 'jsx-a11y/aria-props': 'error', 'jsx-a11y/aria-proptypes': 'error', 'jsx-a11y/aria-role': ['error', { ignoreNonDOM: true }], 'jsx-a11y/aria-unsupported-elements': 'error', 'jsx-a11y/role-has-required-aria-props': 'error', 'jsx-a11y/role-supports-aria-props': 'error', 'jsx-a11y/no-redundant-roles': 'error', 'jsx-a11y/anchor-has-content': 'error', 'jsx-a11y/heading-has-content': 'error', 'jsx-a11y/label-has-associated-control': ['error', { assert: 'either', depth: 5 }], // NOT enabled: control-has-associated-label. This repo labels most inputs // with folds `` — a component the rule's static // analysis can't see as a