next.js icon indicating copy to clipboard operation
next.js copied to clipboard

next-lint Doesn't Support ESLint 9

Open dylandignan opened this issue 1 year ago • 61 comments
trafficstars

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/vigilant-pine-6wmz8y

To Reproduce

  1. Add next lint script to package.json per https://nextjs.org/docs/app/building-your-application/configuring/eslint
  2. Add .eslintrc.json to project per https://nextjs.org/docs/app/building-your-application/configuring/eslint
{
  "extends": "next/core-web-vitals"
}
  1. Run lint and get an error
➜  /workspace git:(master) ✗ npm run lint

> lint
> next lint

Invalid Options:
- Unknown options: useEslintrc, extensions, resolvePluginsRelativeTo, rulePaths, ignorePath, reportUnusedDisableDirectives
- 'extensions' has been removed.
- 'resolvePluginsRelativeTo' has been removed.
- 'ignorePath' has been removed.
- 'rulePaths' has been removed. Please define your rules using plugins.
- 'reportUnusedDisableDirectives' has been removed. Please use the 'overrideConfig.linterOptions.reportUnusedDisableDirectives' option instead.

Current vs. Expected behavior

Expected lint to run successfully, but it failed with errors.

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP PREEMPT_DYNAMIC Sun Aug  6 20:05:33 UTC 2023
  Available memory (MB): 4102
  Available CPU cores: 2
Binaries:
  Node: 20.9.0
  npm: 9.8.1
  Yarn: 1.22.19
  pnpm: 8.10.2
Relevant Packages:
  next: 14.2.1-canary.0 // Latest available version is detected (14.2.1-canary.0).
  eslint-config-next: 14.1.4
  react: 18.2.0
  react-dom: 18.2.0
  typescript: 5.1.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

ESLint (eslint-config-next)

Which stage(s) are affected? (Select all that apply)

next dev (local), next build (local), next start (local)

Additional context

It looks like this is coming from https://github.com/vercel/next.js/blob/canary/packages/next/src/cli/next-lint.ts. This needs to be changed to support ESLint 9's flat config https://eslint.org/docs/latest/use/migrate-to-9.0.0#flat-config. The migration guide is at https://eslint.org/docs/latest/use/configure/migration-guide

NEXT-3112

dylandignan avatar Apr 12 '24 17:04 dylandignan

Bumping this for visibility, it does not seem to prevent build but unsure of the impact.

✓ Compiled successfully
   Linting and checking validity of types  .. 
⨯ ESLint: Invalid Options: - Unknown options: useEslintrc, extensions - 'extensions' has been removed.
✓ Linting and checking validity of type
"next": "^14.2.0",
"eslint-config-next": "^14.2.0",
"eslint": "^9.0.0",
{
  "extends": "next/core-web-vitals"
}

davidjulakidze avatar Apr 13 '24 08:04 davidjulakidze

Bumping this, it is breaking package update automation at my corp

elmarsto avatar Apr 15 '24 16:04 elmarsto

Yeah it's breaking change because ESLint 9.0.0 is a major release with a ton of breaking changes. You better hope NextJS didn't adhoc around every single ESLint implementation detail, so the migration to the newest version wouldn't take forever on their end. For now a "fix" is to update next and eslint-config-next to 14.2.1 which limits the range of required ESLint version to eslint@"^7.23.0 || ^8.0.0" and therefore will crash on install/update instead.

GabenGar avatar Apr 16 '24 10:04 GabenGar

IMHO, the adoption should be done gradually in the following steps:

  • Migrate eslint-config-next, so it exposes both the legacy eslintrc config and the new flat config.
  • Change Next.js internal ESLint implementation, allows it to accept both the legacy config .eslintrc and the new flat config eslint.config.js.
  • Change next lint so that it can accept eslint.config.js
  • Change create-next-app built-in template to use eslint.config.js
  • Change Next.js example to use eslint.config.js

SukkaW avatar Apr 17 '24 02:04 SukkaW

IMHO, the adoption should be done gradually in the following steps:

  • Migrate eslint-config-next, so it exposes both the legacy eslintrc config and the new flat config.

  • Change Next.js internal ESLint implementation, allows it to accept both the legacy config .eslintrc and the new flat config eslint.config.js.

  • Change next lint so that it can accept eslint.config.js

  • Change create-next-app built-in template to use eslint.config.js

  • Change Next.js example to use eslint.config.js

I would like to add that the new flat config file can have multiple names:

  • eslint.config.js
  • eslint.config.mjs
  • eslint.config.cjs

Source: https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file

BnAmN avatar Apr 17 '24 13:04 BnAmN

Same error

image

