UNPKG

@kununu/eslint-config

Version:
317 lines (310 loc) 11.7 kB
import babelParser from '@babel/eslint-parser'; import babelPlugin from '@babel/eslint-plugin'; import js from '@eslint/js'; import stylistic from '@stylistic/eslint-plugin'; import typescriptParser from '@typescript-eslint/parser'; import granularSelectorsPlugin from 'eslint-plugin-granular-selectors'; import pluginJest from 'eslint-plugin-jest'; import jestDomPlugin from 'eslint-plugin-jest-dom'; import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'; import lodashPlugin from 'eslint-plugin-lodash'; import perfectionistPlugin from 'eslint-plugin-perfectionist'; import reactPlugin from 'eslint-plugin-react'; import reactHooksPlugin from 'eslint-plugin-react-hooks'; import sonarjs from 'eslint-plugin-sonarjs'; import testingLibraryPlugin from 'eslint-plugin-testing-library'; import {defineConfig, globalIgnores} from 'eslint/config'; import tseslint from 'typescript-eslint'; export default defineConfig([ js.configs.recommended, jsxA11yPlugin.flatConfigs.recommended, perfectionistPlugin.configs['recommended-natural'], reactHooksPlugin.configs.flat.recommended, reactPlugin.configs.flat.recommended, reactPlugin.configs.flat['jsx-runtime'], sonarjs.configs.recommended, stylistic.configs.recommended, // Scope the TypeScript rules to TS files only; .js/.jsx use the Babel parser // below and would otherwise run type-aware rules under the wrong parser. ...tseslint.configs.recommended.map(config => ({ ...config, files: ['**/*.ts', '**/*.tsx'], })), globalIgnores([ '**/.next/', '**/__mocks__/', '**/__snapshots__/', '**/build/', '**/coverage/', '**/dist/', '**/node_modules/', '**/out/', '.git/', 'jest.config.js', 'newrelic.js', 'next.config.js', ]), { files: ['**/*.ts', '**/*.tsx'], languageOptions: { parser: typescriptParser, parserOptions: { projectService: true, }, }, rules: { '@typescript-eslint/no-require-imports': 'error', // Allow intentionally-unused identifiers prefixed with `_`. '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrorsIgnorePattern: '^_', destructuredArrayIgnorePattern: '^_', ignoreRestSiblings: true, varsIgnorePattern: '^_', }], '@typescript-eslint/no-use-before-define': 'error', }, }, { files: ['**/*.js', '**/*.jsx'], languageOptions: { parser: babelParser, parserOptions: { requireConfigFile: false, }, }, plugins: { babel: babelPlugin, }, }, // Test-only configs. Spread as separate entries so their plugins/rules // aren't clobbered, and scope them to spec files (incl. testing-library, // which previously applied to every file). {...pluginJest.configs['flat/recommended'], files: ['**/*.spec.*']}, {...jestDomPlugin.configs['flat/recommended'], files: ['**/*.spec.*']}, {...testingLibraryPlugin.configs['flat/dom'], files: ['**/*.spec.*']}, { files: ['**/*.spec.*'], rules: { '@typescript-eslint/no-explicit-any': 'off', // Tests legitimately require() dynamic mocks / json fixtures. '@typescript-eslint/no-require-imports': 'off', // Recognise assertions made through `assert*` helper functions, not just // direct expect() calls. 'jest/expect-expect': ['warn', {assertFunctionNames: ['expect', 'assert*']}], // kununu stores shared test fixtures under __mocks__ and imports them // directly; this rule targets jest auto-mocks, not that convention. 'jest/no-mocks-import': 'off', 'react/display-name': 'off', // Prop-types are redundant in a TypeScript codebase. 'react/prop-types': 'off', // Test fixtures often contain password-like strings (mock query strings, // tokens) that aren't real credentials; keep the rule for source code. 'sonarjs/no-hardcoded-passwords': 'off', // Direct DOM access is common and acceptable in tests. 'testing-library/no-node-access': 'off', 'testing-library/render-result-naming-convention': 'off', }, }, { languageOptions: { parserOptions: { ecmaFeatures: { jsx: true, }, }, }, linterOptions: { reportUnusedDisableDirectives: 'error', }, plugins: { 'granular-selectors': granularSelectorsPlugin, lodash: lodashPlugin, }, rules: { // Prevent empty lines in arrays '@stylistic/array-bracket-newline': ['error', 'consistent'], '@stylistic/array-bracket-spacing': ['error', 'never'], '@stylistic/arrow-parens': ['error', 'as-needed'], '@stylistic/brace-style': ['error', '1tbs'], '@stylistic/max-len': ['error', { code: 120, ignoreComments: true, // Ignore all comments ignoreRegExpLiterals: true, // Ignore lines containing regex patterns ignoreStrings: true, // Ignore lines with long strings (SVG paths, long text content) ignoreTemplateLiterals: true, // Ignore template literals ignoreTrailingComments: true, // Ignore trailing comments on lines with code ignoreUrls: true, // Ignore lines containing URLs }], '@stylistic/member-delimiter-style': ['error', { multiline: { delimiter: 'semi', requireLast: true, }, overrides: { interface: { multiline: { delimiter: 'semi', requireLast: true, }, }, }, singleline: { delimiter: 'semi', requireLast: false, }, }], // Prevent multiple consecutive empty lines but allow single ones '@stylistic/no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0, }], // For object destructuring patterns '@stylistic/object-curly-newline': ['error', {consistent: true}], // Prevent empty lines inside object literals and destructuring '@stylistic/object-curly-spacing': ['error', 'never'], '@stylistic/operator-linebreak': ['error', 'after', {overrides: {'|': 'before'}}], '@stylistic/padded-blocks': ['error', 'never'], '@stylistic/padding-line-between-statements': ['error', // Allow any spacing between imports (to allow grouping and other import rules) {blankLine: 'any', next: 'import', prev: 'import'}, // Always require blank line after imports when followed by non-imports {blankLine: 'always', next: '*', prev: 'import'}, // Re-allow consecutive imports with no blank line. Must stay AFTER the // rule above (last match wins) or every adjacent import is forced apart. {blankLine: 'any', next: 'import', prev: 'import'}, // Allow any spacing between variable declarations (before and between) {blankLine: 'any', next: ['const', 'let', 'var'], prev: '*'}, // Always require blank line after variable declarations when followed by non-variables {blankLine: 'always', next: '*', prev: ['const', 'let', 'var']}, // But allow variables to be followed by variables without forcing blank line (override above) {blankLine: 'any', next: ['const', 'let', 'var'], prev: ['const', 'let', 'var']}, // Always require blank line before return statements {blankLine: 'always', next: 'return', prev: '*'}, // Always require blank line before case and default statements {blankLine: 'always', next: '*', prev: ['case', 'default']}, // Allow blank lines between JSX elements (expression statements) {blankLine: 'any', next: 'expression', prev: 'expression'}], '@stylistic/quote-props': ['error', 'as-needed'], '@stylistic/semi': ['error', 'always'], '@stylistic/switch-colon-spacing': 'error', 'granular-selectors/granular-selectors': ['error', { include: ['use.*Selector.*', 'use.*Store.*'], }], 'lodash/import-scope': [2, 'method'], 'no-console': 'warn', 'no-param-reassign': ['error', {props: false}], 'no-restricted-imports': [ 'error', { paths: [ { importNames: ['default'], message: '\n We want to avoid importing react directly. Please import individual hooks and types from the react module instead.', name: 'react', }, ], }, ], 'no-use-before-define': 'off', 'perfectionist/sort-imports': [ 'error', { customGroups: [ { elementNamePattern: ['^react$'], groupName: 'react', }, { elementNamePattern: [ '^actions/.+', '^client/.+', '^components/.+', '^contexts/.+', '^genericTypes/.+', '^hooks/.+', '^images/.+', '^mocks/.+', '^pages/.+', '^server/.+', '^slices/.+', '^src/.+', '^state/.+', '^tracking/.+', '^types/.+', '^utils/.+', ], groupName: 'alias', }, ], groups: [ 'react', 'type-import', ['type-builtin', 'type-external'], 'type-internal', 'type-parent', 'type-sibling', 'type-index', ['value-builtin', 'value-external'], 'value-internal', 'alias', 'value-parent', 'value-sibling', 'value-index', 'ts-equals-import', 'unknown', ], internalPattern: ['^~/.+', '^@kununu/.+'], type: 'natural', }, ], 'perfectionist/sort-modules': ['error', {type: 'usage'}], 'perfectionist/sort-union-types': ['error', { groups: [ 'conditional', 'function', 'import', 'intersection', 'keyword', 'literal', 'named', 'object', 'operator', 'tuple', 'union', 'nullish', ], }], 'prefer-template': 'error', 'react-hooks/exhaustive-deps': 'warn', // React Compiler rules: advisory for now. They flag patterns the compiler // can't optimise, but the current codebases use them widely; keep as // warnings rather than blocking errors. 'react-hooks/immutability': 'warn', 'react-hooks/refs': 'warn', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/set-state-in-effect': 'warn', 'react-hooks/use-memo': 'warn', 'react/jsx-closing-bracket-location': ['error', 'tag-aligned'], // More restrictive JSX rules that are auto-fixable 'react/jsx-curly-spacing': ['error', 'never'], 'react/jsx-first-prop-new-line': ['error', 'multiline-multiprop'], // Control JSX prop formatting more precisely 'react/jsx-max-props-per-line': ['error', {maximum: 1, when: 'multiline'}], // Keep the non-auto-fixable rule to detect the issue 'react/jsx-props-no-multi-spaces': 'error', // Use jsx-tag-spacing for what it can auto-fix 'react/jsx-tag-spacing': ['error', { afterOpening: 'never', beforeClosing: 'never', beforeSelfClosing: 'always', closingSlash: 'never', }], 'sonarjs/deprecation': ['warn'], // TypeScript already infers/checks return types; this duplicates it. 'sonarjs/function-return-type': 'off', 'sonarjs/todo-tag': ['warn'], }, }, ]);