// ESLint flat config for ESLint 9+ // This config is compatible with Prettier and won't conflict with formatting rules import js from '@eslint/js'; import tseslint 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 importPlugin from 'eslint-plugin-import'; import prettierConfig from 'eslint-config-prettier'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export default [ // Base recommended rules js.configs.recommended, // Ignore patterns { ignores: [ 'node_modules/**', 'target/**', '**/target/**', 'dist/**', 'coverage/**', 'app/**', 'src-tauri/**', 'rust-core/**', 'skills/**', 'references/**', 'scripts/**', '*.config.js', '*.config.ts', 'test/vitest.config.ts', 'tsconfig.tsbuildinfo', ], }, // Browser environment globals { files: ['**/*.js', '**/*.ts', '**/*.jsx', '**/*.tsx'], languageOptions: { globals: { // Browser globals window: 'readonly', localStorage: 'readonly', sessionStorage: 'readonly', document: 'readonly', navigator: 'readonly', console: 'readonly', setTimeout: 'readonly', setInterval: 'readonly', clearTimeout: 'readonly', clearInterval: 'readonly', fetch: 'readonly', AbortSignal: 'readonly', self: 'readonly', crypto: 'readonly', atob: 'readonly', btoa: 'readonly', // React globals React: 'readonly', // Node.js globals (for Vite/node polyfills) require: 'readonly', process: 'readonly', Buffer: 'readonly', global: 'readonly', __dirname: 'readonly', __filename: 'readonly', module: 'readonly', exports: 'readonly', }, }, }, // TypeScript files configuration { files: ['src/**/*.ts', 'src/**/*.tsx'], languageOptions: { parser: tsparser, parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true, }, project: './tsconfig.json', tsconfigRootDir: __dirname, }, }, plugins: { '@typescript-eslint': tseslint, import: importPlugin, }, rules: { // Disable base no-unused-vars in favor of TypeScript version 'no-unused-vars': 'off', // TypeScript recommended rules (disable base JS rules that TypeScript handles) '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_|^[A-Z_]+$', // Ignore _prefixed vars and ALL_CAPS (enum members) caughtErrorsIgnorePattern: '^_', ignoreRestSiblings: true, }, ], '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-non-null-assertion': 'off', // Import/export rules // Note: import/order is disabled to let Prettier handle import sorting // ESLint still checks for other import issues 'import/order': 'off', // Prettier plugin handles import sorting 'import/no-unresolved': 'off', // TypeScript handles this 'import/no-cycle': 'warn', 'import/no-duplicates': 'error', // Prevent duplicate imports // General JavaScript/TypeScript rules 'no-console': 'off', // Allow console in frontend code 'no-debugger': 'error', 'no-duplicate-imports': 'error', 'no-unused-expressions': 'off', // Covered by @typescript-eslint version '@typescript-eslint/no-unused-expressions': 'error', // Code quality 'prefer-const': 'error', 'no-var': 'error', 'object-shorthand': 'error', 'prefer-arrow-callback': 'error', // Style: Enforce single-line statements on same line without braces when possible curly: ['error', 'multi', 'consistent'], // Allow single-line without braces, require braces only for multi-statement blocks 'nonblock-statement-body-position': ['error', 'beside'], // Enforce single-line statements on same line (prevents braces on single-line) }, }, // React files configuration { files: ['src/**/*.jsx', 'src/**/*.tsx'], languageOptions: { parser: tsparser, parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true, }, project: './tsconfig.json', tsconfigRootDir: __dirname, }, }, plugins: { react: reactPlugin, 'react-hooks': reactHooksPlugin, }, settings: { react: { version: 'detect', }, }, rules: { ...reactPlugin.configs.recommended.rules, ...reactHooksPlugin.configs.recommended.rules, 'react/react-in-jsx-scope': 'off', // Not needed in React 17+ 'react/prop-types': 'off', // TypeScript handles prop validation 'react/display-name': 'off', // Not needed with TypeScript 'react/no-unescaped-entities': 'off', // Prettier handles this 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', 'react-hooks/set-state-in-effect': 'warn', // Allow initialization in effects 'react-hooks/refs': 'off', // Allow ref access in context providers }, }, // Vitest test files and test setup files (must come after TypeScript config to override rules) { files: [ '**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx', '**/__tests__/**/*.ts', '**/__tests__/**/*.tsx', ], languageOptions: { globals: { // Vitest globals describe: 'readonly', it: 'readonly', test: 'readonly', expect: 'readonly', beforeEach: 'readonly', afterEach: 'readonly', beforeAll: 'readonly', afterAll: 'readonly', vi: 'readonly', vitest: 'readonly', }, }, rules: { '@typescript-eslint/no-explicit-any': 'off', // Allow any in tests '@typescript-eslint/no-non-null-assertion': 'off', // Allow non-null assertions in tests 'no-undef': 'off', // Vitest provides globals }, }, // Unit test files in test/ — TypeScript + JSX, parsed with main tsconfig { files: ['test/*.test.ts', 'test/*.test.tsx'], languageOptions: { parser: tsparser, parserOptions: { ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true }, project: './test/tsconfig.unit.json', tsconfigRootDir: __dirname, }, globals: { describe: 'readonly', it: 'readonly', test: 'readonly', expect: 'readonly', beforeEach: 'readonly', afterEach: 'readonly', beforeAll: 'readonly', afterAll: 'readonly', vi: 'readonly', }, }, plugins: { '@typescript-eslint': tseslint, react: reactPlugin, 'react-hooks': reactHooksPlugin, }, settings: { react: { version: 'detect' } }, rules: { 'react/react-in-jsx-scope': 'off', 'react/prop-types': 'off', '@typescript-eslint/no-explicit-any': 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, ], 'no-undef': 'off', }, }, // E2E test files (Appium/WebDriverIO) — use tsconfig.e2e.json for parsing { files: ['test/e2e/**/*.ts', 'test/wdio.conf.ts'], languageOptions: { parser: tsparser, parserOptions: { ecmaVersion: 'latest', sourceType: 'module', project: './test/tsconfig.e2e.json', tsconfigRootDir: __dirname, }, globals: { browser: 'readonly', $: 'readonly', $$: 'readonly', describe: 'readonly', it: 'readonly', before: 'readonly', after: 'readonly', beforeEach: 'readonly', afterEach: 'readonly', expect: 'readonly', }, }, plugins: { '@typescript-eslint': tseslint, }, rules: { 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_' }, ], '@typescript-eslint/no-explicit-any': 'off', 'no-undef': 'off', }, }, // JavaScript files configuration { files: ['**/*.js', '**/*.jsx'], languageOptions: { ecmaVersion: 'latest', sourceType: 'module' }, rules: { 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], 'no-console': 'off', 'no-debugger': 'error', 'prefer-const': 'error', 'no-var': 'error', }, }, // Disable all Prettier-conflicting rules (must be last) prettierConfig, ];