package.json

"dependencies": {
    "next": "14.2.2"
},
"devDependencies": {
    "eslint": "^9.1.0",
    "eslint-config-next": "14.2.2"
}

.eslintrc.json

{
  "extends": ["next/core-web-vitals", "./node_modules/standard/eslintrc.json"],
  "rules": {
    "space-before-function-paren": "off"
  }
}

deploy

image

nxtvoid avatar Apr 21 '24 01:04 nxtvoid

Not a fix but you can ignore eslint during build like so:

// next.config.js
module.exports = {
  eslint: {
    // Warning: This allows production builds to successfully complete even if
    // your project has ESLint errors.
    ignoreDuringBuilds: true,
  },
}

This way you have manual control over eslint, you can run a custom linting config before build.

Willem-Jaap avatar Apr 21 '24 12:04 Willem-Jaap

Not a fix but you can ignore eslint during build like so:

// next.config.js
module.exports = {
  eslint: {
    // Warning: This allows production builds to successfully complete even if
    // your project has ESLint errors.
    ignoreDuringBuilds: true,
  },
}

This way you have manual control over eslint, you can run a custom linting config before build.

I went back to version "eslint": "^8.41.0" and the error stopped.

nxtvoid avatar Apr 21 '24 20:04 nxtvoid

Same error

Screenshot 2024-04-22 204334

.eslintrc.json : { "extends": "next/core-web-vitals" }

I updated all the packages to the latest version but this still does not solve the problem

I don’t want to go back to the old version to fix it. If anyone knows how to solve the problem without going to the old version, please help me 🙏

mirasayon avatar Apr 22 '24 15:04 mirasayon

The only package update of relevance is eslint-config-next, which is tied to the version of next, aka wait for one of the newest nextjs versions. Until then you have to stick to old one.

GabenGar avatar Apr 22 '24 16:04 GabenGar

Same issue here, I thought I was crazy. Downgrade to an old version for now... 🤖 😸

devDependencies:
- eslint 9.1.1
+ eslint 8.57.0 (9.1.1 is available)

dalindev avatar Apr 25 '24 21:04 dalindev

having the same problem here, I have a custom-shared lint config and my latest PR when linked locally to test it it fails, https://github.com/sebalaini/sebalaini-lints-config/pull/6

sebalaini avatar Apr 28 '24 09:04 sebalaini

If this gains attention from maintainers, this might be the perfect opportunity to rework some of the linting / doctoring behaviour of next lint, for example to support biome & eslint 9

Related: https://github.com/vercel/next.js/discussions/59347 https://github.com/vercel/next.js/discussions/59165

I'd be happy to contribute or add more context / ideas!

Willem-Jaap avatar Apr 29 '24 11:04 Willem-Jaap

same error when using bun + nextjs 14 + eslint 9.

Xanonymous-GitHub avatar May 04 '24 15:05 Xanonymous-GitHub

For those using @next/eslint-plugin-next, the ESLint Compatibility Utilities worked for me. CodeSandbox example: https://codesandbox.io/p/devbox/vibrant-hill-29h4tw

npm install --save-dev @next/eslint-plugin-next @eslint/compat @eslint/eslintrc

eslint.config.mjs:

import { fixupConfigRules } from "@eslint/compat";
import { FlatCompat } from "@eslint/eslintrc";

const compat = new FlatCompat();

export default [
  {
    ignores: [".next/"],
  },
  ...fixupConfigRules(compat.extends("plugin:@next/next/core-web-vitals")),
];

package.json:

{
  "scripts": {
    "lint": "eslint"
  },
  "dependencies": {
    "next": "^14.2.3",
    "react": "^18",
    "react-dom": "^18"
  },
  "devDependencies": {
    "@eslint/compat": "^1.0.1",
    "@eslint/eslintrc": "^3.0.2",
    "@next/eslint-plugin-next": "^14.2.3",
    "eslint": "^9.2.0"
  }
}

saltycrane avatar May 16 '24 00:05 saltycrane

same issue, still looking for a proper version update.

Edit by maintainer bot: Comment was automatically minimized because it was considered unhelpful. (If you think this was by mistake, let us know). Please only comment if it adds context to the issue. If you want to express that you have the same problem, use the upvote 👍 on the issue description or subscribe to the issue for updates. Thanks!

bonesoul avatar May 17 '24 15:05 bonesoul

I was trying your solution and it doesn't seem to work, since I run the command pnpm eslint --debug and it seems to load the plugin, but it doesn't load anything from the plugin, since nothing appears about Loading {extends: ‘plugin:react/recommended’} or Loading plugin ‘react’ from ... that should appear, so then the solution is not working I guess.

