UNPKG

@aleph/eslint-config

Version:

Aleph's ESLint configuration for JavaScript and TypeScript projects - ESLint 9 flat config

340 lines (304 loc) 10.1 kB
/** * Aleph ESLint Configuration - ESLint 9 Flat Config * * This configuration is maintained as part of our documentation system. * For detailed explanation of rules and rationale, see: * https://docs.aleph.inc/docs/resources/4devs/standards/code-style-standards * * This config extends recommended presets and only includes rules where * we override the defaults or have specific preferences. */ const js = require('@eslint/js'); const stylistic = require('@stylistic/eslint-plugin'); const importPlugin = require('eslint-plugin-import'); const reactPlugin = require('eslint-plugin-react'); const reactHooksPlugin = require('eslint-plugin-react-hooks'); const jsxA11yPlugin = require('eslint-plugin-jsx-a11y'); const globals = require('globals'); // === Configuration Objects === // Common language options for all configurations const commonLanguageOptions = { ecmaVersion: 'latest', sourceType: 'module', globals: { ...globals.browser, ...globals.node, ...globals.es2022, React: 'readonly', JSX: 'readonly', }, parserOptions: { ecmaFeatures: { jsx: true, }, }, }; // Common plugin configuration const commonPlugins = { '@stylistic': stylistic, 'import': importPlugin, 'react': reactPlugin, 'react-hooks': reactHooksPlugin, 'jsx-a11y': jsxA11yPlugin, }; // Common settings const commonSettings = { 'react': { version: 'detect', }, 'import/resolver': { typescript: { project: './tsconfig.json', }, node: { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, }, }; module.exports = [ // Global ignore patterns { ignores: [ '**/*.d.ts', 'storybook-static', 'dist', 'node_modules', '.next/**', '*.config.js', 'next.config.js', 'package-lock.json', 'yarn.lock', ], }, // Base JavaScript configuration - extends recommended js.configs.recommended, // Core JavaScript/React configuration - only our customizations { languageOptions: commonLanguageOptions, plugins: commonPlugins, settings: commonSettings, rules: { // === Core JavaScript Customizations === // Warnings instead of errors for development convenience 'no-console': 'warn', 'no-alert': 'warn', // Our preferences that differ from defaults 'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', ignoreRestSiblings: true, }], 'no-use-before-define': ['error', { functions: false, // Allow hoisted function use classes: true, variables: true, }], // Allow certain patterns common in our codebase 'no-restricted-exports': 'off', // Allow default exports (common in Next.js) // === Stylistic Rules (Our Code Style) === // Semicolons - We don't use them '@stylistic/semi': ['error', 'never'], // Quotes - Single quotes '@stylistic/quotes': ['error', 'single', { avoidEscape: true }], // Line length - Relaxed to 140 chars as warning '@stylistic/max-len': ['warn', { code: 140, ignoreUrls: true, ignoreComments: false, ignoreRegExpLiterals: true, ignoreStrings: true, ignoreTemplateLiterals: true, }], // Indentation - 2 spaces '@stylistic/indent': ['error', 2, { SwitchCase: 1, VariableDeclarator: 1, outerIIFEBody: 1, FunctionDeclaration: { parameters: 1, body: 1 }, FunctionExpression: { parameters: 1, body: 1 }, CallExpression: { arguments: 1 }, ArrayExpression: 1, ObjectExpression: 1, ImportDeclaration: 1, flatTernaryExpressions: false, ignoredNodes: ['JSXElement', 'JSXElement > *', 'JSXAttribute', 'JSXIdentifier', 'JSXNamespacedName', 'JSXMemberExpression', 'JSXSpreadAttribute', 'JSXExpressionContainer', 'JSXOpeningElement', 'JSXClosingElement', 'JSXFragment', 'JSXOpeningFragment', 'JSXClosingFragment', 'JSXText', 'JSXEmptyExpression', 'JSXSpreadChild'], ignoreComments: false }], // Trailing commas - Always in multiline '@stylistic/comma-dangle': ['error', { arrays: 'always-multiline', objects: 'always-multiline', imports: 'always-multiline', exports: 'always-multiline', functions: 'always-multiline', }], // Spacing preferences '@stylistic/object-curly-spacing': ['error', 'always'], '@stylistic/array-bracket-spacing': ['error', 'never'], '@stylistic/comma-spacing': ['error', { before: false, after: true }], '@stylistic/key-spacing': ['error', { beforeColon: false, afterColon: true }], '@stylistic/space-before-function-paren': ['error', { anonymous: 'always', named: 'never', asyncArrow: 'always' }], '@stylistic/space-in-parens': ['error', 'never'], '@stylistic/space-infix-ops': 'error', '@stylistic/keyword-spacing': ['error', { before: true, after: true, }], // Code organization '@stylistic/eol-last': ['error', 'always'], '@stylistic/no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }], '@stylistic/no-trailing-spaces': 'error', '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }], // === Import Rules === // Import ordering - Our specific organization 'import/order': ['error', { 'groups': [ 'builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type' ], 'pathGroups': [ { 'pattern': 'react', 'group': 'builtin', 'position': 'before' }, { 'pattern': 'next/**', 'group': 'builtin', 'position': 'before' }, { 'pattern': '@/**', 'group': 'internal' }, { 'pattern': 'app/**', 'group': 'internal' } ], 'pathGroupsExcludedImportTypes': ['react', 'next'], 'newlines-between': 'always', 'alphabetize': { 'order': 'asc', 'caseInsensitive': true } }], // Import preferences 'import/newline-after-import': 'error', 'import/no-duplicates': 'error', 'import/prefer-default-export': 'off', // Allow named exports without default // === ES6+ Preferences === // Prefer modern syntax 'prefer-const': 'error', 'prefer-template': 'error', 'prefer-arrow-callback': 'error', 'no-var': 'error', 'object-shorthand': 'error', 'arrow-body-style': ['error', 'as-needed'], }, }, // React-specific rules { files: ['**/*.{js,jsx,ts,tsx}'], languageOptions: commonLanguageOptions, plugins: commonPlugins, settings: commonSettings, rules: { // === React Customizations === // Next.js compatibility 'react/react-in-jsx-scope': 'off', // Next.js has automatic JSX runtime // TypeScript makes these redundant 'react/prop-types': 'off', 'react/require-default-props': 'off', // Allow flexible component definitions 'react/function-component-definition': 'off', // Allow arrow functions for components // Warnings instead of errors 'react/no-danger': 'warn', // Allow dangerouslySetInnerHTML but warn // Our preferences 'react/jsx-no-target-blank': 'error', 'react/self-closing-comp': 'error', 'react/button-has-type': 'error', // === React Hooks === 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', // Warn instead of error for deps // === JSX A11y - Keep important ones === 'jsx-a11y/alt-text': 'error', 'jsx-a11y/anchor-has-content': 'error', 'jsx-a11y/anchor-is-valid': 'error', 'jsx-a11y/aria-props': 'error', 'jsx-a11y/aria-role': 'error', '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/no-access-key': 'error', 'jsx-a11y/role-has-required-aria-props': 'error', 'jsx-a11y/role-supports-aria-props': 'error', 'jsx-a11y/tabindex-no-positive': 'error', }, }, // TypeScript-specific configuration { files: ['**/*.{ts,tsx}'], languageOptions: { ...commonLanguageOptions, parser: require('@typescript-eslint/parser'), }, plugins: { ...commonPlugins, '@typescript-eslint': require('@typescript-eslint/eslint-plugin'), }, settings: commonSettings, rules: { // Use TypeScript's versions of these rules 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_', ignoreRestSiblings: true, }], 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': ['error', { functions: false, classes: true, variables: true, }], 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'error', 'no-redeclare': 'off', '@typescript-eslint/no-redeclare': 'error', }, }, // SVG component files override { files: ['**/*svg*.js', '**/*svg*.jsx', '**/*svg*.ts', '**/*svg*.tsx'], rules: { '@stylistic/max-len': 'off', // Disable line length limits for SVG components }, }, // Storybook files override { files: ['.storybook/**/*', '**/*.stories.{ts,tsx,js,jsx}', '**/stories/**/*'], rules: { '@stylistic/max-len': 'off', // Disable line length limits for Storybook files }, }, ];