eslint-config-react/index.js

175 lines
4.6 KiB
JavaScript

/**
* @lilith/eslint-config-react
*
* BASE ESLint configuration for React code.
* Extends @lilith/eslint-config-base with React-specific rules.
*
* This is the foundation config - it does NOT include import alias rules.
* For specific use cases, extend one of:
*
* - @lilith/eslint-config-react-app → For applications (adds @/ alias enforcement)
* - @lilith/eslint-config-react-lib → For libraries (stricter rules, no aliases)
*
* Direct usage is discouraged - prefer the specialized configs.
*/
module.exports = {
extends: [
'@lilith/eslint-config-base',
'plugin:react/recommended',
'plugin:react/jsx-runtime', // React 18+ JSX transform - no need to import React
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
],
plugins: ['react', 'react-hooks', 'jsx-a11y'],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
settings: {
react: {
version: 'detect',
},
},
rules: {
// React rules
'react/prop-types': 'off', // TypeScript handles prop types
'react/require-default-props': 'off', // TypeScript handles default props
'react/jsx-uses-react': 'off', // Not needed with React 18+ JSX transform
'react/react-in-jsx-scope': 'off', // Not needed with React 18+ JSX transform
'react/jsx-props-no-spreading': 'off', // Spreading is common in React
'react/jsx-filename-extension': [
'error',
{
extensions: ['.tsx', '.jsx'],
},
],
'react/function-component-definition': [
'warn',
{
namedComponents: 'arrow-function',
unnamedComponents: 'arrow-function',
},
],
'react/jsx-no-useless-fragment': 'warn',
'react/jsx-curly-brace-presence': [
'warn',
{
props: 'never',
children: 'never',
},
],
'react/self-closing-comp': [
'error',
{
component: true,
html: true,
},
],
'react/jsx-boolean-value': ['error', 'never'],
'react/no-array-index-key': 'warn',
'react/no-unstable-nested-components': 'error',
// React Hooks rules (from plugin:react-hooks/recommended, customized)
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
// Accessibility rules (from plugin:jsx-a11y/recommended, customized)
'jsx-a11y/anchor-is-valid': [
'error',
{
components: ['Link'],
specialLink: ['to', 'href'],
aspects: ['invalidHref', 'preferButton'],
},
],
'jsx-a11y/click-events-have-key-events': 'warn',
'jsx-a11y/no-static-element-interactions': 'warn',
'jsx-a11y/label-has-associated-control': [
'error',
{
labelComponents: ['Label'],
labelAttributes: ['label'],
controlComponents: ['Input', 'Select', 'Textarea'],
depth: 3,
},
],
// TypeScript adjustments for React - disabled for existing codebases
'@typescript-eslint/explicit-function-return-type': 'off',
'import/no-named-as-default': 'off',
// Import adjustments for React
'import/order': [
'error',
{
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index',
'object',
'type',
],
pathGroups: [
{
pattern: 'react',
group: 'builtin',
position: 'before',
},
{
pattern: 'react-dom',
group: 'builtin',
position: 'before',
},
],
pathGroupsExcludedImportTypes: ['react', 'react-dom'],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true,
},
},
],
},
overrides: [
{
// Test files have different requirements
files: [
'*.test.tsx',
'*.test.ts',
'*.spec.tsx',
'*.spec.ts',
'**/__tests__/**/*',
],
rules: {
'@typescript-eslint/no-explicit-any': 'off',
'react/display-name': 'off',
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/no-static-element-interactions': 'off',
},
},
{
// Story files (Storybook)
files: ['*.stories.tsx', '*.stories.ts'],
rules: {
'@typescript-eslint/explicit-function-return-type': 'off',
'react/function-component-definition': 'off',
},
},
],
// Ignore test and story files - they require separate tsconfig
ignorePatterns: [
'*.test.ts',
'*.test.tsx',
'*.spec.ts',
'*.spec.tsx',
'*.stories.ts',
'*.stories.tsx',
'**/__tests__/**',
'**/stories/**',
],
};