No react plugins, etc loaded

image

sawa-ko avatar May 29 '24 22:05 sawa-ko

@saltycrane

sawa-ko avatar May 29 '24 22:05 sawa-ko

With just ...fixupConfigRules(compat.extends('next', 'plugin:@next/next/core-web-vitals')) it seems to work, but nevertheless there are still some console errors.

image image

sawa-ko avatar May 29 '24 22:05 sawa-ko

Is there any update on a simple eslint.config.js configuration that works with NextJS and the ESLint v9? Using this doesn't work anymore with the new version of ESLint "extends": "next/core-web-vitals"

adrenaline681 avatar Jun 03 '24 00:06 adrenaline681

Hi everyone,

Unfortunately, we were not able to ship this with Next.js 15. The current understanding of this is that we cannot do a clean ship of this until certain plugins add support for ESLint v9. For example:

  • https://github.com/vercel/next.js/blob/3dc2e672f1a9eb16564779fba44eab3b0393b8dd/packages/eslint-config-next/index.js#L33-L35
  • https://github.com/import-js/eslint-plugin-import, https://github.com/import-js/eslint-plugin-import/issues/2948
  • https://github.com/jsx-eslint/eslint-plugin-react, https://github.com/jsx-eslint/eslint-plugin-react/issues/3699
  • https://github.com/jsx-eslint/eslint-plugin-jsx-a11y, https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/issues/978

We will share updates as they come!

samcx avatar Jun 03 '24 23:06 samcx

@samcx the Next.js team may also want to consider switching from eslint-plugin-import to eslint-plugin-import-x, due to incompatibilities and other multiple issues and missing features

I mentioned something similar in my comment on the eslint-plugin-import issue (unfortunately marked as spam) - that it's possible to switch to eslint-plugin-import-x:


In case anyone wants to upgrade to ESLint v9 now, an alternative is to switch to the eslint-plugin-import-x fork:

  1. Install eslint-plugin-import-x
  2. Install @typescript-eslint/parser for TypeScript support
-import eslintImport from 'eslint-plugin-import';
+import eslintImportX from 'eslint-plugin-import-x';

