eslint-config-next: support ESLint 10 (#99628)
## Summary
`eslint-config-next` crashes under ESLint 10. This makes it work with
both ESLint 9 and 10 without raising Next.js's Node.js minimum
(`>=20.9.0`). ESLint 10 itself requires Node.js `^20.19.0 || ^22.13.0 ||
>=24`, so users on older Node.js 20 versions keep using ESLint 9.
### Upgraded
Dependencies that already have releases declaring ESLint 10 support:
- `typescript-eslint` `^8.46.0` → `^8.56.0`
- `eslint-plugin-react-hooks` `^7.0.0` → `^7.1.0`. 7.1 removed the
deprecated `react-hooks/component-hook-factories` rule (now a no-op)
from its recommended config, so it's no longer enabled. Fresh installs
of the previous `^7.0.0` range already get this version.
### Patched
Dependencies with no ESLint 10–compatible release that Next.js can use.
The plugin versions below are their latest releases. Their behavior is
adapted in `eslint-config-next`; the patches do nothing under ESLint 9.
- **Babel parser** (`@babel/eslint-parser` 7.24.6, bundled in `next`):
ESLint 10 registers configured and inline (`/* global */`) globals
through `scopeManager.addGlobals()`, which this parser's scope manager
doesn't have (`TypeError: scopeManager.addGlobals is not a function`).
`eslint-config-next/parser` adds it, doing the same scope maintenance
ESLint 9 did itself. Babel 8's parser supports ESLint 10 but requires
Node.js `^22.18 || >=24.11`. The parser comes from the user's installed
`next`, so the method is only added when it's missing.
- **`eslint-plugin-react`** (7.37.5) and **`eslint-plugin-import`**
(2.32.0): ESLint 10 removed the rule context members `getCwd()`,
`getFilename()`, `getPhysicalFilename()`, `getSourceCode()`,
`parserOptions` and `parserPath`. `eslint-plugin-react` calls them for
`settings.react.version: 'detect'`, and rules such as
`import/no-default-export` read `parserOptions`. The rules are patched
in place to restore these members, so the plugin objects keep their
identity; a wrapped copy breaks configs that also use the plugin's own
configs (`Cannot redefine plugin "react"`).
- **Import resolvers**: `eslint-plugin-import` loads the configured
resolvers by name from the linted file. With ESLint 10, npm doesn't
hoist `eslint-plugin-import` and `eslint-import-resolver-typescript`
because of the plugin's peer range, so the lookup falls back to the
`typescript` package (`Resolve error: typescript with invalid interface
loaded as resolver`) and rules such as `import/no-unresolved` can't
resolve TypeScript path aliases. The resolvers are referenced by their
resolved paths again, as before the flat config rewrite (#84874). As in
v15, a user's own `import/resolver` entries are used in addition to
these rather than replacing them.
### Unchanged
- `eslint-plugin-jsx-a11y` (6.10.2) and `@next/eslint-plugin-next` don't
use the removed APIs, and `eslint-plugin-react-hooks` falls back to
their replacements.
- `eslint-plugin-jsx-a11y` 6.10.2 is its latest release, so there's no
newer version to upgrade to.
### Other
- **Docs**: ESLint 10 support, its Node.js requirement, and the peer
dependency warnings.
- **Build**: `@types/node` added as a dev dependency with `"types":
["node"]`, since TypeScript 6 no longer includes `@types` packages
automatically and the config now uses `require.resolve`.
### Known limitations
`eslint-plugin-react`, `eslint-plugin-import` and
`eslint-plugin-jsx-a11y` don't list ESLint 10 in their peer dependencies
yet. Until they publish releases that do:
- Installs with ESLint 10 show peer dependency warnings (`npm warn
ERESOLVE overriding peer dependency`, pnpm `unmet peer`), but succeed
and lint correctly.
- Installs with strict peer dependencies fail.
- With npm, projects that list `eslint-plugin-react` as a direct
dependency fail with `ERESOLVE` unless installed with
`--legacy-peer-deps` or `--force`, because npm treats peer conflicts in
the project's own dependencies as errors.
## Verification
- New `test/production/eslint-config-next` test, which installs the
packed config with ESLint 9.37.0 and 10.11.0 and requires both to report
the same diagnostics for a JavaScript project (Babel parser) and a
TypeScript project (typescript-eslint parser). A third case installs
with npm and ESLint 10, where the plugins and resolvers are nested under
`eslint-config-next`. Disabling each patch in the ESLint 10 installs
makes ESLint crash, throw a config error, or fail to resolve imports, so
the test fails.
- CI runs the test on Node.js 20.9.0.
<!-- NEXT_JS_LLM -->