next.js/eslint.config.mjs
eslint.config.mjs576 lines15.4 KB
import { defineConfig } from 'eslint/config'
import react from 'eslint-plugin-react'
import reactHooks from 'eslint-plugin-react-hooks'
import jest from 'eslint-plugin-jest'
import _import from 'eslint-plugin-import'
import jsdoc from 'eslint-plugin-jsdoc'
import { fixupPluginRules } from '@eslint/compat'
import globals from 'globals'
import babelParser from '@babel/eslint-parser'
import tseslint from 'typescript-eslint'
import nextEslintPluginInternal from '@next/eslint-plugin-internal'
import mdxParser from 'eslint-mdx'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import js from '@eslint/js'
import { FlatCompat } from '@eslint/eslintrc'
import eslintignore from './.config/eslintignore.mjs'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({
  baseDirectory: __dirname,
  recommendedConfig: js.configs.recommended,
  allConfig: js.configs.all,
})

// This is the default eslint config that is used by IDEs. It does not use
// computation-heavy type-checked rules to ensure maximum responsiveness while
// writing code. In addition, there is .eslintrc.cli.json that does use
// type-checked rules in addition to the rules defined here, and it is used
// when running `pnpm lint-eslint` locally or in CI.
export default defineConfig([
  eslintignore,
  {
    files: ['**/*.{js,jsx,mjs,ts,tsx,mts,mdx}'],
    linterOptions: {
      reportUnusedDisableDirectives: 'error',
    },
    plugins: {
      '@typescript-eslint': tseslint.plugin,
      react,
      'react-hooks': reactHooks,
      jest,
      import: fixupPluginRules(_import),
      jsdoc,
    },
    languageOptions: {
      globals: {
        ...globals.browser,
        ...globals.commonjs,
        ...globals.node,
        ...globals.jest,
      },
      parser: babelParser,
      ecmaVersion: 2020,
      sourceType: 'module',
      parserOptions: {
        requireConfigFile: false,
        ecmaFeatures: {
          jsx: true,
        },
        babelOptions: {
          presets: ['next/babel'],
          caller: {
            supportsTopLevelAwait: true,
          },
        },
      },
    },
    settings: {
      react: {
        version: 'detect',
      },
      'import/internal-regex': '^next/',
    },
    rules: {
      'array-callback-return': 'error',

      'default-case': [
        'error',
        {
          commentPattern: '^no default$',
        },
      ],

      'dot-location': ['error', 'property'],
      eqeqeq: ['error', 'smart'],
      'new-parens': 'error',
      'no-array-constructor': 'error',
      'no-caller': 'error',
      'no-cond-assign': ['error', 'except-parens'],
      'no-const-assign': 'error',
      'no-control-regex': 'error',
      'no-delete-var': 'error',
      'no-dupe-args': 'error',
      'no-dupe-class-members': 'error',
      'no-dupe-keys': 'error',
      'no-duplicate-case': 'error',
      'no-empty-character-class': 'error',
      'no-empty-pattern': 'error',
      'no-eval': 'error',
      'no-ex-assign': 'error',
      'no-extend-native': 'error',
      'no-extra-bind': 'error',
      'no-extra-label': 'error',
      'no-fallthrough': 'error',
      'no-func-assign': 'error',
      'no-implied-eval': 'error',
      'no-invalid-regexp': 'error',
      'no-iterator': 'error',
      'no-label-var': 'error',

      'no-labels': [
        'error',
        {
          allowLoop: true,
          allowSwitch: false,
        },
      ],

      'no-lone-blocks': 'error',
      'no-loop-func': 'error',

      'no-mixed-operators': [
        'error',
        {
          groups: [
            ['&', '|', '^', '~', '<<', '>>', '>>>'],
            ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
            ['&&', '||'],
            ['in', 'instanceof'],
          ],

          allowSamePrecedence: false,
        },
      ],

      'no-multi-str': 'error',
      'no-native-reassign': 'error',
      'no-negated-in-lhs': 'error',
      'no-new-func': 'error',
      'no-new-object': 'error',
      'no-new-symbol': 'error',
      'no-new-wrappers': 'error',
      'no-obj-calls': 'error',
      'no-octal': 'error',
      'no-octal-escape': 'error',
      'no-regex-spaces': 'error',

      'no-restricted-imports': [
        'error',
        {
          patterns: [
            {
              group: ['*/next-devtools/dev-overlay*'],
              message:
                'Use `next/dist/compiled/next-devtools` (`src/next-devtools/dev-overlay/entrypoint.ts`) instead. Prefer `src/next-devtools/shared/` for shared utils.',
            },
          ],
        },
      ],

      'no-restricted-syntax': [
        'error',
        'WithStatement',
        {
          message: 'substr() is deprecated, use slice() or substring() instead',
          selector: "MemberExpression > Identifier[name='substr']",
        },
        {
          selector:
            "BinaryExpression[left.object.name='workUnitStore'][left.property.name='type'][operator=/^(?:===|!==)$/]",
          message:
            'Use an exhaustive switch on `workUnitStore.type` (with a `never`-based default) instead of using if statements or ternaries.',
        },
        {
          selector:
            "BinaryExpression[left.type='ChainExpression'][left.expression.object.name='workUnitStore'][left.expression.property.name='type'][operator=/^(?:===|!==)$/]",
          message:
            'Use an exhaustive switch on `workUnitStore.type` (with a `never`-based default) instead of using if statements or ternaries.',
        },
      ],

      'no-script-url': 'error',
      'no-self-assign': 'error',
      'no-self-compare': 'error',
      'no-sequences': 'error',
      'no-shadow-restricted-names': 'error',
      'no-sparse-arrays': 'error',
      'no-template-curly-in-string': 'error',
      'no-this-before-super': 'error',
      'no-throw-literal': 'error',
      'no-undef': 'error',
      'no-unexpected-multiline': 'error',
      'no-unreachable': 'error',

      'no-unused-expressions': [
        'error',
        {
          allowShortCircuit: true,
          allowTernary: true,
          allowTaggedTemplates: true,
        },
      ],

      'no-unused-labels': 'error',

      'no-unused-vars': [
        'error',
        {
          args: 'none',
          caughtErrors: 'none',
          ignoreRestSiblings: true,
        },
      ],

      'no-use-before-define': 'off',
      'no-useless-computed-key': 'error',
      'no-useless-concat': 'error',
      'no-useless-constructor': 'error',
      'no-useless-escape': 'error',

      'no-useless-rename': [
        'error',
        {
          ignoreDestructuring: false,
          ignoreImport: false,
          ignoreExport: false,
        },
      ],

      'no-with': 'error',
      'no-whitespace-before-property': 'error',
      'react-hooks/exhaustive-deps': 'error',
      'require-yield': 'error',
      'rest-spread-spacing': ['error', 'never'],
      strict: ['error', 'never'],
      'unicode-bom': ['error', 'never'],
      'use-isnan': 'error',
      'valid-typeof': 'error',
      'getter-return': 'error',

      'react/forbid-foreign-prop-types': [
        'error',
        {
          allowInPropTypes: true,
        },
      ],

      'react/jsx-no-comment-textnodes': 'error',
      'react/jsx-no-duplicate-props': 'error',
      'react/jsx-no-target-blank': 'error',
      'react/jsx-no-undef': 'error',

      'react/jsx-pascal-case': [
        'error',
        {
          allowAllCaps: true,
          ignore: [],
        },
      ],

      'react/jsx-uses-react': 'error',
      'react/jsx-uses-vars': 'error',
      'react/no-danger-with-children': 'error',
      'react/no-deprecated': 'error',
      'react/no-direct-mutation-state': 'error',
      'react/no-is-mounted': 'error',
      'react/no-typos': 'error',
      'react/react-in-jsx-scope': 'off',
      'react/require-render-return': 'error',
      'react/style-prop-object': 'error',
      'react-hooks/rules-of-hooks': 'error',
      '@typescript-eslint/prefer-as-const': 'error',

      '@typescript-eslint/no-redeclare': [
        'error',
        {
          builtinGlobals: false,
          ignoreDeclarationMerge: true,
        },
      ],
    },
  },
  {
    files: [
      'test/**/*.js',
      'test/**/*.ts',
      'test/**/*.tsx',
      '**/*.test.ts',
      '**/*.test.tsx',
    ],
    ignores: ['test/tmp/**'],
    extends: compat.extends('plugin:jest/recommended'),
    rules: {
      'jest/expect-expect': 'off',
      'jest/no-disabled-tests': 'off',
      'jest/no-conditional-expect': 'off',
      'jest/valid-title': 'off',
      'jest/no-interpolation-in-snapshots': 'off',
      'jest/no-export': 'off',

      'jest/no-standalone-expect': [
        'error',
        {
          additionalTestBlockFunctions: [
            'retry',
            'itCI',
            'itHeaded',
            'itTurbopack',
            'itTurbopackDev',
            'itOnlyTurbopack',
          ],
        },
      ],
    },
  },
  {
    files: ['**/__tests__/**'],
    languageOptions: {
      globals: {
        ...globals.jest,
      },
    },
  },
  {
    files: ['**/*.ts', '**/*.tsx', '**/*.mts'],
    extends: [tseslint.configs.recommended, tseslint.configs.stylistic],
    languageOptions: {
      ecmaVersion: 2020,
      sourceType: 'module',
      parserOptions: {
        warnOnUnsupportedTypeScriptVersion: false,
      },
    },
    rules: {
      '@typescript-eslint/array-type': 'off',
      '@typescript-eslint/ban-ts-comment': 'off',
      '@typescript-eslint/ban-tslint-comment': 'off',
      '@typescript-eslint/no-empty-object-type': 'off',
      '@typescript-eslint/no-restricted-types': 'off',
      '@typescript-eslint/no-unsafe-function-type': 'off',
      '@typescript-eslint/no-wrapper-object-types': 'off',
      '@typescript-eslint/class-literal-property-style': 'off',
      '@typescript-eslint/consistent-generic-constructors': 'off',
      '@typescript-eslint/consistent-indexed-object-style': 'off',
      '@typescript-eslint/consistent-type-definitions': 'off',
      '@typescript-eslint/no-empty-function': 'off',
      '@typescript-eslint/no-namespace': 'off',
      '@typescript-eslint/no-shadow': 'off',
      '@typescript-eslint/no-empty-interface': 'off',
      '@typescript-eslint/no-explicit-any': 'off',
      '@typescript-eslint/no-inferrable-types': 'off',
      '@typescript-eslint/no-require-imports': 'off',
      '@typescript-eslint/no-var-requires': 'off',
      '@typescript-eslint/prefer-for-of': 'off',
      '@typescript-eslint/prefer-function-type': 'off',
      '@typescript-eslint/no-this-alias': 'off',
      '@typescript-eslint/triple-slash-reference': 'off',
      'no-var': 'off',
      'prefer-const': 'off',
      'prefer-rest-params': 'off',
      'prefer-spread': 'off',
      'no-unused-expressions': 'off',

      '@typescript-eslint/no-unused-expressions': [
        'error',
        {
          allowShortCircuit: true,
          allowTernary: true,
          allowTaggedTemplates: true,
        },
      ],

      'no-unused-vars': 'off',

      '@typescript-eslint/no-unused-vars': [
        'error',
        {
          args: 'none',
          ignoreRestSiblings: true,
          argsIgnorePattern: '^_',
          caughtErrors: 'none',
          caughtErrorsIgnorePattern: '^_',
          destructuredArrayIgnorePattern: '^_',
          varsIgnorePattern: '^_',
        },
      ],

      'no-use-before-define': 'off',
      'no-useless-constructor': 'off',
      '@typescript-eslint/no-use-before-define': 'off',
      '@typescript-eslint/no-useless-constructor': 'error',
      '@typescript-eslint/prefer-literal-enum-member': 'error',
    },
  },
  {
    files: ['packages/**/*.ts', 'packages/**/*.tsx'],
    plugins: {
      '@next/internal': nextEslintPluginInternal,
    },
    rules: {
      '@next/internal/typechecked-require': 'error',
      'jsdoc/no-types': 'error',
      'jsdoc/no-undefined-types': 'error',
    },
  },
  {
    files: [
      'packages/next/src/server/**/*.js',
      'packages/next/src/server/**/*.jsx',
      'packages/next/src/server/**/*.ts',
      'packages/next/src/server/**/*.tsx',
    ],
    plugins: {
      '@next/internal': nextEslintPluginInternal,
    },
    rules: {
      '@next/internal/no-ambiguous-jsx': 'error',
    },
  },
  {
    files: ['examples/**/*'],
    linterOptions: {
      reportUnusedDisableDirectives: 'off',
    },
    rules: {
      '@typescript-eslint/no-use-before-define': [
        'error',
        {
          functions: true,
          classes: true,
          variables: true,
          enums: true,
          typedefs: true,
        },
      ],

      'import/no-anonymous-default-export': [
        'error',
        {
          allowArrowFunction: false,
          allowAnonymousClass: false,
          allowAnonymousFunction: false,
          allowArray: true,
          allowCallExpression: true,
          allowLiteral: true,
          allowObject: true,
        },
      ],
    },
  },
  {
    files: ['packages/**'],
    ignores: [
      'packages/next/taskfile*.js',
      'packages/next/next-devtools.webpack-config.js',
      'packages/next/next-runtime.webpack-config.js',
    ],
    rules: {
      'no-shadow': [
        'error',
        {
          builtinGlobals: false,
        },
      ],

      'import/no-extraneous-dependencies': [
        'error',
        {
          devDependencies: false,
        },
      ],
    },
  },
  {
    files: ['packages/**/*.tsx', 'packages/**/*.ts'],

    rules: {
      'no-shadow': 'off',

      '@typescript-eslint/no-shadow': [
        'error',
        {
          builtinGlobals: false,
        },
      ],

      '@typescript-eslint/no-unused-vars': [
        'error',
        {
          args: 'all',
          argsIgnorePattern: '^_',
          caughtErrors: 'none',
          ignoreRestSiblings: true,
          varsIgnorePattern: '^_',
        },
      ],
    },
  },
  {
    files: [
      'packages/eslint-plugin-next/**/*.js',
      'test/unit/eslint-plugin-next/**/*.test.ts',
    ],
    extends: compat.extends('plugin:eslint-plugin/recommended'),
    languageOptions: {
      ecmaVersion: 5,
      sourceType: 'script',
    },
    rules: {
      'eslint-plugin/prefer-replace-text': 'error',
      'eslint-plugin/report-message-format': [
        'error',
        '.+\\. See: https://nextjs.org/docs/messages/[a-z\\-]+$',
      ],
      'eslint-plugin/require-meta-docs-description': [
        'error',
        {
          pattern: '.+',
        },
      ],
      'eslint-plugin/require-meta-docs-url': 'error',
    },
  },
  {
    files: ['packages/**/*.tsx', 'packages/**/*.ts'],
    rules: {
      '@typescript-eslint/consistent-type-imports': [
        'error',
        {
          disallowTypeAnnotations: false,
        },
      ],
      '@typescript-eslint/no-import-type-side-effects': 'error',
    },
  },
  {
    files: ['**/*.mdx'],
    extends: compat.extends('plugin:mdx/recommended'),
    languageOptions: {
      parser: mdxParser,
    },
    rules: {
      'react/jsx-no-undef': 'off',
    },
  },
  {
    files: [
      'packages/next/src/next-devtools/dev-overlay/**/*.tsx',
      'packages/next/src/next-devtools/dev-overlay/**/*.ts',
    ],
    extends: [reactHooks.configs.flat['recommended']],
    rules: {
      'react-hooks/exhaustive-deps': 'error',
    },
  },
  {
    // auto-generated file
    files: [
      'packages/next/src/build/swc/generated-native.d.ts',
      'packages/next/src/build/swc/generated-wasm.d.ts',
      'rspack/crates/binding/index.d.ts',
    ],
    linterOptions: {
      reportUnusedDisableDirectives: 'off',
    },
  },
  {
    files: ['turbopack/crates/turbopack-tests/tests/**/*'],
    rules: {
      'no-console': 'off',
    },
  },
])
Quest for Codev2.0.0
/
SIGN IN