/** @type {import('@typescript-eslint/utils/ts-eslint').FlatConfig.ConfigArray} */
const configArray = [
  {
    plugins: {
-     import: eslintImport,
+     'import-x': eslintImportX,
    },
    settings: {
      'import-x/parsers': {
        '@typescript-eslint/parser': ['.ts', '.tsx'],
      },
      'import-x/resolver': {
        // Load <rootdir>/tsconfig.json
        typescript: true,
        node: true,
      },
    },
    rules: {
      // Error on imports that don't match the underlying file system
-     // https://github.com/import-js/eslint-plugin-import/blob/master/docs/rules/no-unresolved.md
-     'import/no-unresolved': 'error',
+     // https://github.com/un-ts/eslint-plugin-import-x/blob/master/docs/rules/no-unresolved.md
+     'import-x/no-unresolved': 'error',
  },
];

karlhorky avatar Jun 04 '24 08:06 karlhorky

we cannot do a clean ship of this until certain plugins add support for ESLint v9

@samcx just as a note (probably you've already seen the tweet thread), there are ways of shipping a config with those unsupporting plugins like eslint-plugin-react:

  • tweet thread: https://x.com/karlhorky/status/1792500811307622631
  • our PR implementing support for ESLint v9 in our shared config (which also depends on eslint-plugin-react): https://github.com/upleveled/eslint-config-upleveled/pull/369/

karlhorky avatar Jun 04 '24 08:06 karlhorky

what about to move to OXLint?

SalahAdDin avatar Jul 22 '24 17:07 SalahAdDin

We're about to hit 4 months, could we please get an idea on the official stance here?

Should we hold back all NextJS projects on ESLint ^8 <9 indefinitely?


If this is the case, we'll have to tell Dependabot to skip eslint@9 across our Enterprise -_-

AlbinoGeek avatar Jul 28 '24 08:07 AlbinoGeek

@AlbinoGeek There is no reason to wait, you can use eslint v9 in nextjs project today. The only problem is that the complexity that eslint-config-next was hiding away will now be clearly visible. Here is the relevant part of our configuration:

import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
import nextPlugin from "@next/eslint-plugin-next";
import reactHooksPlugin from "eslint-plugin-react-hooks";
import reactRecommended from "eslint-plugin-react/configs/recommended.js";

const patchedNextPlugin = fixupPluginRules(nextPlugin);
const patchedReactHooksPlugin = fixupPluginRules(reactHooksPlugin);

const eslintConfig = [
    {
        name: "Country: react our rules",
        rules: {
	    "react/jsx-boolean-value": "error",
	    "react/jsx-max-props-per-line": "error",
	    "react/jsx-filename-extension": "error",
	    "react/jsx-sort-props": [
	        "error",
	        {
		    callbacksLast: true,
		    shorthandFirst: true,
		    reservedFirst: true,
		    multiline: "last",
	        },
	    ],
        },
    },
    {
	name: "Country: react hooks plugin",
	plugins: { "react-hooks": patchedReactHooksPlugin },
	rules: {
	    ...patchedReactHooksPlugin.configs.recommended.rules,
	    "react-hooks/exhaustive-deps": ["error"],
	},
    },
    {
	name: "Country: next plugin",
	plugins: { "@next/next": patchedNextPlugin },
	rules: {
	    ...patchedNextPlugin.configs["recommended"].rules,
	    ...patchedNextPlugin.configs["core-web-vitals"].rules,
	},
    },
    {
	name: "Country: jsx-a11y plugin",
	plugins: { "jsx-a11y": jsxA11yPlugin },
	...jsxA11yPlugin.flatConfigs.recommended,
    },
    {
	// We can't load eslint-config-next because it uses
	// @rushstack/eslint-patch which is not compatible with eslint
	// v9:
	name: "Country: eslint-config-next reimplemented",
	rules: {
	    "import/no-anonymous-default-export": "error",
	    "react/no-unknown-property": "off",
	    "react/react-in-jsx-scope": "off",
	    "react/prop-types": "off",
	    "jsx-a11y/alt-text": [
		"error",
		{ elements: ["img"], img: ["Image"] },
	    ],
	    "jsx-a11y/aria-props": "error",
	    "jsx-a11y/aria-proptypes": "error",
	    "jsx-a11y/aria-unsupported-elements": "error",
	    "jsx-a11y/role-has-required-aria-props": "error",
	    "jsx-a11y/role-supports-aria-props": "error",
	    "react/jsx-no-target-blank": "off",
	},
    }
]

DamienCassou avatar Jul 28 '24 10:07 DamienCassou

@AlbinoGeek There is no reason to wait, you can use eslint v9 in nextjs project today. The only problem is that the complexity that eslint-config-next was hiding away will now be clearly visible. Here is the relevant part of our configuration:

If this is such a relatively simple change I wonder what is taking vercel 4 months to ship it.

Thank you for the configuration, we will be merging this into many projects over the next week :)

You're a life-saver.

AlbinoGeek avatar Jul 28 '24 10:07 AlbinoGeek

@AlbinoGeek There is no reason to wait, you can use eslint v9 in nextjs project today. The only problem is that the complexity that eslint-config-next was hiding away will now be clearly visible. Here is the relevant part of our configuration:

import jsxA11yPlugin from "eslint-plugin-jsx-a11y";
import nextPlugin from "@next/eslint-plugin-next";
import reactHooksPlugin from "eslint-plugin-react-hooks";
import reactRecommended from "eslint-plugin-react/configs/recommended.js";

const patchedNextPlugin = fixupPluginRules(nextPlugin);
const patchedReactHooksPlugin = fixupPluginRules(reactHooksPlugin);

const eslintConfig = [
    {
        name: "Country: react our rules",
        rules: {
	    "react/jsx-boolean-value": "error",
	    "react/jsx-max-props-per-line": "error",
	    "react/jsx-filename-extension": "error",
	    "react/jsx-sort-props": [
	        "error",
	        {
		    callbacksLast: true,
		    shorthandFirst: true,
		    reservedFirst: true,
		    multiline: "last",
	        },
	    ],
        },
    },
    {
	name: "Country: react hooks plugin",
	plugins: { "react-hooks": patchedReactHooksPlugin },
	rules: {
	    ...patchedReactHooksPlugin.configs.recommended.rules,
	    "react-hooks/exhaustive-deps": ["error"],
	},
    },
    {
	name: "Country: next plugin",
	plugins: { "@next/next": patchedNextPlugin },
	rules: {
	    ...patchedNextPlugin.configs["recommended"].rules,
	    ...patchedNextPlugin.configs["core-web-vitals"].rules,
	},
    },
    {
	name: "Country: jsx-a11y plugin",
	plugins: { "jsx-a11y": jsxA11yPlugin },
	...jsxA11yPlugin.flatConfigs.recommended,
    },
    {
	// We can't load eslint-config-next because it uses
	// @rushstack/eslint-patch which is not compatible with eslint
	// v9:
	name: "Country: eslint-config-next reimplemented",
	rules: {
	    "import/no-anonymous-default-export": "error",
	    "react/no-unknown-property": "off",
	    "react/react-in-jsx-scope": "off",
	    "react/prop-types": "off",
	    "jsx-a11y/alt-text": [
		"error",
		{ elements: ["img"], img: ["Image"] },
	    ],
	    "jsx-a11y/aria-props": "error",
	    "jsx-a11y/aria-proptypes": "error",
	    "jsx-a11y/aria-unsupported-elements": "error",
	    "jsx-a11y/role-has-required-aria-props": "error",
	    "jsx-a11y/role-supports-aria-props": "error",
	    "react/jsx-no-target-blank": "off",
	},
    }
]

Migrating to ESLint 9 is terrible paintful

SalahAdDin avatar Jul 28 '24 17:07 SalahAdDin

@SalahAdDin @AlbinoGeek @DamienCassou there might be an easier migration path, I wrote a setup guide for Eslint 9 with Next.js, starting from a minimal app, this should probably scale to work with any project but please let me know if there is any issue, this is kind of a long read : Eslint 9 & Next.js — Setup Guide


TLDR;

  • You should install and override eslint 9 to avoid any issues on npm install
  • You should declare your own build and lint rules
  • You should opt-out of Next.js linting during build
  • You should migrate your .eslintrc.(js|json|yml) using ESlint's @eslint/migrate-config
  • You should patch problematic plugins using ESLint's fixupPluginRules

The resulting files should look something like this

package.json

{
...
  "scripts": {
    "dev": "next dev",
    "build": "npm run lint && next build",
    "start": "next start",
    "lint": "eslint ."
   },
  ...
  "devDependencies": {
    ...
    "eslint": "^9.8.0",
    ...
  },
  "overrides": {
    "eslint": "^9.8.0"
  }
}

next.config.mjs

/** @type {import('next').NextConfig} */
const nextConfig = {
  eslint: {
    ignoreDuringBuilds: true,
  },
};

export default nextConfig;

eslint.config.mjs

// @ts-check
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
import { fixupPluginRules } from "@eslint/compat";

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,
});

