Skip to content

feat!: support ESLint 10 - #32

Draft
sheepsteak wants to merge 6 commits into
mainfrom
chris/upgrade-eslint-10
Draft

sheepsteak wants to merge 6 commits into
mainfrom
chris/upgrade-eslint-10

Conversation

@sheepsteak

@sheepsteak sheepsteak commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Adds ESLint 10 support while keeping ESLint 9.38+ working, so consumers like Bolt can take this release first and move to ESLint 10 on their own schedule.

Dependencies

eslint moves from dependencies to a ^9.38.0 || ^10.0.0 peer dependency, so consumers bring their own ESLint. The 9.38 floor comes from eslint-plugin-jsonc 3 and eslint-plugin-unicorn 65, which both need it. The last 9.x release is 9.39.5, so in practice this is "latest ESLint 9". ESLint 9 is now end-of-life (npm marks it deprecated), so a later release can drop it.

prettier is now a ^3.0.0 peer dependency. recommended() always enables prettier/prettier, so consumers already need prettier installed, and ESLint should use their copy so linting and formatting agree. It also passes on eslint-plugin-prettier's own prettier peer, which strict package managers like Yarn PnP require. TypeScript stays undeclared: typescript-eslint caps its supported range (<6.1.0) while npm's latest is TypeScript 7, so a loose peer could pull in an unsupported version.

Each dependency floor is the lowest release that works, so the ranges stay wide and dedupe easily in consumers. For most of them that's the first release that supports ESLint 10. typescript-eslint is an exception: its first ESLint 10 release is 8.56.0, but 8.56 and 8.57 only support TypeScript below 6.0, so TypeScript 6 projects like Bolt would get an unsupported-version warning. Its floor is 8.58.0 instead.

Package Before After
typescript-eslint, @typescript-eslint/parser, @typescript-eslint/utils ^8.0.0-alpha.30 ^8.58.0
@stylistic/eslint-plugin-ts → @stylistic/eslint-plugin ^2.2.2 ^5.9.0
eslint-plugin-import-x ^3.0.1 ^4.16.2
eslint-plugin-jsonc ^2.16.0 ^3.0.0
eslint-plugin-unicorn ^55.0.0 ^65.0.0
eslint-config-prettier ^9.1.0 ^10.1.0
eslint-plugin-prettier ^5.1.3 ^5.1.0
  • eslint-config-prettier 9 already works with ESLint 10, but version 10 also turns off the @stylistic formatting rules that conflict with Prettier. Our own rule blocks come after it, so recommended() resolves to the same rule settings as before. The floor is 10.1.0 because 10.0.x doesn't load: 10.0.0 has no main field, and 10.0.1–10.0.3 keep their files under build/, so eslint-plugin-prettier's recommended config can't find eslint-config-prettier/prettier. eslint-plugin-prettier excludes 10.0.x from its peer range for the same reason. The malicious 10.1.6 and 10.1.7 publishes (GHSA-f29h-pxvx-f335) were removed from npm, so the range can't resolve to them.
  • eslint-plugin-prettier's floor drops to 5.1.0, the first release with the flat recommended config we import. Fresh installs get 5.5.7.
  • eslint-plugin-unicorn stops at 65 because 66+ requires ESLint >=10.4 and Node 22.
  • Removed globals (unused), @typescript-eslint/eslint-plugin (comes with typescript-eslint), @types/eslint, @typescript-eslint/rule-tester, and the resolutions override for the typescript-eslint alpha.
  • Node engines are now ^20.19.0 || ^22.13.0 || >=24, matching ESLint 10.
  • CI runs ESLint 9 and 10 on Node 20, 22 and 24. A Test job summarizes the matrix, so branch protection only needs to require that one check.

Rules

  • padding-line-between-statements and multiline-comment-style are deprecated in ESLint core, so they now come from @stylistic with the same options. The @stylistic version also checks statements inside declare module blocks, and it treats function overload signatures as statements. A new function-overload → function entry keeps overloads directly above their implementation allowed, as they were before.
  • Removed @stylistic/ts/type-annotation-spacing, since prettier already formats type annotations. Also removed @stylistic/ts/lines-around-comment: 'off', which had no effect.
  • @blitz/lines-around-comment now extends the unified @stylistic/lines-around-comment. Its tests pass unchanged.
  • The JSON config uses recommended-with-jsonc, because the flat/ prefix is only a backwards-compatibility alias in eslint-plugin-jsonc 3.

Build

@stylistic/eslint-plugin and eslint-plugin-jsonc are now ESM-only. tsconfig.json switches to module: node20. The output is still CommonJS in the same dist/ layout, which Bolt deep-imports, and Node loads the ESM dependencies through require(esm). That's what sets the Node 20.19 floor.

The repo now builds with TypeScript 6. tsconfig.json lists node in types because TypeScript 6 no longer includes @types packages by default. The emitted dist/ is byte-identical to the TypeScript 5.9 build. TypeScript 7 isn't possible yet: its compiler builds the repo fine, but typescript-eslint refuses to load with it, and our lint and tests run through typescript-eslint (typescript-eslint#10940).

