Repository navigation
chore: replace ESLint with oxlint (type-aware) and run lint in CI - #845
Conversation
7236c18 to
d36b3dd
Compare
Replace ESLint 8, @typescript-eslint v5, eslint-plugin-react, eslint-plugin-react-hooks and eslint-plugin-no-only-tests with oxlint, including type-aware rules via oxlint-tsgolint. - .oxlintrc.json ports the current recommended presets (ESLint, typescript-eslint, eslint-plugin-react, eslint-plugin-react-hooks v7 including the React Compiler rules) rule by rule, keeping the existing warn-level overrides. Categories are off so oxlint upgrades don't change what's enforced. - Add typescript-eslint's recommended-type-checked rules. Rules with existing findings are warnings for now; the no-unsafe-* rules and require-await stay off. typescript/no-deprecated (warn) replaces react/no-deprecated. - vitest/no-focused-tests replaces eslint-plugin-no-only-tests. - Add test/tsconfig.json so type-aware rules (which use the nearest tsconfig.json) resolve test files with the test compiler settings. - Fix findings: require -> import in test/setupTests.ts (already failing lint on main), and await deleteApp in two afterEach hooks. - Suppress react/static-components in SuspenseWithPerf with a TODO (fixing it changes runtime behavior), plus one intentional pattern in a test and one only-throw-error false positive. - Run `npm run lint` in the test workflow before the tests. - Fix the `npm run lint -- --fix` instruction in CONTRIBUTING.md. - Regenerate reference docs (line number shift).
d36b3dd to
84902f8
Compare
tyler-reitz
left a comment
There was a problem hiding this comment.
Approving. I think switching is the right call for this repo. Speed isn't the reason (under a second vs 1.6s here). The win is getting type-aware rules and the React Compiler hook rules without maintaining an ESLint 9 + typescript-eslint + plugin stack. The tradeoff to watch: type-aware linting runs on oxlint-tsgolint, which is built on TypeScript 7 (typescript-go), while the repo compiles with TypeScript 6.0.3. So lint and tsc can disagree, and tsgolint upgrades follow TS releases. I didn't hit a disagreement here.
On moving to VoidZero tools (now part of Cloudflare): we already depend on Vite 8 (whose bundler, Rolldown, is built on Oxc) and Vitest, so this adds to a dependency we have rather than starting a new one. Switching back would also be cheap, since every rule is listed explicitly under its ESLint name (plugin rules only need a prefix rename).
On the rebased head, lint exits 0 locally and in CI, and both tsc configs are clean. It does catch things: a probe file with .only and an un-awaited promise fails the run with both reported as errors, and misspelled rule names fail it too.
Non-blocking:
- Lint runs in every test matrix leg. If it fails, that leg's tests are skipped.
- The
no-unsafe-*rules would add 76 findings, but only 21 insrc(the rest are intest/). They could go in as warnings.
Summary
Replaces ESLint 8 and its plugins (
@typescript-eslintv5,eslint-plugin-react,eslint-plugin-react-hooksv4,eslint-plugin-no-only-tests) with oxlint, including type-aware linting viaoxlint-tsgolint. Also runsnpm run lintin the test workflow before the tests.On
main,npm run lintcurrently fails (arequire()intest/setupTests.ts), which is presumably why it wasn't in CI.Rules
.oxlintrc.jsonlists rules explicitly. oxlint's categories are turned off, so upgrading oxlint doesn't change what's enforced. It's a rule-by-rule port of the latest recommended presets, keeping this repo's existing warn-level overrides:"jsx": "react"in tsconfig)vitest/no-focused-testsinstead ofeslint-plugin-no-only-testsType-aware rules from typescript-eslint's
recommended-type-checked:no-floating-promises,no-misused-promises,await-thenable,unbound-method,only-throw-error(promises allowed, for Suspense) and the rest of that preset are errors.no-redundant-type-constituents,no-unnecessary-type-assertionandrestrict-template-expressionshave existing findings, so they're warnings for now. They can become errors once those are cleaned up.no-deprecated(warning) replacesreact/no-deprecated. It flags anything marked@deprecatedin its types (e.g. RxJS'stoPromiseandempty, Vite 8'srollupOptions).no-unsafe-*rules are off. They flag every use of theanyvalues thatno-explicit-anyalready allows as a warning.require-awaitis off too (stylistic).Not ported:
react/prop-types: TypeScript covers it.react/jsx-uses-reactandreact/jsx-uses-vars: oxlint'sno-unused-varsalready handles JSX.no-octalandno-dupe-args: strict mode covers them.react/require-render-returnandno-useless-assignment: still experimental in oxlint.Code changes for new findings
test/setupTests.ts:require→import.test/sdk.test.tsx,test/firebaseApp.test.tsx: awaitdeleteAppinafterEach(no-misused-promises).src/performance.tsx: suppressedreact/static-componentswith a TODO.SuspenseWithPerfcreates its fallback component during render, so React remounts it, and records an extra measure, whenever it re-renders while suspended. Fixing that changes runtime behavior, so it's left for a follow-up.react/globals) and one false positive (only-throw-error:erroris always set whenstatus === 'error').test/tsconfig.json(extendstsconfig.test.json): type-aware rules use the nearesttsconfig.json, so without it, test files fell back to defaults (e.g.viresolved to an error type). Editors pick it up too.docs/reference: regenerated for a line number shift.CONTRIBUTING.md:npm run lint --fixdoesn't pass--fixthrough; changed it tonpm run lint -- --fix.Results
npm run lint: 0 errors, 89 warnings, ~0.3s (ESLint took ~2s).oxlintandoxlint-tsgolintadded (plus their platform binaries).tsc(src and tests), the full emulator test suite,npm run docs, andnpm ci+ lint on Node 22 and 24 all pass.