const pluginsToPatch = [
  "@next/next",
  // Other plugins to patch, example :
  // "react-hooks",
];

const compatConfig = [...compat.extends("next/core-web-vitals")];

const patchedConfig = compatConfig.map((entry) => {
  const plugins = entry.plugins;
  for (const key in plugins) {
    if (plugins.hasOwnProperty(key) && pluginsToPatch.includes(key)) {
      plugins[key] = fixupPluginRules(plugins[key]);
    }
  }
  return entry;
});

const config = [...patchedConfig, { ignores: [".next/*"] }];

export default config;

poksme avatar Aug 02 '24 15:08 poksme

@SalahAdDin @AlbinoGeek @DamienCassou there might be an easier migration path, I wrote a setup guide for Eslint 9 with Next.js, starting from a minimal app, this should probably scale to work with any project but please let me know if there is any issue, this is kind of a long read : Eslint 9 & Next.js — Setup Guide

TLDR;

  • You should install and override eslint 9 to avoid any issues on npm install
  • You should declare your own build and lint rules
  • You should opt-out of Next.js linting during build
  • You should migrate your .eslintrc.(js|json|yml) using ESlint's @eslint/migrate-config
  • You should patch problematic plugins using ESLint's fixupPluginRules

The resulting files should look something like this

package.json

{
...
  "scripts": {
    "dev": "next dev",
    "build": "npm run lint && next build",
    "start": "next start",
    "lint": "eslint ."
   },
  ...
  "devDependencies": {
    ...
    "eslint": "^9.8.0",
    ...
  },
  "overrides": {
    "eslint": "^9.8.0"
  }
}

next.config.mjs

/** @type {import('next').NextConfig} */
const nextConfig = {
  eslint: {
    ignoreDuringBuilds: true,
  },
};

export default nextConfig;

eslint.config.mjs

// @ts-check
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
import { fixupPluginRules } from "@eslint/compat";

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,
});

const pluginsToPatch = [
  "@next/next",
  // Other plugins to patch, example :
  // "react-hooks",
];

const compatConfig = [...compat.extends("next/core-web-vitals")];

const patchedConfig = compatConfig.map((entry) => {
  const plugins = entry.plugins;
  for (const key in plugins) {
    if (plugins.hasOwnProperty(key) && pluginsToPatch.includes(key)) {
      plugins[key] = fixupPluginRules(plugins[key]);
    }
  }
  return entry;
});

const config = [...patchedConfig, { ignores: [".next/*"] }];

export default config;

With this approach, do you just handle linting on the build process manually on the CI for example?

dir avatar Aug 02 '24 15:08 dir