Skip to content

ci: add oxlint as fast pre-lint pass before ESLint#10355

Open
benpsnyder wants to merge 3 commits intoTanStack:mainfrom
benpsnyder:feat/oxlint-eslint-bridge
Open

ci: add oxlint as fast pre-lint pass before ESLint#10355
benpsnyder wants to merge 3 commits intoTanStack:mainfrom
benpsnyder:feat/oxlint-eslint-bridge

Conversation

@benpsnyder
Copy link
Copy Markdown

@benpsnyder benpsnyder commented Mar 30, 2026

🎯 Changes

Add oxlint (Rust-based linter, ~50-100x faster than ESLint) as a first-pass linter that runs before ESLint in CI. Oxlint handles the bulk of correctness rules in ~250ms across 691 files, letting ESLint focus only on rules it exclusively covers (cspell, type-aware typescript-eslint rules, import ordering, react-hooks).

This pattern is already used by Preact, Sentry, Renovate, and Kibana. The implementation follows the same approach used in the AnalogJS repo (v3-alpha branch).

What changed:

  • New: oxlint.config.ts — typed config with defineConfig, plugins: typescript, vitest, import, react
  • Modified: eslint.config.js — imports oxlint config via jiti, appends buildFromOxlintConfig() as last entry to auto-disable overlapping rules, sets reportUnusedDisableDirectives: 'off'
  • Modified: package.json — adds test:oxlint script, adds it to test:pr/test:ci Nx targets and includedScripts
  • Modified: packages/vue-query/src/__tests__/useMutation.test.ts — fix pre-existing unsafe optional chaining with a defensive check
  • Modified: packages/react-query/src/__tests__/useQuery.promise.test.tsx — suppress false positive on vitest 3-arg describe() with options object
  • New devDependencies: oxlint, eslint-plugin-oxlint, jiti

Also incorporated from other open PRs:

