aboutsummaryrefslogtreecommitdiffstats
path: root/.eslintrc.cjs
diff options
context:
space:
mode:
authorArmand Philippot <git@armandphilippot.com>2023-09-25 12:33:00 +0200
committerArmand Philippot <git@armandphilippot.com>2023-10-24 12:23:48 +0200
commit31695306bfed44409f03006ea717fd2cceff8f87 (patch)
tree03c1f4dab34d8f4927e1b824ddf0ab8031538221 /.eslintrc.cjs
parenta22214f2a2efcdac2666e1aad3332cb49d2f2198 (diff)
build(eslint): improve ESlint rules
In my opinion, next/core-web-vitals rules are too loose so I added a custom config to improve code consistency and to enforce best practices.
Diffstat (limited to '.eslintrc.cjs')
-rw-r--r--.eslintrc.cjs606
1 files changed, 602 insertions, 4 deletions
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 09a4e71..7e2f9b1 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -1,11 +1,248 @@
module.exports = {
- extends: ['next/core-web-vitals', 'prettier', 'plugin:storybook/recommended'],
- plugins: ['formatjs'],
+ env: {
+ browser: true,
+ es6: true,
+ node: true,
+ },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:import/recommended',
+ 'plugin:react/recommended',
+ 'plugin:react/jsx-runtime',
+ 'plugin:react-hooks/recommended',
+ 'plugin:jsx-a11y/recommended',
+ 'next/core-web-vitals',
+ 'prettier',
+ ],
+ plugins: ['react', 'import', 'jsx-a11y', 'formatjs'],
parserOptions: {
- project: ['tsconfig.json', './tests/cypress/tsconfig.json'],
- tsconfigRootDir: __dirname,
+ ecmaFeatures: {
+ jsx: true,
+ },
+ ecmaVersion: 'latest',
+ },
+ settings: {
+ 'import/extensions': ['.js', '.jsx'],
+ 'import/resolver': {
+ node: true,
+ },
+ react: {
+ version: 'detect',
+ },
},
rules: {
+ // CORE RULES
+ 'array-callback-return': [
+ 'error',
+ { allowImplicit: false, checkForEach: true },
+ ],
+ 'arrow-body-style': [
+ 'error',
+ 'as-needed',
+ { requireReturnForObjectLiteral: true },
+ ],
+ 'block-scoped-var': 'error',
+ // cspell:disable-next-line
+ camelcase: [
+ 'error',
+ {
+ properties: 'always',
+ ignoreDestructuring: false,
+ ignoreImports: false,
+ ignoreGlobals: true,
+ },
+ ],
+ complexity: ['error', { max: 20 }],
+ 'consistent-return': ['error', { treatUndefinedAsUnspecified: false }],
+ 'default-case': 'error',
+ 'default-case-last': 'error',
+ 'default-param-last': 'error',
+ 'dot-notation': ['error', { allowKeywords: true }],
+ eqeqeq: ['error', 'always', { null: 'always' }],
+ 'func-name-matching': ['error', 'always'],
+ 'func-names': ['error', 'always'],
+ 'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
+ 'guard-for-in': 'error',
+ 'init-declarations': ['error', 'always'],
+ 'max-depth': ['error', { max: 4 }],
+ 'max-nested-callbacks': ['error', { max: 10 }],
+ 'max-params': ['error', { max: 3 }],
+ 'max-statements': ['error', { max: 10 }],
+ 'new-cap': [
+ 'error',
+ {
+ capIsNew: true,
+ capIsNewExceptionPattern: '^[A-Z]+[A-Z._]*[A-Z]+$',
+ newIsCap: true,
+ properties: true,
+ },
+ ],
+ 'no-alert': 'error',
+ 'no-array-constructor': 'error',
+ 'no-await-in-loop': 'error',
+ 'no-bitwise': 'error',
+ 'no-caller': 'error',
+ 'no-confusing-arrow': [
+ 'error',
+ { allowParens: false, onlyOneSimpleParam: false },
+ ],
+ 'no-constant-binary-expression': 'error',
+ 'no-constructor-return': 'error',
+ 'no-div-regex': 'error',
+ 'no-duplicate-imports': ['error', { includeExports: false }],
+ 'no-else-return': ['error', { allowElseIf: false }],
+ 'no-empty-function': 'error',
+ 'no-empty-static-block': 'error',
+ 'no-eq-null': 'error',
+ 'no-eval': 'error',
+ 'no-extend-native': 'error',
+ 'no-extra-bind': 'error',
+ 'no-extra-label': 'error',
+ 'no-floating-decimal': 'error',
+ 'no-implicit-coercion': [
+ 'error',
+ {
+ boolean: false,
+ number: true,
+ string: true,
+ disallowTemplateShorthand: false,
+ },
+ ],
+ 'no-implicit-globals': 'error',
+ 'no-implied-eval': 'error',
+ 'no-invalid-this': ['error', { capIsConstructor: true }],
+ 'no-iterator': 'error',
+ 'no-label-var': 'error',
+ 'no-labels': ['error', { allowLoop: false, allowSwitch: false }],
+ 'no-lone-blocks': 'error',
+ 'no-lonely-if': 'error',
+ 'no-loop-func': 'error',
+ 'no-magic-numbers': [
+ 'error',
+ {
+ detectObjects: false,
+ enforceConst: false,
+ ignore: [-1, 0, 1, 2],
+ ignoreArrayIndexes: false,
+ ignoreClassFieldInitialValues: true,
+ ignoreDefaultValues: true,
+ },
+ ],
+ 'no-multi-assign': ['error', { ignoreNonDeclaration: false }],
+ 'no-multi-str': 'error',
+ 'no-negated-condition': 'error',
+ 'no-new': 'error',
+ 'no-new-func': 'error',
+ // cspell:disable-next-line
+ 'no-new-native-nonconstructor': 'error',
+ 'no-new-object': 'error',
+ 'no-new-wrappers': 'error',
+ 'no-octal-escape': 'error',
+ 'no-param-reassign': ['error', { props: false }],
+ 'no-promise-executor-return': 'error',
+ 'no-proto': 'error',
+ // cspell:disable-next-line
+ 'no-restricted-globals': ['error', 'event', 'fdescribe'],
+ 'no-restricted-syntax': ['error', 'WithStatement'],
+ 'no-return-assign': ['error', 'always'],
+ 'no-return-await': 'error',
+ 'no-script-url': 'error',
+ 'no-self-compare': 'error',
+ 'no-sequences': ['error', { allowInParentheses: true }],
+ 'no-shadow': [
+ 'error',
+ {
+ builtinGlobals: true,
+ hoist: 'functions',
+ allow: ['name'],
+ ignoreOnInitialization: false,
+ },
+ ],
+ 'no-template-curly-in-string': 'error',
+ 'no-throw-literal': 'error',
+ 'no-unmodified-loop-condition': 'error',
+ 'no-unneeded-ternary': ['error', { defaultAssignment: false }],
+ 'no-unreachable-loop': 'error',
+ 'no-unused-expressions': [
+ 'error',
+ {
+ allowShortCircuit: false,
+ allowTernary: false,
+ allowTaggedTemplates: false,
+ enforceForJSX: true,
+ },
+ ],
+ 'no-unused-vars': [
+ 'error',
+ {
+ argsIgnorePattern: '^_',
+ ignoreRestSiblings: true,
+ varsIgnorePattern: '^_',
+ },
+ ],
+ 'no-use-before-define': [
+ 'error',
+ {
+ functions: true,
+ classes: true,
+ variables: true,
+ allowNamedExports: false,
+ },
+ ],
+ 'no-useless-call': 'error',
+ 'no-useless-computed-key': ['error', { enforceForClassMembers: true }],
+ 'no-useless-concat': 'error',
+ 'no-useless-rename': [
+ 'error',
+ { ignoreImport: false, ignoreExport: false, ignoreDestructuring: false },
+ ],
+ 'no-useless-return': 'error',
+ 'no-var': 'error',
+ 'no-warning-comments': [
+ 'error',
+ { location: 'anywhere', terms: ['fixme'] },
+ ],
+ 'object-shorthand': ['error', 'properties'],
+ 'one-var': ['error', 'never'],
+ 'prefer-arrow-callback': [
+ 'error',
+ { allowNamedFunctions: false, allowUnboundThis: true },
+ ],
+ 'prefer-const': [
+ 'error',
+ { destructuring: 'any', ignoreReadBeforeAssign: false },
+ ],
+ 'prefer-destructuring': [
+ 'error',
+ {
+ array: false,
+ object: true,
+ },
+ {
+ enforceForRenamedProperties: false,
+ },
+ ],
+ 'prefer-named-capture-group': 'error',
+ 'prefer-object-has-own': 'error',
+ 'prefer-object-spread': 'error',
+ 'prefer-promise-reject-errors': ['error', { allowEmptyReject: false }],
+ 'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }],
+ 'prefer-rest-params': 'error',
+ 'prefer-spread': 'error',
+ 'prefer-template': 'error',
+ 'quote-props': [
+ 'error',
+ 'as-needed',
+ { keywords: false, numbers: true, unnecessary: true },
+ ],
+ radix: ['error', 'always'],
+ 'require-atomic-updates': ['error', { allowProperties: false }],
+ 'require-await': 'error',
+ strict: ['error', 'safe'],
+ 'symbol-description': 'error',
+ 'vars-on-top': 'error',
+ yoda: ['error', 'never', { exceptRange: false, onlyEquality: false }],
+ // FORMATJS PLUGIN
'formatjs/enforce-default-message': ['error', 'literal'],
'formatjs/enforce-description': ['error', 'literal'],
'formatjs/enforce-id': [
@@ -14,5 +251,366 @@ module.exports = {
idInterpolationPattern: '[sha512:contenthash:base64:6]',
},
],
+ // IMPORT PLUGIN
+ 'import/first': 'error',
+ 'import/newline-after-import': [
+ 'error',
+ { considerComments: true, count: 1 },
+ ],
+ 'import/no-absolute-path': 'error',
+ 'import/no-amd': 'error',
+ 'import/no-deprecated': 'warn',
+ 'import/no-duplicates': [
+ 'error',
+ { considerQueryString: true, 'prefer-inline': true },
+ ],
+ 'import/no-empty-named-blocks': 'error',
+ 'import/no-import-module-exports': 'error',
+ 'import/no-mutable-exports': 'error',
+ 'import/no-named-default': 'error',
+ 'import/no-self-import': 'error',
+ 'import/no-useless-path-segments': [
+ 'error',
+ {
+ noUselessIndex: true,
+ },
+ ],
+ 'import/order': [
+ 'error',
+ { alphabetize: { order: 'asc', caseInsensitive: true } },
+ ],
+ // JSX A11Y PLUGIN
+ 'jsx-a11y/control-has-associated-label': 'warn',
+ 'jsx-a11y/no-aria-hidden-on-focusable': 'error',
+ 'jsx-a11y/prefer-tag-over-role': 'warn',
+ // REACT PLUGIN
+ 'react/boolean-prop-naming': [
+ 'warn',
+ {
+ propTypeNames: ['bool'],
+ rule: '^(is|has|hide|show)[A-Z]([A-Za-z0-9]?)+',
+ message:
+ 'It is better if your prop ({{ propName }}) matches this pattern: ({{ pattern }})',
+ validateNested: true,
+ },
+ ],
+ 'react/button-has-type': [
+ 'error',
+ {
+ button: true,
+ submit: true,
+ reset: true,
+ },
+ ],
+ 'react/default-props-match-prop-types': [
+ 'error',
+ { allowRequiredDefaults: false },
+ ],
+ 'react/destructuring-assignment': [
+ 'error',
+ 'always',
+ { ignoreClassFields: false, destructureInSignature: 'ignore' },
+ ],
+ 'react/forbid-prop-types': ['error', { allowInPropTypes: true }],
+ 'react/hook-use-state': ['error', { allowDestructuredState: false }],
+ 'react/iframe-missing-sandbox': 'warn',
+ 'react/jsx-child-element-spacing': 'warn',
+ 'react/jsx-closing-bracket-location': ['error', 'tag-aligned'],
+ 'react/jsx-closing-tag-location': 'error',
+ 'react/jsx-curly-brace-presence': [
+ 'warn',
+ { children: 'ignore', propElementValues: 'always', props: 'never' },
+ ],
+ 'react/jsx-equals-spacing': ['warn', 'never'],
+ 'react/jsx-filename-extension': [
+ 'error',
+ { allow: 'as-needed', extensions: ['.jsx'] },
+ ],
+ 'react/jsx-fragments': ['error', 'syntax'],
+ 'react/jsx-key': [
+ 'error',
+ {
+ checkFragmentShorthand: true,
+ checkKeyMustBeforeSpread: false,
+ warnOnDuplicates: true,
+ },
+ ],
+ 'react/jsx-no-bind': [
+ 'error',
+ {
+ ignoreDOMComponents: false,
+ ignoreRefs: false,
+ allowArrowFunctions: false,
+ allowFunctions: false,
+ allowBind: false,
+ },
+ ],
+ 'react/jsx-no-constructed-context-values': 'error',
+ 'react/jsx-no-leaked-render': [
+ 'error',
+ { validStrategies: ['ternary', 'coerce'] },
+ ],
+ 'react/jsx-no-literals': [
+ 'warn',
+ {
+ allowedStrings: ['button', 'reset', 'submit'],
+ ignoreProps: false,
+ noAttributeStrings: true,
+ noStrings: true,
+ },
+ ],
+ 'react/jsx-no-script-url': 'error',
+ 'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }],
+ 'react/jsx-pascal-case': [
+ 'error',
+ {
+ allowAllCaps: false,
+ allowLeadingUnderscore: false,
+ allowNamespace: false,
+ },
+ ],
+ // cspell:disable-next-line
+ 'react/no-access-state-in-setstate': 'error',
+ 'react/no-array-index-key': 'warn',
+ 'react/no-arrow-function-lifecycle': 'error',
+ 'react/no-danger': 'warn',
+ 'react/no-did-mount-set-state': 'error',
+ 'react/no-did-update-set-state': 'error',
+ 'react/no-invalid-html-attribute': 'error',
+ 'react/no-namespace': 'error',
+ 'react/no-object-type-as-default-prop': 'error',
+ 'react/no-redundant-should-component-update': 'error',
+ 'react/no-this-in-sfc': 'error',
+ 'react/no-typos': 'error',
+ 'react/no-unstable-nested-components': ['error', { allowAsProps: true }],
+ 'react/no-unused-class-component-methods': 'error',
+ 'react/no-unused-prop-types': ['error', { skipShapeProps: true }],
+ 'react/no-unused-state': 'error',
+ 'react/no-will-update-set-state': 'error',
+ 'react/self-closing-comp': [
+ 'error',
+ {
+ component: true,
+ html: true,
+ },
+ ],
+ 'react/style-prop-object': 'error',
+ 'react/void-dom-elements-no-children': 'error',
},
+ overrides: [
+ {
+ files: ['*.ts', '*.tsx', '.mts', '.cts'],
+ extends: [
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:@typescript-eslint/stylistic-type-checked',
+ 'plugin:import/typescript',
+ ],
+ plugins: ['@typescript-eslint'],
+ parserOptions: {
+ ecmaVersion: 'latest',
+ project: ['tsconfig.eslint.json', './tests/cypress/tsconfig.json'],
+ sourceType: 'module',
+ tsconfigRootDir: __dirname,
+ warnOnUnsupportedTypeScriptVersion: true,
+ },
+ settings: {
+ 'import/extensions': ['.js', '.jsx', '.ts', '.tsx'],
+ 'import/parsers': {
+ '@typescript-eslint/parser': ['.ts', '.tsx'],
+ },
+ 'import/resolver': {
+ node: true,
+ typescript: {
+ alwaysTryTypes: true,
+ },
+ },
+ },
+ rules: {
+ 'react/jsx-filename-extension': [
+ 'error',
+ { allow: 'as-needed', extensions: ['.tsx'] },
+ ],
+ '@typescript-eslint/consistent-type-definitions': ['error', 'type'],
+ '@typescript-eslint/consistent-type-exports': [
+ 'error',
+ { fixMixedExportsWithInlineTypeSpecifier: false },
+ ],
+ '@typescript-eslint/consistent-type-imports': [
+ 'error',
+ {
+ prefer: 'type-imports',
+ fixStyle: 'inline-type-imports',
+ disallowTypeAnnotations: true,
+ },
+ ],
+ '@typescript-eslint/method-signature-style': ['error', 'property'],
+ '@typescript-eslint/no-base-to-string': 'warn',
+ '@typescript-eslint/no-confusing-void-expression': [
+ 'error',
+ { ignoreArrowShorthand: true, ignoreVoidOperator: true },
+ ],
+ '@typescript-eslint/no-duplicate-enum-values': 'error',
+ '@typescript-eslint/no-dynamic-delete': 'error',
+ '@typescript-eslint/no-import-type-side-effects': 'error',
+ '@typescript-eslint/no-invalid-void-type': [
+ 'warn',
+ { allowInGenericTypeArguments: true, allowAsThisParameter: false },
+ ],
+ '@typescript-eslint/no-mixed-enums': 'error',
+ '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error',
+ '@typescript-eslint/no-non-null-assertion': 'error',
+ '@typescript-eslint/no-redundant-type-constituents': 'error',
+ '@typescript-eslint/no-require-imports': 'error',
+ '@typescript-eslint/no-unnecessary-boolean-literal-compare': [
+ 'warn',
+ {
+ allowComparingNullableBooleansToTrue: true,
+ allowComparingNullableBooleansToFalse: true,
+ },
+ ],
+ '@typescript-eslint/no-unnecessary-condition': [
+ 'error',
+ {
+ allowConstantLoopConditions: false,
+ allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
+ },
+ ],
+ '@typescript-eslint/no-unnecessary-type-arguments': 'error',
+ '@typescript-eslint/no-unsafe-declaration-merging': 'error',
+ '@typescript-eslint/no-useless-empty-export': 'error',
+ '@typescript-eslint/prefer-enum-initializers': 'error',
+ '@typescript-eslint/prefer-function-type': 'error',
+ '@typescript-eslint/prefer-includes': 'error',
+ '@typescript-eslint/prefer-literal-enum-member': [
+ 'error',
+ { allowBitwiseExpressions: false },
+ ],
+ '@typescript-eslint/prefer-nullish-coalescing': [
+ 'error',
+ {
+ ignoreConditionalTests: true,
+ ignoreTernaryTests: true,
+ ignoreMixedLogicalExpressions: true,
+ allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
+ },
+ ],
+ '@typescript-eslint/prefer-optional-chain': 'error',
+ '@typescript-eslint/prefer-string-starts-ends-with': 'error',
+ '@typescript-eslint/prefer-ts-expect-error': 'error',
+ '@typescript-eslint/promise-function-async': [
+ 'error',
+ {
+ allowAny: true,
+ allowedPromiseNames: [],
+ checkArrowFunctions: true,
+ checkFunctionDeclarations: true,
+ checkFunctionExpressions: true,
+ checkMethodDeclarations: true,
+ },
+ ],
+ '@typescript-eslint/require-array-sort-compare': [
+ 'warn',
+ { ignoreStringArrays: false },
+ ],
+ '@typescript-eslint/switch-exhaustiveness-check': 'error',
+ '@typescript-eslint/unified-signatures': [
+ 'warn',
+ { ignoreDifferentlyNamedParameters: false },
+ ],
+ /**
+ * Typescript extension rules -- Core rules need to be disabled.
+ */
+ 'default-param-last': 'off',
+ '@typescript-eslint/default-param-last': 'error',
+ 'dot-notation': 'off',
+ '@typescript-eslint/dot-notation': ['error', { allowKeywords: true }],
+ 'init-declarations': 'off',
+ '@typescript-eslint/init-declarations': ['error', 'always'],
+ 'no-array-constructor': 'off',
+ '@typescript-eslint/no-array-constructor': 'error',
+ 'no-empty-function': 'off',
+ '@typescript-eslint/no-empty-function': 'error',
+ 'no-implied-eval': 'off',
+ '@typescript-eslint/no-implied-eval': 'error',
+ 'no-invalid-this': 'off',
+ '@typescript-eslint/no-invalid-this': [
+ 'error',
+ { capIsConstructor: true },
+ ],
+ 'no-loop-func': 'off',
+ '@typescript-eslint/no-loop-func': 'error',
+ 'no-magic-numbers': 'off',
+ '@typescript-eslint/no-magic-numbers': [
+ 'error',
+ {
+ detectObjects: false,
+ enforceConst: false,
+ ignore: [-1, 0, 1, 2],
+ ignoreArrayIndexes: false,
+ ignoreClassFieldInitialValues: true,
+ ignoreDefaultValues: true,
+ },
+ ],
+ 'no-shadow': 'off',
+ '@typescript-eslint/no-shadow': [
+ 'error',
+ {
+ builtinGlobals: true,
+ hoist: 'functions',
+ allow: ['name'],
+ ignoreOnInitialization: false,
+ },
+ ],
+ 'no-throw-literal': 'off',
+ '@typescript-eslint/no-throw-literal': 'error',
+ 'no-unused-expressions': 'off',
+ '@typescript-eslint/no-unused-expressions': [
+ 'error',
+ {
+ allowShortCircuit: false,
+ allowTernary: false,
+ allowTaggedTemplates: false,
+ enforceForJSX: true,
+ },
+ ],
+ 'no-unused-vars': 'off',
+ '@typescript-eslint/no-unused-vars': [
+ 'error',
+ {
+ argsIgnorePattern: '^_',
+ ignoreRestSiblings: true,
+ varsIgnorePattern: '^_',
+ },
+ ],
+ 'no-use-before-define': 'off',
+ '@typescript-eslint/no-use-before-define': [
+ 'error',
+ {
+ functions: true,
+ classes: true,
+ variables: true,
+ allowNamedExports: false,
+ },
+ ],
+ 'require-await': 'off',
+ '@typescript-eslint/require-await': 'error',
+ },
+ },
+ {
+ files: ['*.stories.@(ts|tsx|js|jsx|mjs|cjs)'],
+ extends: ['plugin:storybook/recommended'],
+ rules: {
+ 'react/jsx-no-literals': 'off',
+ 'storybook/csf-component': 'warn',
+ 'storybook/no-stories-of': 'warn',
+ 'storybook/no-title-property-in-meta': 'off',
+ },
+ },
+ {
+ files: ['*.test.@(ts|tsx|js|jsx|mjs|cjs)'],
+ rules: {
+ 'react/jsx-no-literals': 'off',
+ },
+ },
+ ],
};