Summary
ESLint 8 has been end of life since 2024-10-05 and the repository still uses the legacy .eslintrc.cjs, so a migration is due whatever we choose. Measured on dev, oxlint covers most of what we lint for at about a twentieth of the time, but not everything. Proposal: oxlint runs first, a slimmed ESLint 9 (flat config) keeps the rules oxlint does not enforce for us, wired with eslint-plugin-oxlint so nothing is checked twice.
Measurements (dev, Node 22, median of three runs)
|
Time |
Findings |
npm run lint:ci (ESLint 8.57) |
10.2 s |
0 errors, 14 warnings, all react-hooks/exhaustive-deps |
| oxlint 1.83.0 with the react plugin |
0.53 s |
the same violations in the same 13 files (reported as 15, one split in two) |
Parity
react-hooks/exhaustive-deps: implemented, on by default. rules-of-hooks: implemented, off by default (category "suspicious"), to switch on.
- The typescript-eslint rules our config sets to error (
no-explicit-any, ban-ts-comment, explicit-function-return-type, no-empty-function, no-empty-interface) and the no-restricted-imports guard that keeps src/domain pure all exist in oxlint but are off by default: each has to be enabled explicitly and proven with a failing fixture.
- Type-aware linting needs the separate
oxlint-tsgolint package, still experimental: not adopted here.
@oxlint/migrate only ported two rules and ignored every extends and both overrides blocks, so the config is written by hand.
What oxlint found that ESLint did not
- Real:
no-unsafe-optional-chaining in four test files (genuine), react/set-state-in-effect in 23 places and react/refs in 14, to triage rather than fix blindly.
- Style: four
unicorn hits.
- Noise: two false positives on
src/global.d.ts that tsc accepts.
Plan
.oxlintrc.json written by hand: the rules above switched on, the two global.d.ts false positives ignored with a comment, a fixture test proving the domain import guard and rules-of-hooks fire.
- ESLint moved to 9 with a flat config reduced to what oxlint does not cover, plus
eslint-plugin-oxlint.
lint and lint:ci run oxlint then ESLint; CI unchanged otherwise. Prettier is separate and unaffected.
- The new real findings triaged in their own issue, not mixed into the migration.
Acceptance
Summary
ESLint 8 has been end of life since 2024-10-05 and the repository still uses the legacy
.eslintrc.cjs, so a migration is due whatever we choose. Measured ondev, oxlint covers most of what we lint for at about a twentieth of the time, but not everything. Proposal: oxlint runs first, a slimmed ESLint 9 (flat config) keeps the rules oxlint does not enforce for us, wired witheslint-plugin-oxlintso nothing is checked twice.Measurements (dev, Node 22, median of three runs)
npm run lint:ci(ESLint 8.57)react-hooks/exhaustive-depsParity
react-hooks/exhaustive-deps: implemented, on by default.rules-of-hooks: implemented, off by default (category "suspicious"), to switch on.no-explicit-any,ban-ts-comment,explicit-function-return-type,no-empty-function,no-empty-interface) and theno-restricted-importsguard that keepssrc/domainpure all exist in oxlint but are off by default: each has to be enabled explicitly and proven with a failing fixture.oxlint-tsgolintpackage, still experimental: not adopted here.@oxlint/migrateonly ported two rules and ignored everyextendsand bothoverridesblocks, so the config is written by hand.What oxlint found that ESLint did not
no-unsafe-optional-chainingin four test files (genuine),react/set-state-in-effectin 23 places andreact/refsin 14, to triage rather than fix blindly.unicornhits.src/global.d.tsthattscaccepts.Plan
.oxlintrc.jsonwritten by hand: the rules above switched on, the twoglobal.d.tsfalse positives ignored with a comment, a fixture test proving the domain import guard andrules-of-hooksfire.eslint-plugin-oxlint.lintandlint:cirun oxlint then ESLint; CI unchanged otherwise. Prettier is separate and unaffected.Acceptance
lint:ciwall time reported before and after.package.json.