Discord conversation:
https://discord.com/channels/1079625926024900739/1498648746433445988
Long story short: we are looking to move from eslint to oxlint in a
large (>15m LOC) monorepo. Internally we use
[lage](https://microsoft.github.io/lage/) for fast runs of lint +
caching. Eslint exposes a programmatic API that we can use to share the
same instance of eslint across different node processes. Thus saving us
time in startup. However, oxlint does not currently expose a
programmatic API. The workaround: reach deep into oxlint built source to
extract `lint()` API (from `dist/bindings.js`) and use that. Yes - it is
very hacky. But it allows us to use oxlint + lage. Reproduction of this
hacky solution can be found here:
https://github.com/MatissJanis/oxlint-lage-repro
In order for the hacky solution to work: we need to fix a few instances
of panic. Hence the PR.
Full disclosure: this was built using claude code (opus 4.7).
---------
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
This PR implements the `eslint/id-match` rule, which enforces a
configured naming regex for identifiers
ESLint ref: https://eslint.org/docs/latest/rules/id-match
Issue: https://github.com/oxc-project/oxc/issues/479
### Behavior Differences from ESLint
This implementation is intentionally stricter in two cases:
- Computed destructuring keys are checked, for example `const {
[bad_name]: x } = obj` and `({ [bad_name]: x } = obj)`, because the
computed key is a normal reference expression, not a binding introduced
by destructuring
- With `properties` enabled, ordinary top-level dynamic import option
keys are checked, for example `import("x", { bad_option: true })`.
Import attributes inside `with { ... }` are still ignored
### TypeScript
TypeScript syntax is supported on a best-effort basis for identifiers
that flow through the same visited AST node kinds and transparent
wrappers such as `as`, `satisfies`, non-null assertions, and
instantiation expressions
This is not a replacement for `@typescript-eslint/naming-convention` or
future tsgolint naming-convention work:
https://github.com/oxc-project/tsgolint/issues/186
### Regex Compatibility
The pattern is compiled with Rust regex syntax. JavaScript-specific
regex features such as lookaround and backreferences are not supported.
Unicode escapes should use Rust syntax, for example `\u{XXXX}`
### Algorithmic Complexity
The generated rule runner limits the hot path to identifier-like AST
nodes:
- `BindingIdentifier`
- `IdentifierReference`
- `IdentifierName`
- `PrivateIdentifier`
- `LabelIdentifier`
Each visited identifier first runs the regex check and returns
immediately when the name matches
For non-default configurations, the hot path is linear in the total
length of checked identifier names, with additional ancestor/context
checks only for failed regex matches
Space complexity is `O(1)` beyond the compiled regex and options. The
hot path does not allocate unless a diagnostic is emitted
The default pattern `^.+$` matches every non-empty identifier name, so
`should_run` skips rule work entirely for the default configuration
### AI Disclosure
Claude Opus 4.7 & Codex GPT 5.5
---------
Co-authored-by: Cameron Clark <cameron.clark@hey.com>
New place to share code for use in `src-js`, similar to how `crates/oxc_config` works.
Initially, I considered creating an `apps/oxc_cli` and moving `crates/oxc_config` there. However, it felt unnatural deps for `crates/oxc_linter` -> `apps/oxc_cli`, so I decided to keep the Rust code as is for now and share only the JS code.
Since `apps/shared` exports `.ts` files directly, each app is responsible for bundling them via `@oxapps/shared`. Tests still live in `apps/shared`, so separate CI is also added and hooked by each app.
To start, I've only shared the function that reports TS incompatibility errors. This is just for verification purposes and will be fixed in a later PR.
> [!NOTE]
>
> **AI disclosure**: Claude Opus 4.7 authored all code changes. I
reviewed all of them.
This PR implements
[react/no-object-type-as-default-prop](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-object-type-as-default-prop.md).
That was the only missing piece when I migrated from ESLint 😀
Related: #1022
## Difference from `eslint-plugin-react`
As a result of pursuing the best possible implementation, it diverges
from `eslint-plugin-react`'s in the following ways.
If we should strictly match the `eslint-plugin-react` behavior, I'm
happy to revise the implementation.
### 1. Flags JSX Fragment as object type
```jsx
// Flagged by ours, not by eslint-plugin-react
function Foo({ icon = <></> }) { return <div /> }
```
### 2. Flags wrapped components
```jsx
// Flagged by ours, not by eslint-plugi-react
const Foo = React.forwardRef(({ items = [] }, ref) => <div ref={ref} />);
```
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## Summary
- The file walker (`ignore::WalkParallel`) was using its own default thread count (`available_parallelism()`), independent of rayon's pool size. When users pass `--threads=N` to lower rayon's count, the walker still silently spun up a full background pool, adding kernel pressure during the walk phase.
- This brings oxlint in line with oxfmt, which already does the same: `apps/oxfmt/src/cli/walk.rs:416`.
## Measurement
On a 72k-file repo (rolldown) on M3 Max, 8 runs per config:
| Config | Wall | User | Sys |
|---|---|---|---|
| `--threads=14` (default) before | 3.31 s | 2.50 s | 32.0 s |
| `--threads=14` after | 3.21 s | 2.50 s | 29.4 s |
| `--threads=6` before | 1.36 s | 1.88 s | 5.88 s |
| `--threads=6` after | **1.28 s** | 1.84 s | **3.91 s** |
The win is largest when `--threads` is set below `available_parallelism()`: 33% sys-time reduction at `--threads=6`. At default threads it's a small consistency improvement (no behavior change since both pools were already at the same count).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Related to: #4656
## Summary
Implements the `vitest/padding-around-after-all-blocks` linter rule,
which enforces a blank line of padding before `afterAll` blocks in test
files. Ports the rule from
[eslint-plugin-vitest](https://github.com/vitest-dev/eslint-plugin-vitest).
## AI disclosure
I used Claude Code to help me with onboarding, since I am unfamiliar
with the code. It explained to me how certain things like
`run_on_jest_node`, `report_missing_padding_before_jest_block`, and
other things work under the hood. I also used it to help me with
generating additional test cases for this rule.
And it also generated this PR description for me as well, based on the
file changes.
## Description
This rule helps keep test files readable by visually separating
`afterAll` hooks from surrounding code.
**Incorrect:**
```js
const thing = 123;
afterAll(() => {});
```
**Correct:**
```js
const thing = 123;
afterAll(() => {});
```
The rule:
- Category: `style`
- Plugin: `vitest`
- Auto-fixable: yes (inserts the missing blank line)
- Runs on Jest/Vitest nodes via `run_on_jest_node`, filters to `Hook`
kind calls named `afterAll`, and delegates reporting to the shared
`report_missing_padding_before_jest_block` helper.
---------
Co-authored-by: Kapobajza <kapobajza@gmail.com>
Also removed unused functions from `oxc_config`.
As a bonus of sharing the code, the number of syscalls should decrease, resulting in a slight improvement in perf.
Race probe: A protective measure required to correctly locate the nearest config file without a pre-scan.
The `ignore` crate traverses directories in parallel. Because this behavior is somewhat akin to a DFS and is further influenced by factors like OS resource availability, it is unpredictable.
Consequently, when each file attempts to locate its nearest config, we must prevent it from "settling" for a config further up the directory tree and accidentally adopting the wrong one.
Fixes#22250
> I'm benchmarking oxfmt on a big repo with around 50k directories.
> https://github.com/oxc-project/oxc/issues/22225
>
> The changes in this PR improve the times from 3.4s -> 2.8s, so 600ms savings 🎉
> https://github.com/oxc-project/oxc/pull/22258#issuecomment-4406579633
When I tested this on a local repo of about 20k, I didn't see as much improvement as reported, but there was definitely an improvement.
---
> [!WARNING]
> This PR changes the CLI behavior in the following nitch cases.
- When you have nested configs,
- and one of which is invalid,
- and you run it in "write" mode
Previously, the CLI would pre-scan the all config files, so an error would be reported immediately and no files would be formatted at all.
With this PR, while it will still report an error and abort upon finding an invalid config file, it will now proceed to format the files for any parts where a valid config can be applied.
In other words, it is highly unlikely that you will encounter this change.
Closes#22371, follows https://github.com/azat-io/eslint-plugin-perfectionist/pull/732
---
As a baseline, while the result effectively follows the behavior of a perfectionist, there is no fundamental reason why it must be done that way.
Node.js subpaths are more flexible and aren't necessarily "internal", you can also alias external modules.
However, I do think it's consistent in the sense that it treats them the same way as other conventions like `@`.
Add a check that the input passed to raw transfer parser implementations is valid - both in `napi/parser` and in the raw transfer implementation used in Oxlint's `RuleTester`.
Check that `source_start` and `source_len` which are passed from JS side are within bounds of the active region of the `Arena`. This check is not strictly necessary - it's part of the safety contract, and is enforced on JS side - but it's critical as the setup of the `Arena` on Rust side depends on the accuracy of these values. If they were wrong, it could lead to reading/writing out of bounds. The check is cheap, so it seems worthwhile - safety trumps the tiny perf impact here.
The 3rd invariant of these functions is that the bytes in the buffer between `source_start` and `source_start + source_len` represent a valid UTF-8 string. Here a full runtime check isn't appropriate, as it would involve scanning and validating the whole source text string (expensive). But _do_ enable the full check in debug builds (used by raw transfer parser tests, and Oxlint conformance tests).
Oxlint's raw transfer implementation stores the source text at end of the buffer. Prior to this PR `napi/parser` stored source text at the start of the buffer.
Bring `napi/parser` into line with Oxlint, by also storing source text at end of the buffer.
Making all versions of raw transfer that we use align with each other removes complication (all versions now have the same implementation of `deserializeStr`) and also removes some fragile code where it'd be easy to accidentally trigger UB (see the updated comments in `arena/fixed_size/windows.rs` and `arena/alloc_impl.rs`).
Unfortunately, storing source at end of buffer is slightly less efficient than storing it at the start. We'll switch all implementations to storing source at start of buffer when we move to storing all strings (source text and all other strings) together in a single contiguous block. But in meantime, this slight inefficiency is outweighed by the gain in safety.
Fix the init script which initializes submodules for Oxlint JS plugins
conformance tests.
The script was broken by upgrade to PNPM 11 (#22224), which made
installing packages which have install scripts a hard error.
Fix by telling PNPM not to run packages' install scripts, but not to
raise an error about it either.
- part of https://github.com/oxc-project/oxc/issues/22138
This PR implements two separate but related things:
- A new `--debug` option for the `oxlint` CLI that accepts a comma separated list of flags like `--debug foo,bar,baz`. This allows adding more debug functionality in the future without needing to add lots of individual CLI options.
- A `--debug timings` option that enables per-rule timing information to be recorded and output. This enables determining what the slowest rules are in a single run of the linter in the CLI.
### Rule timings
The primary goal of this altered approach to timing linter rules was: **when timings are not enabled, it doesn't cost any performance.**. To achieve that, we've added a `TIMINGS` const generic argument to all of the core `run`, `run_on_jest_node`, `run_once`, etc. functions in the linter. This creates more code overall, because instead of having a single version of each function, now we have two: one where the compiler optimized it with `TIMINGS = false` and one where `TIMINGS = true`. We then switch between these two paths at runtime by passing `--debug timings`.
So, was this successful? In short, yes. There is no significant difference between `oxlint` in `main` and this PR when it comes to performance, unless you enable per-rule timings (see https://github.com/oxc-project/oxc/pull/22282#issuecomment-4421061041). When you enable timing information, there is a 20-30% overhead in performance. This is because we have to record lots of time information and bookkeeping in hash maps to output the final information. This could be optimized down the line possibly, but it's not a primary goal.
This is also kept fairly generic so that in future PRs we can add support for JS plugin timings and `tsgolint` rule timings as well, if those are available.
Overall, I think the increased maintenance burden is worth it. Even looking at the example output below, I can already see ways to improve specific lint rules for improved performance. It will be invaluable for users to be able to record timing information and submit it to us without requiring them to pull up a profiler and submit a flamegraph profile. This doesn't entirely replace a profiler, but it allows us to get useful information without needing to recompile a binary or install additional tools.
### Example output
Example output from `--debug timings` on the vscode repository (using dev mode, not release build, in case this seems really slow):
```
Found 7790 warnings and 19 errors.
Finished in 16.6s on 6911 files with 95 rules using 8 threads.
Rule timings:
Rule Time (ms) Relative Calls Source
------------------------------------------------------- ---------- -------- ------- ------
eslint/no-unused-vars 4119.700 22.5% 6681 native
eslint/no-unreachable 4116.910 22.5% 6900 native
eslint/no-loss-of-precision 1329.986 7.3% 168327 native
oxc/bad-array-method-on-arguments 849.118 4.6% 9749545 native
eslint/no-useless-escape 633.403 3.5% 469909 native
eslint/no-nonoctal-decimal-escape 602.055 3.3% 447866 native
eslint/no-this-before-super 467.429 2.6% 3912 native
typescript/no-unsafe-declaration-merging 429.882 2.4% 20334 native
eslint/no-obj-calls 423.232 2.3% 602268 native
eslint/no-useless-backreference 319.706 1.7% 605544 native
eslint/no-dupe-keys 284.629 1.6% 109038 native
eslint/no-unsafe-optional-chaining 246.120 1.3% 2036313 native
eslint/no-control-regex 216.737 1.2% 605544 native
eslint/no-misleading-character-class 212.035 1.2% 605544 native
eslint/constructor-super 172.978 0.9% 11088 native
eslint/no-caller 171.227 0.9% 946489 native
eslint/no-const-assign 161.911 0.9% 167786 native
unicorn/no-thenable 160.821 0.9% 851478 native
eslint/no-iterator 155.057 0.8% 975576 native
eslint/no-shadow-restricted-names 146.540 0.8% 6900 native
eslint/no-dupe-class-members 135.977 0.7% 4144 native
unicorn/prefer-set-size 135.732 0.7% 946489 native
typescript/no-extra-non-null-assertion 134.364 0.7% 1523512 native
oxc/bad-replace-all-arg 123.456 0.7% 551933 native
eslint/no-eval 117.968 0.6% 774258 native
oxc/only-used-in-recursion 108.291 0.6% 150511 native
unicorn/no-await-in-promise-methods 103.633 0.6% 551933 native
oxc/bad-min-max-func 102.570 0.6% 551933 native
eslint/no-invalid-regexp 101.664 0.6% 602268 native
eslint/no-dupe-else-if 97.380 0.5% 84274 native
unicorn/prefer-string-starts-ends-with 94.667 0.5% 551933 native
unicorn/no-invalid-remove-event-listener 93.116 0.5% 551933 native
oxc/bad-char-at-comparison 91.417 0.5% 551933 native
unicorn/no-single-promise-in-promise-methods 90.413 0.5% 551933 native
eslint/no-extra-boolean-cast 90.250 0.5% 613702 native
oxc/number-arg-out-of-range 87.906 0.5% 551933 native
eslint/no-import-assign 82.042 0.4% 73720 native
unicorn/no-useless-length-check 81.488 0.4% 42367 native
unicorn/no-useless-spread 78.767 0.4% 178577 native
typescript/no-unnecessary-parameter-property-assignment 76.019 0.4% 62949 native
eslint/use-isnan 75.399 0.4% 650300 native
eslint/no-unsafe-finally 70.008 0.4% 93520 native
unicorn/no-invalid-fetch-options 68.221 0.4% 602268 native
oxc/const-comparisons 54.044 0.3% 131693 native
typescript/no-wrapper-object-types 52.855 0.3% 221028 native
eslint/no-constant-binary-expression 48.784 0.3% 131693 native
eslint/no-irregular-whitespace 39.979 0.2% 476825 native
eslint/no-unassigned-vars 34.773 0.2% 167786 native
eslint/no-unused-expressions 34.643 0.2% 329941 native
eslint/no-sparse-arrays 31.352 0.2% 69539 native
typescript/no-this-alias 29.074 0.2% 233601 native
eslint/no-useless-rename 28.954 0.2% 142726 native
eslint/no-constant-condition 25.769 0.1% 103602 native
typescript/triple-slash-reference 22.440 0.1% 6798 native
eslint/no-setter-return 22.301 0.1% 83251 native
eslint/no-cond-assign 22.198 0.1% 172385 native
eslint/no-compare-neg-zero 22.107 0.1% 89326 native
eslint/no-global-assign 21.885 0.1% 6900 native
typescript/prefer-as-const 21.868 0.1% 204458 native
eslint/no-self-assign 18.860 0.1% 68783 native
eslint/require-yield 17.908 0.1% 80796 native
eslint/no-unused-private-class-members 17.573 0.1% 4144 native
eslint/no-func-assign 17.390 0.1% 80796 native
typescript/prefer-namespace-keyword 17.112 0.1% 783 native
typescript/no-misused-new 15.415 0.1% 30084 native
eslint/no-async-promise-executor 15.132 0.1% 50335 native
oxc/missing-throw 14.423 0.1% 50335 native
oxc/erasing-op 13.472 0.1% 89326 native
oxc/bad-comparison-sequence 11.513 0.1% 89326 native
eslint/no-delete-var 9.966 0.1% 43952 native
oxc/bad-object-literal-comparison 9.699 0.1% 89326 native
unicorn/no-new-array 9.666 0.1% 50335 native
eslint/no-duplicate-case 9.498 0.1% 1520 native
oxc/double-comparisons 9.466 0.1% 42367 native
eslint/no-unsafe-negation 9.424 0.1% 89326 native
eslint/no-class-assign 8.982 0.0% 11088 native
unicorn/no-useless-fallback-in-spread 8.865 0.0% 42367 native
oxc/uninvoked-array-callback 8.857 0.0% 50335 native
eslint/no-ex-assign 8.852 0.0% 2729 native
typescript/no-duplicate-enum-values 8.704 0.0% 1507 native
eslint/valid-typeof 8.612 0.0% 43952 native
eslint/no-new-native-nonconstructor 7.647 0.0% 50335 native
eslint/no-empty-character-class 5.828 0.0% 3276 native
typescript/no-useless-empty-export 4.494 0.0% 27137 native
unicorn/no-empty-file 4.235 0.0% 6900 native
unicorn/no-unnecessary-await 3.885 0.0% 31924 native
eslint/no-unused-labels 2.308 0.0% 6900 native
eslint/for-direction 1.938 0.0% 2837 native
eslint/no-useless-catch 1.788 0.0% 3853 native
eslint/getter-return 1.754 0.0% 1019 native
typescript/no-non-null-asserted-optional-chain 1.734 0.0% 7549 native
eslint/no-empty-pattern 1.226 0.0% 5829 native
eslint/no-debugger 0.074 0.0% 1 native
```
this PR implements `eslint/prefer-arrow-callback` rule, passes all
upstream tests (39 pass + 68 fail cases, 51 autofix snapshots).
Highlights:
- Honors `allowNamedFunctions` and `allowUnboundThis` options (defaults
match ESLint).
- Strips `.bind(this)` chains during autofix and re-wraps the arrow in
parens when needed (member access, logical/binary operands).
- Handles optional chaining (`function?.bind(this)`, `foo?.(...)`) and
parenthesized callbacks.
issue #479
**AI Disclosure**: I used AI (Claude) to help with Rust idioms and oxc
internals. All output was reviewed locally; `cargo test -p oxc_linter`,
`cargo clippy -p oxc_linter --no-deps`, and `cargo fmt --check` are
clean.
---------
Co-authored-by: Cam McHenry <camchenry@users.noreply.github.com>
Fixes#22350
Currently, the js-in-vue process involves formatting the code on the Rust side using `oxc_formatter`, converting it into an IR, transforming that into a Prettier `Doc`, serializing it into JSON, and then sending it to the JS side, where it is finally passed to Prettier.
Some types of `Docs` include multiple variations that are selected based on the `printWidth`, and all of these variations were being included as part of the JSON payload.
As a result, deep nesting caused the payload size to grow exponentially, leading to hangs.
This PR resolves the issue by sending these variations separately as shared references.
> This PR enhances Oxlint configuration schema fidelity by expanding `DummyRuleMap` to explicitly list all built-in rules as schema properties (improving editor autocomplete), while adding markdown-renderer filtering to avoid generating massive documentation sections for `rules`.
<img width="632" height="391" alt="grafik" src="https://github.com/user-attachments/assets/732bf81b-b090-429a-8bcd-20f0a6a54aa6" />
we're still having some internal discussion about if this feature should ship.
This PR removes the two flags from the documentation to give us the option to make breaking changes in future.