Rules that stay ESLint-only (no oxlint equivalent)
  • @cspell/eslint-plugin (spell checking)
  • @typescript-eslint/consistent-type-imports, array-type, naming-convention, method-signature-style
  • @typescript-eslint/no-unnecessary-condition, no-unnecessary-type-assertion, no-for-in-array, require-await (type-aware)
  • import/* rules (ordering, no-commonjs, no-duplicates)
  • node/prefer-node-protocol
  • @stylistic/spaced-comment
  • sort-imports
  • eslint-plugin-react-hooks

Related issues & PRs

Superseded by this PR (can be closed):

Related but independent (not affected by this PR):

This is PR 1 of 6 in the Oxc/Rolldown/tsdown modernization series.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Integrated oxlint for additional code quality checks alongside existing linting
    • Added unused imports detection to linting rules
  • Chores

    • Updated ESLint configuration tooling and dependencies
    • Added test:oxlint script to validation pipeline
    • Standardized ESLint configuration structure across packages

…nfiguration

- Added `eslint-plugin-oxlint` and `jiti` as dependencies.
- Created `oxlint.config.ts` for custom linting rules and configurations.
- Updated `eslint.config.js` to include oxlint settings and disable overlapping ESLint rules.
- Modified package scripts to include a new `test:oxlint` command for running oxlint checks.
- Adjusted test files to comply with new linting rules.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 30, 2026

📝 Walkthrough

Walkthrough

Integrates oxlint into the repo: adds oxlint.config.ts, updates root eslint.config.js to load and append oxlint-generated configs at runtime via jiti, adds unused-imports plugin and rule tweaks, updates package scripts/devDependencies, and applies many ESLint export refactors and a couple small test edits.

Changes

Cohort / File(s) Summary
Root ESLint + Oxlint
eslint.config.js, oxlint.config.ts
Added oxlint.config.ts (defineConfig with plugins, envs, categories, overrides). Refactored eslint.config.js to export a typed config array, load ./oxlint.config.ts at runtime via jiti, append oxlint.buildFromOxlintConfig(oxlintConfig), register eslint-plugin-unused-imports, set unused-imports/no-unused-imports: warn, disable no-shadow and certain pnpm rules, and set tanstack/linter-options.reportUnusedDisableDirectives: 'off'.
Tooling / Scripts / Dependencies
package.json
Added test:oxlint script and included it in test:pr/test:ci, updated nx.includedScripts, bumped @tanstack/eslint-config, and added devDependencies: oxlint, eslint-plugin-oxlint, eslint-plugin-unused-imports, jiti.
Tests (small edits)
packages/react-query/src/__tests__/useQuery.promise.test.tsx, packages/vue-query/src/__tests__/useMutation.test.ts
Added an ESLint disable comment for vitest/valid-describe-callback and tightened a test assertion to assert presence of relevantMutation before checking nested fields.
ESLint export refactors (many files)
examples/react/.../eslint.config.js, packages/.../eslint.config.js (multiple files listed in diff)
Replaced inline export default [...] with a named config constant (JSDoc or TS-typed as import('eslint').Linter.Config[]) and export default config across numerous example and package ESLint flat config files. Changes are cosmetic/typing-only; rule contents preserved.

Sequence Diagram(s)

sequenceDiagram
  participant Runner as "ESLint Runner"
  participant RootConfig as "root eslint.config.js"
  participant Jiti as "jiti loader"
  participant OxlintConfig as "oxlint.config.ts"
  participant Oxlint as "oxlint.buildFromOxlintConfig"
  participant Final as "Final ESLint config"

  Runner->>RootConfig: require/import config
  RootConfig->>Jiti: load './oxlint.config.ts' (runtime)
  Jiti->>OxlintConfig: compile & return default export
  RootConfig->>Oxlint: call buildFromOxlintConfig(oxlintConfig)
  Oxlint-->>RootConfig: oxlint-generated config entries
  RootConfig->>Final: append oxlint entries & plugin settings
  Final-->>Runner: run lint with merged config
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through configs, line by line,
Jiti fetched oxlint in moonlit time,
Plugins whispered, rules aligned,
Warnings danced and errors chimed,
A rabbit's tidy linting rhyme ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ci: add oxlint as fast pre-lint pass before ESLint' clearly and specifically describes the main change: adding oxlint as a CI linting tool to run before ESLint.
Description check ✅ Passed The PR description comprehensively covers the required template sections: detailed 'Changes' section explaining the modifications across multiple files, checked release impact confirming docs/CI/dev-only with no changeset needed, and contributing steps followed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@benpsnyder benpsnyder changed the title feat: integrate oxlint for enhanced linting support and add oxlint configuration ci: add oxlint as fast pre-lint pass before ESLint Mar 30, 2026
- Added `eslint-plugin-unused-imports` to manage unused imports.
- Updated `@tanstack/eslint-config` to version 0.4.0 for improved linting rules.
- Modified ESLint configurations across multiple packages to include type annotations and ensure consistent exports.
- Adjusted rules in `oxlint.config.ts` to allow shadowing and refined other linting rules.
- Updated package dependencies in `pnpm-lock.yaml` to reflect the changes.
Copy link
Copy Markdown
Collaborator

@TkDodo TkDodo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally in favour of this, but what’s stopping us from replacing eslint completely with oxlint? I think it would even be fine if there isn’t a 1:1 mapping of the rules (dependent on which rule are missing of course). But I’d rather just have one linter than two ...

"eslint": "^9.36.0",
"eslint-plugin-oxlint": "^1.57.0",
"eslint-plugin-react-hooks": "^6.1.1",
"eslint-plugin-unused-imports": "^4.4.1",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need eslint-plugin-unused-imports? As far as I know, TypeScript will flag unused imports just fine

@nx-cloud
Copy link
Copy Markdown

nx-cloud bot commented Mar 30, 2026

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit db4c96a

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ❌ Failed 1m 24s View ↗
nx run-many --target=build --exclude=examples/*... ❌ Failed 35s View ↗

☁️ Nx Cloud last updated this comment at 2026-03-30 12:39:32 UTC

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
eslint.config.js (1)

10-12: Consider adding error context for config loading failure.

The dynamic import of oxlint.config.ts will throw if the file is missing or has syntax errors. While fail-fast behavior is reasonable here, the error message may not clearly indicate the source of the problem during ESLint initialization.

This is optional—the current approach works and keeps the code concise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@eslint.config.js` around lines 10 - 12, Wrap the dynamic import of
'./oxlint.config.ts' performed via createJiti(import.meta.url) and
jiti.import(...) in a try/catch so failures include clear context; catch the
error around the jiti.import call that assigns to oxlintConfig, augment or
rethrow a new Error that mentions it failed to load or parse the
oxlint.config.ts (including the original error.message or error), and ensure the
process exits or bubbles the error so ESLint initialization reports the config
filename and underlying error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@eslint.config.js`:
- Around line 10-12: Wrap the dynamic import of './oxlint.config.ts' performed
via createJiti(import.meta.url) and jiti.import(...) in a try/catch so failures
include clear context; catch the error around the jiti.import call that assigns
to oxlintConfig, augment or rethrow a new Error that mentions it failed to load
or parse the oxlint.config.ts (including the original error.message or error),
and ensure the process exits or bubbles the error so ESLint initialization
reports the config filename and underlying error.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a14ceddf-3071-442b-b19c-bf6435ac366f

📥 Commits

Reviewing files that changed from the base of the PR and between bddb0b8 and db4c96a.

📒 Files selected for processing (2)
  • eslint.config.js
  • packages/vue-query/src/__tests__/useMutation.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/vue-query/src/tests/useMutation.test.ts

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