Breaking changes

  • Requires ESLint ^9.38.0 || ^10.0.0, Prettier 3, and Node ^20.19.0 || ^22.13.0 || >=24.
  • padding-line-between-statements and multiline-comment-style are renamed to @stylistic/padding-line-between-statements and @stylistic/multiline-comment-style. Overrides and disable comments that use the old names need updating.
  • The @stylistic/ts plugin namespace is replaced by @stylistic. A consumer config that registers a different copy of @stylistic for the same files will get a "Cannot redefine plugin" error.
  • eslint-config-prettier 10 also turns off @stylistic formatting rules. A consumer that enables them in a config block before ours will now have them turned off. Blocks after ours aren't affected.

I haven't bumped the version. This should probably go out as 0.2.0.

Impact on Bolt

I linted Bolt's 6,760 JS/TS/JSON files with recommended() alone (without Bolt's own overrides), before and after this change. Messages went from 6,634 to 6,684, with identical results on Bolt's current ESLint 9.39.1 and on ESLint 10.14:

  • New: 70 import/order (import-x 4 orders some workspace and type imports differently), 2 padding lines inside declare module blocks, and 1 disable directive that's now unused. All are autofixable.
  • Gone: 18 @typescript-eslint/no-unused-vars "only used as a type" reports and 5 no-var.

Moving to eslint-config-prettier 10.1.8 and eslint-plugin-prettier 5.5.7 doesn't change any of these results.

Bolt doesn't reference any of the renamed or removed rules, and it can take this release without changing its ESLint version. When it does move to ESLint 10, note that eslint-plugin-react 7.37.5 (the latest) only declares support up to ESLint 9. Six of its rules crash on ESLint 10 (none of them in its recommended configs), but Bolt doesn't enable any react rules, so that only produces a peer warning.

Verification

  • pnpm build, pnpm lint and pnpm test pass with the newest versions in range, on both ESLint 10.14 and 9.39.5.
  • They also pass with every direct dependency at its floor (resolution-mode=lowest-direct, including typescript-eslint 8.58.0, TypeScript 6.0.2, eslint-config-prettier 10.1.0 and eslint-plugin-prettier 5.1.0), on both ESLint 10.0.0 and 9.38.0.
  • Ran on Node 20.19.0, 22, 24 and 26.
  • No new tests. The existing 169 rule tests now run against both ESLint majors.
  • Independent of fix(lines-around-comment): allow comments at the start of namespace blocks #31. The two merge cleanly, and all 175 tests pass on the combined branch.

Before merging

Branch protection on main still requires the old Test (18) check, which no longer exists. Before merging, switch the required checks to Test and Validate PR Title. After that, matrix changes like dropping ESLint 9 won't need settings changes. #31 still reports Test (18), so either merge it first or update it from main once this lands. Re-running its old CI run isn't enough, because that reuses the old workflow file.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas (n/a: no new logic)
  • I have made corresponding changes to the documentation if applicable (README now documents flat config and the ESLint and Prettier peers; lines-around-comment docs link the unified @stylistic rule)
  • I have added tests for this change (existing tests cover it, see Verification)

Require ESLint 10 as a peer dependency and bump each plugin to the first
version that supports it. Swap the deprecated core
padding-line-between-statements and multiline-comment-style rules for
their @Stylistic equivalents, and drop @stylistic/ts/type-annotation-spacing,
which prettier already covers.

BREAKING CHANGE: requires ESLint 10 and Node ^20.19.0 || ^22.13.0 || >=24.
padding-line-between-statements and multiline-comment-style are now
@stylistic/padding-line-between-statements and
@stylistic/multiline-comment-style, and the @stylistic/ts plugin namespace
is replaced by @Stylistic.
@bolt-new-by-stackblitz

Copy link
Copy Markdown

Review PR in StackBlitz Codeflow Run & review this pull request in StackBlitz Codeflow.

eslint-plugin-jsonc 3 and eslint-plugin-unicorn 65 need ESLint 9.38 or
later, so that's the floor for ESLint 9. CI now runs both majors.

recommended() always enables prettier/prettier, so consumers need
prettier installed, and ESLint should use their copy so lint and format
agree.
@sheepsteak sheepsteak changed the title feat!: upgrade to ESLint 10 feat!: support ESLint 10 Nov 11, 2026
8.56 and 8.57 only support TypeScript below 6.0, so projects on TypeScript 6 get an unsupported-version warning from them.
TypeScript 6 no longer includes @types packages by default, so tsconfig lists node explicitly. The emitted dist is unchanged.
Branch protection can require this one job instead of each matrix job, so changing the Node or ESLint matrix no longer means updating the required checks.
eslint-config-prettier 10 also turns off the @Stylistic formatting rules
that conflict with Prettier. Our own rule blocks come after it, so the
effective config is unchanged.

Both floors are the lowest releases that load. eslint-config-prettier
10.0.0 has no main field, and 10.0.1-10.0.3 ship their files under
build/, so eslint-plugin-prettier/recommended can't require
eslint-config-prettier/prettier. eslint-plugin-prettier 5.0.x has no
flat recommended config.
@d3lm

d3lm commented Nov 12, 2026

Copy link
Copy Markdown
Collaborator

We have to be careful with this and it has to be a new major version and we'd likely have to maintain both versions because other repos depend on this eslint plugin too which we can't easily upgrade especially TS 7.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants