Skip to content

feat: adopt ESLint 10 by replacing eslint-plugin-react - #133

Merged
neolitec merged 1 commit into
mainfrom
neolitec/eslint-10-is-blocked-by-the-plugin-ecosystem
Aug 14, 2026
Merged

feat: adopt ESLint 10 by replacing eslint-plugin-react#133
neolitec merged 1 commit into
mainfrom
neolitec/eslint-10-is-blocked-by-the-plugin-ecosystem

Conversation

@neolitec

Copy link
Copy Markdown
Owner

Closes #119.

ESLint 9 reached end of life on 2026-08-06, so holding at 9 now means running an unsupported linter. eslint-plugin-react still has no ESLint 10 release — last release was 7.37.5 in April 2025, and the compatibility PRs (#3979, #4022) have sat unreviewed for months. So we move instead of waiting.

The block was narrower than #119 recorded

Only one of the three plugins was actually broken:

Plugin Reality Action
eslint-plugin-react genuinely broken — calls the removed context.getFilename() unguarded during React version detection, so every rule throws @eslint-react/eslint-plugin
eslint-plugin-jsx-a11y works fine; only its declared peer range lagged eslint-plugin-jsx-a11y-x (e18e fork, declares ^9 || ^10)
eslint-plugin-import never wired into eslint.config.js at all — only present as a peer of the equally unused eslint-import-resolver-typescript both deleted

Two of the three "blockers" were doing nothing. That's the bulk of the lockfile shrink.

Behaviour changes

  • react/no-unused-prop-types is dropped. ESLint React has no equivalent (it's a PropTypes-era rule). Costs nothing here — this package is TypeScript-only and ships no PropTypes.
  • The jsx-runtime config is gone. ESLint 10 tracks JSX references natively.
  • 15 new warnings from the stricter ruleset are disabled at config level, each with its reasoning inline. All were inapplicable or actively wrong for this package:
    • no-children-map / no-children-to-array / no-clone-element (9×) — this is the library's core mechanism: it reads <Tab> children's props and clones them with the wiring they need.
    • no-forward-refpeerDependencies still declare react: ^17 \|\| ^18 \|\| ^19. Passing ref as a plain prop would break every consumer below 19. Revisit when the peer range drops <19.
    • naming-convention-ref-name (4×) — assumes useRef holds a DOM ref; ours hold a timer handle, a focus index and a node array. timeoutId and tabRefs are the accurate names.
    • use-state — false positive; useState is already destructured, the rule just doesn't recognise a destructuring pattern in the value position.

Also

  • Removed the eslint / @eslint/js major holds from dependabot.yml (added in ci: hold back the ESLint and TypeScript majors that are blocked upstream #123) — there's nothing left to hold. The typescript hold stays; TS 7 is a separate block.
  • CI lint job moves Node 20 → 22, the floor declared by @eslint-react/eslint-plugin. Verified it does run on Node 20 in practice, but matching the declared engine avoids surprises.

Verification

pnpm lint exits 0 with zero warnings, pnpm build exits 0, 68/68 tests pass across 5 files. New dependencies are MIT with SLSA provenance attestations.

🤖 Generated with Claude Code

ESLint 9 reached end of life on 2026-08-06, and eslint-plugin-react still
has no release supporting ESLint 10 — its last release was 7.37.5 in April
2025, and the two compatibility PRs (#3979, #4022 upstream) have been open
for months awaiting review. Waiting it out is no longer viable.

The block turned out to be narrower than #119 recorded. Only one of the
three plugins was actually broken:

- eslint-plugin-react calls the removed context.getFilename() unguarded
  while detecting the React version, so every rule throws. Replaced with
  @eslint-react/eslint-plugin, which is TypeScript-first and supports
  ESLint 10. Not a drop-in: react/no-unused-prop-types has no equivalent
  and is dropped, which costs nothing here since this package is
  TypeScript-only and ships no PropTypes. The jsx-runtime config is also
  gone — ESLint 10 tracks JSX references natively.
- eslint-plugin-jsx-a11y only lagged in its declared peer range. Swapped
  for eslint-plugin-jsx-a11y-x, the e18e fork that declares ^9 || ^10.
- eslint-plugin-import was never wired into eslint.config.js at all. It
  was only present as a peer of eslint-import-resolver-typescript, which
  was equally unused. Both removed rather than migrated.

The new recommended ruleset raises 15 warnings, all of which are either
inapplicable or wrong for this package, so they are disabled at config
level with the reasoning inline. Of note, no-forward-ref is off because
peerDependencies still declare react ^17 || ^18 || ^19: passing ref as a
plain prop would break every consumer below 19.

CI moves to Node 22, the floor declared by @eslint-react/eslint-plugin.

Closes #119
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedeslint-plugin-jsx-a11y-x@​0.2.08210010082100
Added@​eslint/​js@​10.0.110010010089100
Added@​eslint-react/​eslint-plugin@​5.18.69910010096100
Addedeslint@​10.8.19710010096100

View full report

@neolitec
neolitec merged commit 1e71df0 into main Aug 14, 2026
7 checks passed
@neolitec
neolitec deleted the neolitec/eslint-10-is-blocked-by-the-plugin-ecosystem branch August 14, 2026 16:41
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.

ESLint 10 is blocked by the plugin ecosystem

1 participant