Commit Graph

167 Commits

Author SHA1 Message Date
Boshen fb6b1b053f release(crates): oxc v0.125.0 (#21374)
### 💥 BREAKING CHANGES

- 36cdc31 str: [**BREAKING**] Remove identity `FromIn` impl for `Ident`
(#21251) (overlookmotel)
- 382958a span: [**BREAKING**] Remove re-exports of string types from
`oxc_span` crate (#21246) (overlookmotel)
- c4aedfa str: [**BREAKING**] Add `static_ident!` macro (#21245)
(overlookmotel)

### 🚀 Features

- e7e1aea transformer/typescript: Add `optimize_enums` option for
regular enum inlining (#20539) (Dunqing)
- 679f57f transformer/typescript: Implement const enum inlining and
declaration removal (#20508) (Dunqing)
- 6dd061c semantic: Extend `MemberWriteTarget` to cover all property
modification patterns (#21205) (Dunqing)
- f134e24 minifier: Support `property_write_side_effects` option to drop
unused property assignments (#20773) (Dunqing)
- 75663c0 semantic: Add enum member value evaluation for const enum
support (#20602) (Dunqing)
- 3cfe8ed semantic: Add `MemberWriteTarget` flag to `ReferenceFlags`
(#20772) (Dunqing)

### 🐛 Bug Fixes

- af1a586 transformer/class-properties: Use correct property name when
converting parameter properties (#21268) (Amal Jossy)
- b43250a allocator: Move allocation tracking into `Bump` (#21342)
(overlookmotel)
- 36f505f allocator: `StringBuilder` use `Allocator::alloc_layout`
(#21340) (overlookmotel)
- 7a08a6f allocator: Fix allocation counting in
`Allocator::alloc_concat_strs_array` (#21336) (overlookmotel)
- 2338e28 ecmascript: Treat `this` as potentially having side effects
(#21297) (sapphi-red)
- bd8bd39 allocator: Remove unsafe hacks from `from_raw_parts` methods
(#21283) (overlookmotel)
- 8f4c340 allocator: Remove dangerous pointer const to mut cast (#21279)
(overlookmotel)
- aa9259f parser: Add missing error code for optional param diagnostic
(#21258) (camc314)
- 04b3c2f str: Fix unsound casting const pointers to mut pointers
(#21242) (overlookmotel)
- ceadf6c str: Make `Ident::from_raw` an unsafe function (#21241)
(overlookmotel)
- eab13b3 transformer/decorators: Avoid accessor storage name collisions
(#21106) (Dunqing)
- 07e8a30 transformer/react-refresh: Handle parenthesized variable
initializers (#21047) (camc314)

### ⚡ Performance

- c3ca6f6 allocator: `StringBuilder::from_strs_array_in` check for 0
length earlier (#21338) (overlookmotel)
- c2422bb allocator: `Allocator::alloc_concat_strs_array` check for 0
length earlier (#21337) (overlookmotel)
- 04b0fdc allocator: Mark `Allocator::alloc_layout` as
`#[inline(always)]` (#21335) (overlookmotel)
- 17aee9e allocator: Use `offset_from_unsigned` in
`ChunkFooter::as_raw_parts` (#21280) (overlookmotel)
- 61adedd minifier: Fix O(n²) perf on very many var decls (#21062)
(Gunnlaugur Thor Briem)
- addcd02 napi/parser, linter/plugins: Raw transfer deserializer for
`Vec`s use shift instead of multiply where possible (#21142)
(overlookmotel)
- 3068ded napi/parser, linter/plugins: Shift before add when calculating
positions in raw transfer deserializer (#21141) (overlookmotel)
- eb400b8 napi/parser, linter/plugins: Remove `uint32` buffer view
(#21140) (overlookmotel)
- 2675085 napi/parser: Lazy deserialization use only `Int32Array`
(#21139) (overlookmotel)
- 5b35a53 napi/parser: Deserializing tokens use only `int32` array
(#21138) (overlookmotel)
- f163d10 parser: Tokens raw deserialization use `Int32Array` (#21137)
(overlookmotel)
- 7a86613 linter/plugins: Use `Int32Array`s for tokens and comments
buffers (#21136) (overlookmotel)
- 8c51121 napi/parser, linter/plugins: Raw transfer deserialize `Span`
fields as `i32`s (#21135) (overlookmotel)
- bc1bcdd napi/parser, linter/plugins: Inline trivial raw transfer field
deserializers into node object definitions (#21134) (overlookmotel)
- c0278ab napi/parser, linter/plugins: Use `Int32Array` in raw transfer
deserializer (#21132) (overlookmotel)
- 43482c7 linter/plugins: Use `>>` not `>>>` in binary search loops
(#21129) (overlookmotel)

### 📚 Documentation

- f5e1845 allocator: Upgrade headers in doc comments for `Bump` (#21263)
(overlookmotel)
- 2870174 allocator: Upper case `SAFETY` in comments (#21253)
(overlookmotel)
- 01bc269 str: Reformat `Ident` doc comments (#21240) (overlookmotel)
- dd47359 allocator: Add doc comments for panics and errors (#21230)
(overlookmotel)
2026-04-13 11:25:53 +01:00
overlookmotel addcd02d97 perf(napi/parser, linter/plugins): raw transfer deserializer for Vecs use shift instead of multiply where possible (#21142)
Tiny optimization to raw transfer. Use shift instead of multiply where possible when calculating the end position of a `Vec`'s contents.
2026-04-07 23:32:03 +00:00
overlookmotel 3068ded6a1 perf(napi/parser, linter/plugins): shift before add when calculating positions in raw transfer deserializer (#21141)
Small perf optimization to raw transfer. When calculating a `u32` offset, shift first then add (`(pos >> 2) + 4`) instead of adding first (`(pos + 16) >> 2`). If a function contains multiple `pos >> 2` calculations, V8 may be able to combine them into a single calculation. Previously it couldn't prove that `pos` is a multiple of 4, so couldn't make this optimization.
2026-04-07 23:32:03 +00:00
overlookmotel eb400b8bb2 perf(napi/parser, linter/plugins): remove uint32 buffer view (#21140)
Continuation of #21132. All uses of the `Uint32Array` view of the buffer in raw transfer code have been removed in preceding PRs. So we can now remove it entirely.
2026-04-07 23:14:37 +00:00
overlookmotel 26750857d5 perf(napi/parser): lazy deserialization use only Int32Array (#21139)
Continuation of #21132. Use `Int32Array` view of the buffer for all code in lazy deserialization. Lazy deserialization is not currently in use, but keeping it in line with the main raw transfer deserializer allows removing the `Unint32Array` view of the buffer entirely in #21140 without breaking the lazy deserializer tests.
2026-04-07 23:14:36 +00:00
overlookmotel 5b35a53a0b perf(napi/parser): deserializing tokens use only int32 array (#21138)
Small perf optimization. When deserializing tokens in `oxc-parser`, use only the `Int32Array` view of the buffer, instead of both `Int32Array` and `Uint8Array`. Less values to keep in registers, and `int32` doesn't need to be obtained with a property lookup on each call.
2026-04-07 23:14:36 +00:00
overlookmotel f163d10859 perf(parser): tokens raw deserialization use Int32Array (#21137)
Continuation of #21132. Use `Int32Array` view of the buffer for tokens deserialization in `oxc-parser`.
2026-04-07 23:14:36 +00:00
overlookmotel 7a866138d1 perf(linter/plugins): use Int32Arrays for tokens and comments buffers (#21136)
Continuation of #21132. Use `Int32Array` view of the buffer for all operations related to tokens and comments.
2026-04-07 23:08:00 +00:00
overlookmotel 8c51121e4c perf(napi/parser, linter/plugins): raw transfer deserialize Span fields as i32s (#21135)
Continuation of #21132. Get span `start` and `end` from the `Int32Array` view of the buffer, instead of the `Uint32Array`view. This allows V8 to statically see that they are SMIs, and skip type checks.
2026-04-07 17:25:42 +00:00
overlookmotel bc1bcdd95e perf(napi/parser, linter/plugins): inline trivial raw transfer field deserializers into node object definitions (#21134)
Utilize the `#[estree(raw_deser_inline)]` attribute introduced in #21134 to mark all custom deserializers which do not depend on `parent` as inline-able.

This reduces deserializer code size, as well as being more performant in some cases, by avoiding write boundary checks.
2026-04-07 17:25:42 +00:00
overlookmotel c0278abf55 perf(napi/parser, linter/plugins): use Int32Array in raw transfer deserializer (#21132)
Use `int32` (`Int32Array`) instead of `uint32`(`Uint32Array`) for getting data from buffer in raw transfer deserializer. The buffer is 2 GiB in size, so all offsets within the buffer can be represented as a `u31` which can be stored in an `i32` without any loss, and with no risk of being interpreted as negative numbers.

Same as in #21129, the advantage of fetching offsets from an `Int32Array` is that V8 statically knows the value can be stored in an SMI, its native integer type, without any range checks.
2026-04-07 17:25:41 +00:00
overlookmotel c70a8e9853 refactor(napi/parser, linter/plugins): add int32 buffer view (#21131)
Add an `Int32Array` view of the buffer for raw transfer. It will be used in PRs later in this stack.
2026-04-07 17:25:41 +00:00
renovate 152b37a6e8 chore(deps): update napi (#20829)
This PR contains the following updates:

| Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Adoption](https://docs.renovatebot.com/merge-confidence/) | [Passing](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | Type | Update |
|---|---|---|---|---|---|---|---|
| [@napi-rs/cli](https://napi.rs/) ([source](https://redirect.github.com/napi-rs/napi-rs)) | [`3.5.1` → `3.6.0`](https://renovatebot.com/diffs/npm/@napi-rs%2fcli/3.5.1/3.6.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@napi-rs%2fcli/3.6.0?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@napi-rs%2fcli/3.6.0?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@napi-rs%2fcli/3.5.1/3.6.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@napi-rs%2fcli/3.5.1/3.6.0?slim=true) | pnpm.catalog.default | minor |
| [napi](https://redirect.github.com/napi-rs/napi-rs) | `3.8.3` → `3.8.4` | ![age](https://developer.mend.io/api/mc/badges/age/crate/napi/3.8.4?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi/3.8.4?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi/3.8.3/3.8.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi/3.8.3/3.8.4?slim=true) | workspace.dependencies | patch |
| [napi-derive](https://redirect.github.com/napi-rs/napi-rs) | `3.5.2` → `3.5.3` | ![age](https://developer.mend.io/api/mc/badges/age/crate/napi-derive/3.5.3?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/crate/napi-derive/3.5.3?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/crate/napi-derive/3.5.2/3.5.3?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/crate/napi-derive/3.5.2/3.5.3?slim=true) | workspace.dependencies | patch |

---

### Release Notes

<details>
<summary>napi-rs/napi-rs (@&#8203;napi-rs/cli)</summary>

### [`v3.6.0`](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.5.1...@napi-rs/cli@3.6.0)

[Compare Source](https://redirect.github.com/napi-rs/napi-rs/compare/@napi-rs/cli@3.5.1...@napi-rs/cli@3.6.0)

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

♻ **Rebasing**: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://redirect.github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/oxc-project/oxc).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My45NC4xIiwidXBkYXRlZEluVmVyIjoiNDMuMTAyLjciLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->
2026-04-07 10:48:24 +00:00
Boshen 38aea543e4 release(crates): oxc v0.124.0 (#21084)
### 🐛 Bug Fixes

- fc7f60c allocator: Revert changes to
`get_current_chunk_footer_field_offset` (#20964) (overlookmotel)
- 31316c8 semantic: Rebind class expressions before identifier checks
(#20916) (camc314)

### ⚡ Performance

- fb52383 napi/parser, linter/plugins: Clear buffers and source texts
earlier (#21025) (overlookmotel)
- 3b7dec4 napi/parser, linter/plugins: Use `utf8Slice` for decoding
UTF-8 strings (#21022) (overlookmotel)
- 012c924 napi/parser, linter/plugins: Speed up decoding strings in raw
transfer (#21021) (overlookmotel)
- 55e1e9b napi/parser, linter/plugins: Initialize vars as 0 (#21020)
(overlookmotel)
- c25ef02 napi/parser, linter/plugins: Simplify branch condition in
`deserializeStr` (#21019) (overlookmotel)
- 9f494c3 napi/parser, linter/plugins: Raw transfer use
`String.fromCharCode` in string decoding (#21018) (overlookmotel)
- 91cf105 allocator: Increase initial chunk size from 512B to 16KB
(#20968) (overlookmotel)
- cbc0c21 allocator: Add `#[cold]` to to error handling functions
(#20967) (overlookmotel)
- 0503a78 napi/parser, linter/plugins: Faster deserialization of `raw`
fields (#20923) (overlookmotel)
- a24f75e napi/parser: Optimize string deserialization for non-ASCII
sources (#20834) (Joshua Tuddenham)

### 📚 Documentation

- c78a57a syntax: Fix typo (#21044) (camc314)
- f5e228f allocator: Fix typo in comment (#20972) (overlookmotel)
- 7159d51 allocator: Improve doc comment examples for `vec2::Vec`
(#20969) (overlookmotel)
- b1da750 allocator, data_structures: Correct comments (#20966)
(overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-04-07 09:34:33 +09:00
overlookmotel a4ac3ce514 refactor(linter/plugins): import getNodeLoc instead of injecting it (#21026)
Refacfor. In a strange pattern, we were injecting `getNodeLoc` into `deserialize.js` module on every call to `deserializeProgramOnly`. It's a static function, so just import it.
2026-04-03 22:05:22 +00:00
overlookmotel fb52383874 perf(napi/parser, linter/plugins): clear buffers and source texts earlier (#21025)
There's no need to defer clearing vars containing buffers and source text in raw transfer deserializer module until end of linting. It was a left-over from when this module contained code which lazily deserialized comments, but that's now done elsewhere.

Clear these vars as soon as deserialization is complete. Buffer and `sourceText` are still held in vars in other modules until linting completes, but `sourceTextLatin` (introduced in #21021) can be freed immediately.
2026-04-03 22:05:22 +00:00
overlookmotel 3b7dec4dbb perf(napi/parser, linter/plugins): use utf8Slice for decoding UTF-8 strings (#21022)
Optimize raw transfer string decoding.

Replace `TextDecoder("utf8")` with `Buffer.prototype.utf8Slice` to decode Rust UTF-8 strings to JS strings.

Benchmarks show this is an average 5% speed-up in `deserializeStr`, though the reason why is unclear. It skips various checks, and avoids creating a temporary `Uint8Array` (which is required with `TextDecoder.decode`) which trims 60 bytes of the size of the assembly created for `deserializeStr`. But that's not enough to explain the speed-up 🤷. Anyway, take the win.
2026-04-03 22:05:22 +00:00
overlookmotel 012c924a04 perf(napi/parser, linter/plugins): speed up decoding strings in raw transfer (#21021)
Improve perf of deserializing strings in raw transfer. This PR combines several optimizations, which have been tested and benchmarked in https://github.com/overlookmotel/oxc-raw-str-bench. This PR implements the version "latin-slice-onebyte64" from that repo, which is the current winner.

String deserialization is the main bottleneck in raw transfer, so speeding it up will likely make a large impact on deserialization overall.

This work follows on from #20834 which produced a major speed-up in many files by making files which contain some non-ASCII characters take the fast path of slicing `sourceText` more often.

This PR tackles the remainder - speeding up the fallback path where the fast path can't be taken.

## Optimizations

The optimizations in this PR are:

### Latin1

When source is not 100% ASCII, decode source text from buffer as Latin1.

A Latin1-decoded string represents each UTF-8 byte as a single Latin1 character, so it can be indexed into using UTF-8 offsets.

So when we can't slice the string from `sourceText` because the UTF-8 and UTF-16 offsets differ (after any non-ASCII character), loop through the string's bytes and check if they're all ASCII. If they are, the string can be sliced from `sourceTextLatin` instead, with the original UTF-8 offsets.

This is way faster than calling `textDecoder.decode`, as it avoids a call into C++. [Benchmarks show](https://github.com/overlookmotel/oxc-raw-str-bench/blob/4f96275efa9a35d5d27615abb27f21a137149cc0/README.md#apply30-vs-latin-vs-latin-source64) speed up of 55% on average, and up to 70% on some files.

### Latin1 decoding method

It turns out that `new TextDecoder("latin1").decode(arr)` doesn't actually decode to Latin1!

Per the WHATWG Encoding Standard, "latin1" is mapped to "windows-1252".

The result is that with `TextDecoder("latin1")`:

1. `decode` is quite complicated, requiring a 2-pass scan of the bytes to determine if they're all ASCII, followed by a 2nd pass to do the actual `windows-1252` decoding. If the string *does* contain any non-ASCII characters (which it always does in our usecase), NodeJS implements the decoding in JS, not native code. Slow.
2. `decode` produces a 2-byte-per-char string (`TWO_BYTE` in V8), which takes more memory, and is slower for all operations on it e.g. string comparison, hashing for use as an object key etc.

Instead, use `Buffer.prototype.latin1Slice` which:

1. Does a pure Latin1 decode, which is just a single `memcpy` call.
2. Produces a 1-byte-per-char string (`ONE_BYTE` in V8).

`latin1Slice` involves a call into C++, but we only do it once per file, so this cost is tiny in context of deserializing the whole AST.

### Latin1 string slicing

In the fast path, slice from the Latin1-decoded string, instead of `sourceText`. In the fast path, we know that all bytes of source comprising the string are ASCII, so no further checks are required.

This makes no difference on benchmarks for `deserializeStr` itself, but it may have beneficial effects downstream for code (e.g. lint rules) which access strings in the AST, e.g. `Identifier` names.

Because Latin1-decoded source text is `ONE_BYTE`-encoded, slices of it are too. In comparison, slices of `sourceText` may be `ONE_BYTE` or `TWO_BYTE`. If a file's source is pure ASCII, it'll be `ONE_BYTE`, if source contains any non-ASCII characters, it'll be `TWO_BYTE`. Files in a repo will likely be a mix of both, which makes strings returned from `deserializeStr` and placed in the AST a mix too. This in turn makes functions (e.g. lint rule visitors) polymorphic. V8 cannot optimize them as aggressively as if they see only `ONE_BYTE` strings.

We cannot make sure that all strings returned by `deserializeStr` are `ONE_BYTE`. Some string may contain non-ASCII characters, and they *have* to be represented in `TWO_BYTE` form. But we can minimize it - now only strings which *themselves* contain non-ASCII characters are `TWO_BYTE`, whereas before they would be if the source text as a whole contains a single non-ASCII byte.

Code which accesses `Identifier` names, for example will exclusively see `ONE_BYTE` strings and will be more heavily optimized, because Unicode `Identifier`s are rarer than hen's teeth in real-world code.

### Remove string-concatenation loop

Previously strings which are outside of source text were assembled byte-by-byte in a loop via concatenation.

Instead, check that all the bytes are ASCII first, copy them into an array and pass that array to `String.fromCharCode` with `fromCharCode.apply(null, array)`.

To avoid allocating a fresh array every time, hold a stock of arrays for all string lengths that this path can require, and reuse them.

This is a variation on the approach that #20883 took, but without the massive switch. This produces much tighter assembly, and avoids regressing the fast path due making `deserializeStr` a very large function.

Despite the complexity, and multiple operations, [this is up to 3x faster](https://github.com/overlookmotel/oxc-raw-str-bench/blob/4f96275efa9a35d5d27615abb27f21a137149cc0/README.md#apply30-vs-switch30) than the switch approach, and gives an average 30% speed-up.

### Increase native call threshold

The above optimizations make the slow path much faster. This shifts the tipping point at which it's faster to make a native call to `TextDecoder.decode` from 9 bytes to 64 bytes. Most strings now avoid the native call and stay in JS code which is heavily optimized by Turbofan.

The tipping point of 64 is something of a guesstimate. Benchmarking shows its in the right ballpark, but we could finesse it, and probably squeeze out another couple of %.

## Credit

The Latin1 string technique was cooked up by @joshuaisaact in https://github.com/overlookmotel/oxc-raw-str-bench/pull/1. All credit to him for this masterstroke which cracks the whole problem!
2026-04-03 22:05:21 +00:00
overlookmotel 55e1e9b1d2 perf(napi/parser, linter/plugins): initialize vars as 0 (#21020)
Tiny optimization to raw transfer string deserialization. Initialize vars which contain integers as `0`. This may help V8 optimize usage of these variables as they always have SMI type.

The effect, if there is one, is too small to register on benchamarks. But it's certainly not a regression, and may be a tiny gain, so why not?
2026-04-03 22:05:21 +00:00
overlookmotel c25ef02453 perf(napi/parser, linter/plugins): simplify branch condition in deserializeStr (#21019)
Follow-on after #20834.

Simplify the branch condition in `deserializeStr` for detemining if can take the fast path of just slicing `sourceText`. There's no need to check `sourceIsAscii`, just compare the offset to `firstNonAsciiPos` (the position in buffer of first non-ASCII byte in source code). When source is 100% ASCII, `firstNonAsciiPos = sourceEndPos`, so `pos < firstNonAsciiPos` passes for all positions in source.

The implementation is different for parser and for Oxlint, as the source text sits in a different location in buffer - at the start in parser, at the end in Oxlint - but the principle is the same in both.

[Benchmarking](https://github.com/overlookmotel/oxc-raw-str-bench) showed this speeds up `deserializeStr` by a small percentage.
2026-04-03 22:05:20 +00:00
overlookmotel 9f494c3bc8 perf(napi/parser, linter/plugins): raw transfer use String.fromCharCode in string decoding (#21018)
Small optimization to raw transfer string deserialization. Use `String.fromCharCode`instead of `String.fromCodePoint`. [Benchmarking](https://github.com/overlookmotel/oxc-raw-str-bench) showed it's slightly faster, as it's simpler - doesn't need to handle astral code points.
2026-04-03 22:05:20 +00:00
overlookmotel 15546c0261 refactor(napi/parser, linter/plugins): shorten raw transfer deserializers for Options (#20924)
Refactor. Just shorten the code generated for deserializing `Option`s in raw transfer deserializer.
2026-04-01 12:08:08 +00:00
overlookmotel 0503a78b8b perf(napi/parser, linter/plugins): faster deserialization of raw fields (#20923)
`raw` field of `NumericLiteral`, `StringLiteral`, `BigIntLiteral`, `RegExpLiteral`, and `JSXText` are slices of source text.

So in raw transfer deserializer, skip going through `deserializeStr`, which can be slow when source contains any non-ASCII characters. Instead just slice the string from the source text directly with `sourceText.slice(start, end)`.

String decoding is the slowest part of raw transfer, so this should be a significant speed gain.
2026-04-01 12:08:08 +00:00
Joshua Tuddenham a24f75e9e8 perf(napi/parser): optimize string deserialization for non-ASCII sources (#20834)
**AI Disclosure:** Developed with Claude Code (Opus). The winning
approach came out of an automated experiment loop
([auto-claude](https://github.com/joshuaisaact/auto-claude)) — I was
looking for a tight feedback loop to test the tool on and the `TODO:
Find best switch-over point` comment in `deserializeStr` caught my eye.
20 experiments, keep-or-revert on each (all 20 summarized in an
expandable section at the bottom). All code reviewed and understood.
Happy to close this if it's not useful or doesn't meet the bar.

## Why

I was profiling the raw transfer deserialization path (`node --prof` on
`checker.ts`) and noticed `StringAdd_CheckNone` at 13.7% of time — the
single hottest function. It comes from the byte-by-byte `out +=
fromCodePoint(c)` loop in `deserializeStr` when `sourceIsAscii` is
false.

The thing is, `sourceIsAscii` is false for almost everything. All 5 NAPI
bench fixtures are non-ASCII. `checker.ts` has literally one Bengali
character at position 2.1M out of 2.9M. That one character disables the
fast `substr` path for all ~148K strings.

## What

Two changes to the generator
(`tasks/ast_tools/src/generators/raw_transfer.rs`) — that's the only
file with real changes. The 9 generated JS files in the diff are the
mechanical output of `cargo run -p oxc_ast_tools`.

**1. `firstNonAsciiPos` scan at init** — On non-ASCII sources, find the
first non-ASCII byte once upfront. Strings ending before that position
can still use `sourceText.substr()` since byte offsets equal char
offsets in the ASCII prefix. For `checker.ts` this covers 73% of the
file, for `pdf.mjs` 98%.

**2. Lower TextDecoder threshold from 50 to 9** — The existing TODO
asked for the right switch-over point. Experimentally, 9 is the sweet
spot: `TextDecoder` beats the `fromCodePoint` concat loop for strings of
10+ bytes, and the concat loop is still faster for very short strings
where the native call overhead dominates.

Benchmarked on the `complicated()` test set (5 rounds of 30 iters,
dropping round 1 for JIT warmup):

```
              Before    After
checker.ts    26.7ms    13.0ms   -51%
cal.com.tsx   15.4ms     9.1ms   -41%
antd.js       53.8ms    44.7ms   -17%
pdf.mjs        3.8ms     4.3ms   noise
binder.ts      0.7ms     0.5ms   noise
Total        100.5ms    71.6ms   -29%
```

Also verified across 15 files — the 10 non-ASCII files above plus 5
ASCII files (`react.development.js`, `binder.ts`, `moment.js`,
`jquery.js`, `vue.js`). ASCII files are unchanged (our code only touches
the non-ASCII path). -16% across non-ASCII files, no regressions.

## References

- Addresses the `TODO: Find best switch-over point` in
`STR_DESERIALIZER_BODY`
- Related to perf goals in #19918

<details>
<summary>I appreciate this is already a bloated PR description (sorry
@overlookmotel) but given the fairly unusual approach I thought you
might want to see a very short summary of all 20 experiments Claude
clauded through:</summary>

The loop works like this: edit code, benchmark, keep if faster, `git
reset --hard` if not. Metric is total deserialization time across the
benchmark corpus.

**Baseline: 97.7ms** (5-file corpus, all non-ASCII)

| # | Idea | Result | Verdict |
|---|------|--------|---------|
| 1 | Always use TextDecoder, delete the loop entirely | 99.2ms |
Revert. TextDecoder's ~78ns fixed overhead kills short strings. |
| 2 | Lower TextDecoder threshold from 50 to 10 (ts.js only) | 89.8ms |
**Keep.** First real win — moves 50% of strings off the concat loop. |
| 3 | Threshold 5 | 91.3ms | Revert. Too aggressive, too many short
strings go to TextDecoder. |
| 4 | Threshold 8 | 95.0ms | Revert. Worse than 10. |
| 5 | Threshold 12 | 91.2ms | Revert. Worse than 10. |
| 6 | Threshold 10 + unrolled `switch` on len for inline
`String.fromCharCode(uint8[pos], ...)` | 90.9ms | Revert. Switch
dispatch overhead eats the gain. |
| 7 | Threshold 10 + special fast path for len=1 | 94.8ms | Revert.
Extra branch hurts more than the 1-byte optimization helps. |
| 8 | Threshold 10 + accumulate char codes in array, single
`fromCharCode.apply` at end | 90.5ms | Revert. Array allocation
overhead. |
| 9 | Threshold 15 | 93.2ms | Revert. 10 is still the sweet spot. |
| 10 | Unrolled ASCII check for bytes 1-4, TextDecoder for 5+ | 94.7ms |
Revert. Branching overhead. |
| 11 | **Apply threshold 10 to js.js too** (had only been changing
ts.js) | 82.5ms | **Keep.** Facepalm moment — antd.js uses the JS
deserializer. |
| 12 | Threshold 9 in both files | 77.9ms | **Keep.** New best. |
| 13 | Threshold 7 | 79.0ms | Revert. 9 wins. |
| 14 | Threshold 11 | 84.8ms | Revert. 9 confirmed. |
| 15 | Replace `fromCodePoint` with `String.fromCharCode` in the loop |
82.5ms | Revert. V8 optimizes the pre-extracted `fromCodePoint` better.
|
| 16 | Various unrolled `fromCharCode` approaches for short strings | —
| Abandoned, too complex for marginal gain. |
| 17 | Always TextDecoder for non-source strings (remove loop) | 96.1ms
| Revert. Confirms the short-string loop IS valuable for 1-9 bytes. |
| 18 | `Buffer.from().toString()` instead of TextDecoder | 80.4ms |
Revert. TextDecoder is faster. |
| 19 | `firstNonAsciiPos` only (use substr before it, TextDecoder after,
no loop) | 84.9ms | Revert. checker.ts loved it (-34%) but antd.js hated
it (+20%) because its first non-ASCII byte is at 1.3%. |
| 20 | **`firstNonAsciiPos` + threshold 9 + keep the loop** | 76.4ms |
**Keep.** Best of both worlds — substr where possible, TextDecoder for
medium strings, loop for short. |

Three things I (Claude) learned:
- Experiment 11 was the biggest single win and it was just... applying
the change to the other file. Embarrassing.
- Every attempt to replace the `fromCodePoint` loop for short strings
(1-9 bytes) made things worse. The loop is genuinely good for that
range.
- `firstNonAsciiPos` only works when combined with the threshold change.
On its own it hurts files where non-ASCII appears early (antd.js,
cal.com.tsx).

</details>
2026-03-30 16:19:17 +01:00
Boshen 990763453c release(crates): oxc v0.123.0 (#20862) 2026-03-30 18:00:11 +08:00
Boshen 9dd33b080c release(crates): oxc v0.122.0 (#20648) 2026-03-23 17:59:21 +08:00
overlookmotel 9a622c79b4 perf(linter/plugins): lazy deserialize tokens and comments (#20474)
Performance improvement to tokens and comments APIs.

## The problem

Previously, all tokens and comments methods would deserialize *all* tokens/comments into an array of `Token` / `Comment` / `Token | Comment` objects, and then binary search through those arrays to find the token(s) / comment(s) they're looking for.

This has 2 major disadvantages:

1. Files typically contain *a lot* of tokens (even more than the number of AST nodes). Deserializing them all is very costly (up to 30% of total Oxlint runtime when run with only a JS rule which just calls a tokens-related method).

2. The binary searches these methods do are quite expensive. Even in TurboFan-optimized code, accessing `token.start` involves getting pointer to the `Token` object from the `tokens` array, an "is this object a `Token`?" safety check, then reading the `start` field from the `Token` - all just to access a single `u32`, and that happens over and over.

## This PR's solution

Solve both these problems by making tokens and comments methods read `start` / `end` offsets directly from the buffers which contain the tokens/comments data.

This data is tightly packed in memory, and strongly typed (read from `Uint32Array`s), so getting `start` / `end` of a token requires no indirection and no type checks.

More importantly, it removes the need to deserialize all tokens / comments upfront. The desired token(s) are located, touching only the buffer, and then *only* the ones which need to be returned to rule code are deserialized into JS objects.

If a rule accesses `ast.tokens`, `ast.comments`, or `sourceCode.tokensAndComments` then all tokens / comments need to be deserialized, as they're all returned to the rule as an array - but that's unavoidable. This PR doesn't make that any cheaper, but it doesn't make it measurably more costly either.

But where no rule requires the full array of tokens / comments, and they only use token/comment search methods (e.g. `getFirstToken`, `getCommentsBefore`), a great deal of work will be saved. This covers the vast majority of rules.

## Implementation details

The main complication is the `includeComments` option to tokens methods. When `true`, search needs to be over a combined set of both tokens and comments.

When `includeComments: true` option is passed to a tokens method, a buffer is created containing data about all tokens and comments, interleaved in source code order. This buffer can then be used for binary search in tokens methods.

Whether each token / comment has been deserialized already or not is tracked by a "deserialized" flag in the tokens/comments buffers. Each token / comment in the buffer is 16 bytes. This flag lives in byte 15. For tokens, this byte is always already 0 in the buffer when it arrives from Rust side. For comments, we manually set `comment.content = CommentContent::None;` for every comment on Rust side. `comment.content` is positioned at byte 15 in the `Comment` struct, and `CommentContent::None` is stored as 0.

## Possible future improvements

### SoA storage

Binary search operates only on `start` field of tokens / comments, which are 16 bytes apart in the buffer. It would be more efficient if tokens were stored in struct-of-arrays (SoA) style so all `start` values were tightly packed together. This would reduce CPU cache misses in the hot loops of binary searches.

### Pre-compute tokens-and-comments buffer on Rust side

The buffer containing tokens and comments, required to support `includeComments: true`, is currently generated on JS side (but lazily). We could move that to Rust side, which would be faster. However, it might be redundant work in many cases because the buffer is only required if a rule uses `includeComments: true`.

We could alternatively keep the laziness optimization, by calling back into Rust to build the buffer on demand - but JS-Rust calls have a cost too. Maybe communicating via `Atomics` would be faster than an actual function call?

If we had a way to share buffers with WASM, optimal solution might be to generate the buffer lazily (as now) but in WASM, which would be faster for this kind of pure number-crunching, but without the overhead of calling into Rust.
2026-03-21 12:24:11 +00:00
overlookmotel c6ea0a068d perf(ast): place NodeId field after Span in structs (#20584)
Alter the code in `ast_tools` which re-orders struct fields, to make `NodeId` always occupy a consistent "slot" in every AST node - at byte position 8, just after `Span`.

This will be a sizeable gain once we start utilizing the `NodeId` fields more. Because, for example, every variant of `Expression` has its `NodeId` stored in same location as all the other variants, `Expression::node_id()` is just 1 operation - a pointer read - rather than a nest of branches, or a lookup table.

Also alter the algorithm for ordering struct fields to fill in the 4-byte gap after `NodeId` field with other field(s), to avoid excess padding.

The new algorithm also prioritizes keeping fields in definition order as much as possible, rather than sorting them strictly in order of size and alignment. This is mildly advantageous because field definition order is the order the AST is walked in, so it avoids bouncing between cache lines while iterating through the fields of a struct when visiting the AST.

No types change size in the process. Fields remain packed to keep type sizes the minimum they can be - they just change order.
2026-03-20 22:15:56 +00:00
overlookmotel 1be1ebe1ae refactor(ast_tools): search all crates which depend on oxc_ast_macros (#20577)
One annoyance with `ast_tools`, our codegen, has been that when you want it to generate code from types in some file, you had to manually add the file paths to a list in `ast_tools` itself.

This PR removes that list and instead finds crates in the monorepo which depend on `oxc_ast_macros` crate, via `cargo metadata`, and searches all files in those crates for types with `#[ast]` attributes etc - types that it need to act on.

There is no longer a list to keep updated, you just mark types `#[ast]` and the codegen will find them.

This produces some churn in generated files. None of the generated code changes, it just gets re-ordered in some places, due to the different order that files get found in now. This order is deterministic, and it should not change again.
2026-03-20 22:15:54 +00:00
overlookmotel d176eccdbf perf(napi/parser, oxlint/plugins): shorten deserializer for WithClause (#20575)
Small optimization to ESTree serialization and raw transfer. Shave off a little work when serializing/deserializing `WithClause`.
2026-03-20 20:40:34 +00:00
overlookmotel 33993feb98 refactor(estree): simplify ESTree converter for VariableDeclarator (#20574)
Belated follow-on after #15925.

`VariableDeclarator` doesn't need a whole-struct ESTree converter. Switch to a converter just for the `id` field, to reduce custom conversion code.
2026-03-20 20:05:10 +00:00
Boshen 6cd513ffae release(crates): oxc v0.121.0 (#20515)
### 🚀 Features

- 7215d9e transformer: Support lowering `accessor` with legacy
decorators (#20348) (Dunqing)

### 🐛 Bug Fixes

- 3bbd0cd transformer: Emit `Object` instead of `void 0` for untyped
getter/setter `design:type` metadata (#20488) (Dunqing)
- 4ae3f3f ecmascript: Apply coercion-is-pure assumption to constructor
side-effect detection (#20420) (Dunqing)
- 11f9695 transformer: Legacy decorator on computed property key leaves
variable unassigned (#20430) (bab)
- efeba28 ecmascript: Add argument validation for NewExpression
side-effect detection (#20395) (Dunqing)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-03-19 14:31:00 +08:00
Boshen 8741e6532a release(crates): oxc v0.120.0 (#20422)
### 🐛 Bug Fixes

- edb8677 ecmascript: Treat collection constructor with variable arg as side-effectful (#20383) (Dunqing)
- 1f65c3f transformer: Emit design:paramtypes when class has static anonymous class expression (#20382) (bab)
- fa70d5c transformer: Use implementation signature for design:paramtypes when constructor is overloaded (#20394) (bab)
- ed5a7fb parser: Report syntax error for `new super()` (#20384) (Boshen)
- e62524d minifier: Treat object spread of getters as having side effects (#20380) (Boshen)
- f8fbd6e linter/plugins: Remove `hashbang` property from AST (#20365) (overlookmotel)

### ⚡ Performance

- 30a2b0f minifier: Use atom_from_strs_array for template literal concat (#20386) (Boshen)
- 690ce17 minifier: Use Vec::with_capacity for inline template expressions (#20389) (Boshen)
- 9cd612f linter/plugins: Recycle comment objects (#20362) (overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-03-16 10:28:35 +00:00
overlookmotel 9cd612f1e9 perf(linter/plugins): recycle comment objects (#20362)
Apply the same optimization as #19978 to comments - hold a pool of `Comment` objects, and re-use those objects rather than creating new objects each time.

Same as with `Token`s, `loc` property is a getter which calculates `loc` lazily, and caches it in a private property.
2026-03-14 12:19:27 +00:00
Boshen e9b5546ed2 release(crates): oxc v0.119.0 (#20371)
### 🚀 Features

- e7163b6 ecmascript: Add known-globals to side-effect-free property
reads (#20212) (Dunqing)
- 139ab68 ecmascript: Add `property_write_side_effects` to
`MayHaveSideEffectsContext` (#20217) (Dunqing)

### 🐛 Bug Fixes

- 78c264a parser: Fix conditional expressions with arrow-function
alternates in TS (#20356) (camc314)
- 5c97b14 minifier: Recognize object spread of object literals as
side-effect-free (#20299) (Boshen)
- 1ff5c1d transformer/typescript: Rewrite extensions in dynamic
`import()` expressions (#20121) (Sverre Johansen)
- 1c07b3b diagnostics: Handle `WouldBlock` in stdout writes to prevent
panic (#20295) (Boshen)
- ade14d4 ecmascript: Enhance side-effect detection for classes,
TypedArrays, computed members, and spread (#20213) (Dunqing)

### ⚡ Performance

- 5474d0a semantic: V8-style walk-up reference resolution (#20292)
(Boshen)

### 📚 Documentation

- e4aa5b5 parser/napi, linter/plugins: Add JSDoc comments to raw
transfer constants (#20286) (overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-03-14 17:23:27 +08:00
overlookmotel e4aa5b51c1 docs(parser/napi, linter/plugins): add JSDoc comments to raw transfer constants (#20286)
Comments-only change. Add JSDoc comments to the constants related to raw transfer in generated files `constants.js` / `constants.ts`. There are now a lot of constants, and it wasn't clear what they all mean.
2026-03-12 12:46:02 +00:00
Boshen ce4ecb0ffe release(crates): oxc v0.118.0 (#20266)
Co-authored-by: leaysgur <6259812+leaysgur@users.noreply.github.com>
2026-03-12 14:31:12 +09:00
Boshen dafe4ad8cc release(crates): oxc v0.117.0 (#20142)
### 🚀 Features

- e8547cc parser: Report error for using declarations in ambient
contexts (#19934) (camc314)
- 8345318 allocator: Add methods for boxed slices `ArenaBox<[T]>`
(#19968) (overlookmotel)
- f83be30 allocator: Add `Vec::push_fast` method (#19959)
(overlookmotel)

### 🐛 Bug Fixes

- 291d867 transformer_plugins: Unwrap ChainExpression after define
replacement removes optional markers (#20058) (IWANABETHATGUY)
- 36b2e56 codegen: Print type for TSImportEqualsDeclaration (#20128)
(camc314)
- 5a246ec codegen: Print type arguments for JSXOpeningElement (#20127)
(camc314)
- a40870e codegen: Preserve parens for TSNonNullExpression (#20125)
(camc314)
- ae830b2 codegen: Print `declare` for `TSInterfaceDeclaration` (#20124)
(camc314)
- 92cfb14 linter/plugins: Fix types for `walkProgram` and
`walkProgramWithCfg` (#20081) (overlookmotel)
- ee0491e apps,napi: Explicitly specify libs in tsconfigs (#20071)
(camc314)
- 588009e codegen: Print `static` keyword for TSIndexSignature (#19755)
(Dunqing)
- 5a8799c codegen: Print `with_clause` for `ExportNamedDeclaration`
(#20002) (Dunqing)
- 7502afe parser: Correct capacity for tokens `Vec` (#19967)
(overlookmotel)

### ⚡ Performance

- 4ea8f9a napi: Remove `napi_build::setup()` from `oxc_napi` to avoid
redundant rebuilds (#20094) (Boshen)
- 2baa5fb napi: Unify build-test profile to coverage for cache sharing
(#20090) (Boshen)
- 8ba61dd parser: Make pushing tokens faster (#19960) (overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-03-09 10:49:21 +00:00
overlookmotel 92cfb14aa4 fix(linter/plugins): fix types for walkProgram and walkProgramWithCfg (#20081)
Fix a mistake in our (internal) types: `walkProgramWithCfg` was missing that elements can be `CfgVisitFn`.

Also refactor the types for `walkProgram`, importing the original `VisitFn` and `EnterExit` types from where they're defined.
2026-03-06 18:04:24 +00:00
Boshen 3a8c4a4cad release(crates): oxc v0.116.0 (#19911)
### 🚀 Features

- 733d6dc parser: Report error on `infer` outside conditional type
(#19879) (camc314)
- c2a42f6 allocator: Add `Vec::into_bump_slice_mut` (#19895)
(overlookmotel)
- ee4982b parser: Add `VARIANTS` const to `Kind` via `fieldless_enum!`
macro (#19877) (overlookmotel)
- b3dceae data_structures: Add `fieldless_enum!` macro (#19876)
(overlookmotel)
- 12b841e parser: Make all `Kind::is_*` methods `const` (#19874)
(overlookmotel)
- 25c2e25 estree/tokens: Add function to update tokens in place (#19856)
(overlookmotel)
- f78e6df parser: Add `mutate_tokens` Cargo feature (#19853)
(overlookmotel)
- 5036bb6 parser: Report error on `for await` in static blocks (#19844)
(camc314)
- 42bd431 parser: Report error for missing initializer in using decl
(#19824) (camc314)
- a2f58e5 parser: Report error for `implements` clause in non-ts files
(#19820) (Cameron)
- b25228a estree: Add `IS_COMPACT` const to `Formatter` trait (#19787)
(overlookmotel)
- e2a1b79 estree: Expose buffer and formatter of serializers (#19773)
(overlookmotel)
- 4699498 data_structures: Add `CodeBuffer::print_strs_array` (#19760)
(overlookmotel)
- 233f947 estree: `oxc_estree` crate export config and formatter types
(#19724) (overlookmotel)
- 5937a32 semantic: Introduce `symbol_declarations` method (#19609)
(camc314)
- ea6b796 parser: Add `LexerConfig::TOKENS_METHOD_IS_STATIC` const
(#19683) (overlookmotel)
- 655c38f semantic: Add "did you mean?" suggestions to undefined name
errors (#19102) (copilot-swe-agent)
- 9e11dc6 parser,estree,coverage: Collect tokens in parser and convert
to ESTree format (#19497) (camc314)
- c4a3677 parser: Report error for initializer in ambient context
(#19187) (camc314)

### 🐛 Bug Fixes

- abc7e19 codegen: Improve parenthesised checks when printing types
(#19880) (camc314)
- 017de5d parser: Update error code for type annotation in `for...in`
statement (#19882) (camc314)
- 7682e5a linter/plugins: Decode escapes in identifier tokens (#19838)
(overlookmotel)
- 06767ed estree/tokens: Convert `this` tokens in `TSTypeName` (#19815)
(overlookmotel)
- ef798af parser: Use TS8037 for satisfies expression in JS files
diagnostic (#19819) (camc314)
- 98ea5c5 parser: Use TS8016 for type assertions in JS files diagnostic
(#19818) (camc314)
- 1710f56 codegen: Remove double indentation for enum inside namespace
(#19775) (Dunqing)
- 9e4995c codegen: Print type annotation on `CatchParameter` (#19790)
(camc314)
- 297b2bb codegen: Wrap `TSConditionalType` in parens when necessary
(#19788) (camc314)
- cec7878 codegen: Print `definite` property on AccessorProperty
(#19786) (camc314)
- 6f395cf codegen: Print `definite` property on PropertyDefinition
(#19785) (camc314)
- b749373 codegen: Correctly parenthesise TSArrayType (#19784) (camc314)
- 876dc1b codegen: Print object property `this` param (#19783) (camc314)
- 93bb861 formatter: Trim trailing whitespace before breaking line
(#19740) (leaysgur)
- ed17bbf codegen: Print `override` keyword for method and property
definitions (#19753) (Dunqing)
- 6a59a76 parser: Improve error recovery for private identifiers in
property names (#19710) (Boshen)
- 3b96f41 codegen: Print comments in JSX expression containers and
spread attributes (#19701) (Boshen)
- f5694ce estree/tokens: Reverse field order of `regex` object in tokens
(#19679) (overlookmotel)
- b2b7a55 estree/tokens: Generate tokens for files with BOM (#19535)
(overlookmotel)
- 50a7514 estree: Fix tokens for JSX (#19524) (overlookmotel)
- a35063e minifier: Preserve side effects for meta property url reads
(#19668) (Boshen)
- 8ad3430 semantic/jsdoc: Handle even-numbered backtick sequences in
JSDoc parsing (#19664) (Boshen)

### ⚡ Performance

- 05ccf9f linter/plugins: Transfer tokens via raw transfer (#19893)
(overlookmotel)
- c1bfdcf estree/tokens: Preallocate sufficient space for tokens JSON
(#19851) (overlookmotel)
- 4b0611a estree/tokens: Introduce `ESTreeTokenConfig` trait (#19842)
(overlookmotel)
- 81bab90 estree/tokens: Do not JSON-encode keyword, punctuator, etc
tokens (#19814) (overlookmotel)
- 6260ddd estree/tokens: Do not JSON-encode `this` identifiers (#19813)
(overlookmotel)
- b378f4a estree/tokens: Do not JSON-encode JSX identifiers (#19812)
(overlookmotel)
- 5016d92 estree/tokens: Handle regex tokens separately (#19796)
(overlookmotel)
- 780a68e estree/tokens: Use strings from AST for identifier tokens
(#19744) (overlookmotel)
- dc9c2e3 estree: Use `CodeBuffer::print_strs_array` to reduce bounds
checks (#19766) (overlookmotel)
- 845da35 estree: Use `CodeBuffer::print_indent` (#19727)
(overlookmotel)
- ec88f6a estree/tokens: Serialize tokens while visiting AST (#19726)
(overlookmotel)
- bc6507f estree/tokens: Serialize with `ESTree` not `serde` (#19725)
(overlookmotel)
- ec24859 estree/tokens: Do not branch on presence of override twice
(#19721) (overlookmotel)
- dac14be estree/tokens: Replace hash map with `Vec` (#19718)
(overlookmotel)
- b9d2443 estree/tokens: Replace multiple hash sets into a single hash
map (#19716) (overlookmotel)
- 7233548 parser: Remove branches from `finish_next_inner` (#19695)
(overlookmotel)
- b5d9845 parser: Remove const generic param from `finish_next_inner`
(#19684) (overlookmotel)
- 8940f66 estree/tokens: Serialize tokens to compact JSON (#19572)
(overlookmotel)
- 136e39b parser/tokens: Pre-allocate capacity for tokens (#19543)
(overlookmotel)
- 6a6513c linter/plugins: Use Oxc tokens in plugins (#19498) (camc314)
- b3b2d30 parser: Introduce `ParserConfig` (#19637) (overlookmotel)

### 📚 Documentation

- b2b7a64 estree/tokens: Correct comment (#19873) (overlookmotel)
- 0399311 estree/tokens: Improve comments (#19836) (overlookmotel)
- 1b392de minifier: Add `Function.prototype.toString` assumption
(#19758) (sapphi-red)
- 75c9cd8 parser: Improve doc comments for `ParserConfig` and
`LexerConfig` (#19682) (overlookmotel)
- 2fa936f README.md: Map npm package links to npmx.dev (#19666) (Boshen)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-03-02 11:21:50 +00:00
overlookmotel 1f9c115632 test(estree/tokens): add tests for tokens via raw transfer (#19885)
Implement the beginnings of support for tokens in `napi/parser`.

This PR only adds support for tokens via raw transfer, and behind an undocumented `experimentalTokens` option.

Add tests checking that tokens received on JS side via raw transfer match snapshots for all Test262, AcornJSX, and TypeScript test cases. They do!

The tests are the main purpose of this PR, making sure it works before we switch over to tokens via raw transfer in Oxlint.

We can add full tokens support to `napi/parser` (including via JSON transfer) later on.
2026-03-01 20:02:49 +00:00
camc314 6a6513c6ab perf(linter/plugins): use Oxc tokens in plugins (#19498)
Builds on #19497. Use the tokens generated by Oxc's parser in linter plugins, instead of running TS parser to tokenize source.

This is a sizeable perf gain, and also allows us to remove TS parser from `oxlint`'s bundle (#19531).

Additionally, our parser is more accurate  than TypeScript - we handle HTML comments and space before slash in closing JSX elements (`< /div>`) - so this fixes a few conformance tests.
2026-02-24 11:31:04 +00:00
Boshen 7675bc43e7 release(crates): oxc v0.115.0 (#19626)
### 🚀 Features

- e814049 oxc_data_structure/rope: Add `get_offset_from_line_and_column`
(#18133) (Sysix)

### 🐛 Bug Fixes

- 7958b56 linter: Fix syntax error reporting in some output formatters.
(#19590) (connorshea)
- e316694 codegen: Avoid sourcemap panic on `U+2028`/`U+2029` (#19548)
(camc314)
- 933ff72 semantic: Emit correct error code for reserved type name
(#19545) (camc314)

### ⚡ Performance

- b5fa195 codegen: Remove bounds check from `SourcemapBuilder` (#19578)
(overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-02-23 10:07:11 +00:00
Boshen 704c109d6d release(crates): oxc v0.114.0 (#19429)
### 🚀 Features

- 429d876 semantic: Assign ast node ids during semantic build (#19263)
(Boshen)
- ebb80b3 ast: Add `node_id` field to all AST struct nodes (#18138)
(Boshen)

### 🐛 Bug Fixes

- bfb15a3 semantic: Make multi_index_vec clone panic-safe (#19299)
(Boshen)
- 41c50a5 transformer: Ignore invalid JSX pragma identifiers (#19296)
(Boshen)
- deed3d8 transformer: Remove unnecessary trailing expression in object
rest spread assignment (#19259) (Boshen)
- 5bdaacc transformer: Propagate source spans for sourcemap correctness
(#19258) (Boshen)
- 3e0e5ba isolated-declarations: Align readonly class array initializer
diagnostics with tsc (#19218) (camc314)

### ⚡ Performance

- c169c77 syntax: Optimize `is_identifier_name_patched` (#19386)
(sapphi-red)
- aa1e1a8 allocator: Inline BitSet accessors (#19331) (Boshen)
- 5b90d46 semantic: Improve SoA with multi index vec (#19138) (Boshen)
- 99ce2a6 isolated_declarations: Mark all diagnostic functions as
`#[cold]` (#19279) (camc314)
- dd0220f transformer: Remove TS-only nodes earlier in
`enter_statements` (#19166) (Dunqing)
- e5baf60 isolated-declarations: Replace hash collections with
index-based Vec (#19221) (Dunqing)

### 📚 Documentation

- 569aa61 rust: Add missing rustdocs and remove missing_docs lint attrs
(#19306) (Boshen)
2026-02-16 13:03:46 +00:00
Boshen ebb80b32ff feat(ast): add node_id field to all AST struct nodes (#18138)
## Summary

- Add `pub node_id: NodeId` as the first field in all AST structs (~190 structs across js.rs, ts.rs, jsx.rs, literal.rs)
- Update codegen to special-case `node_id`:
  - `Dummy`: Use `NodeId::DUMMY` instead of `Dummy::dummy(allocator)`
  - `CloneIn`: Use `NodeId::DUMMY` for regular clone, preserve ID for semantic clone
  - `ContentEq`: Skip `node_id` from equality comparisons
  - `ESTree`: Skip `node_id` from serialization
  - `AstBuilder`: Exclude `node_id` from builder parameters, use `NodeId::DUMMY`
- Update pattern matching in minifier and transformer to use `..` for new field
- Update traverse script to include `NodeId` import

This is the foundational step for assigning unique IDs to AST nodes. The `node_id` defaults to `NodeId::DUMMY` (value 0) for uninitialized nodes.

## Test plan

- [x] `cargo build` succeeds
- [x] `cargo test` passes
- [x] `just ast` regenerates code correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-02-12 08:06:28 +00:00
Boshen acdb40ed42 release(crates): oxc v0.113.0 (#19210)
### 💥 BREAKING CHANGES

- 2bf7293 mangler: [**BREAKING**] Enable `top_level` by default for
modules and commonjs (#18278) (sapphi-red)
- 48b0542 span: [**BREAKING**] SourceType::ts should set module to
unambigious (#18873) (Boshen)

### 🚀 Features

- 500d071 minifier: Local traverse ctx and generated minifier traverse
(#19106) (Boshen)
- 142a1be parser: Detect binary files with TS1490 error (#19047)
(Boshen)
- e316857 allocator/bitset: Add `Ones` iterator to `BitSet` (#19027)
(sapphi-red)
- 742ad3f minifier: Default `invalid_import_side_effects` to `false`
(#18916) (sapphi-red)
- 0eff6be parser: Error JSX-like type assertions and generics in
`.mts`/`.cts` (#18910) (Boshen)
- 18320c6 span: Store file extension in `SourceType` (#18893) (Boshen)

### 🐛 Bug Fixes

- a7514e4 isolated-declarations: Preserve const context in literal type
inference (#19178) (camc314)
- 312e756 isolated-declarations: Preserve readonly literal initializers
(#19177) (camc314)
- d0ca8d0 isolated-declarations: Skip parenthesis when inferring type
(#19176) (camc314)
- 110c300 oxc_ecmascript: `+[false]` and `+[true]` should evaluate to
`NaN` (#19174) (copilot-swe-agent)
- f32ea19 semantic: Report redeclaration error for import bindings
conflicting with value declarations (#19068) (Boshen)
- 3aeba7a semantic: Report redeclaration error for `function a() {} var
a` in module mode (#19041) (Boshen)
- 35e32c6 coverage: Match Babel's options.json inheritance for test
fixtures (#19002) (Boshen)
- 463d60d semantic: Skip TS2391 for standalone computed-name class
methods (#19025) (Boshen)
- 56c086b parser: Add modifier ordering validation (TS1029) (#19024)
(Boshen)
- 6067a49 linter/jsdoc: False positive in `check-tag-names` for `@` in
email addresses and npm scopes (#19021) (Boshen)
- b13bb70 semantic/jsdoc: Inline tags like `{@link}` break jsdoc parsing
(#19019) (Boshen)
- e3609e3 regular_expression: Preserve UnicodeEscape CharacterKind in
string literals (#18998) (Boshen)
- 57917ee parser: Parse decorators on rest parameters (#18938) (Boshen)
- 487601b napi: Disable mimalloc on Windows to fix worker_threads crash
(#18923) (Boshen)
- 1f6b193 parser: Validate TypeScript import type options (#18889)
(Boshen)
- 1663184 parser: Allow conditional types in function type parameters
(#18886) (Boshen)
- 5758046 parser: Error on property access after instantiation
expression (#18887) (Boshen)
- 5eb4a94 parser: Handle `<<` as two `<` tokens in type argument
contexts (#18885) (Boshen)

### ⚡ Performance

- ed8c054 oxc_str: Add precomputed hash to Ident for fast HashMap
lookups (#19143) (Boshen)
- d4a0867 transformer_plugins: Switch ReplaceGlobalDefines from Traverse
to VisitMut (#19146) (Boshen)
- 9eb16b3 syntax: Pack ASCII identifier tables into single bitflag table
(#19088) (Boshen)
- e7595d1 mangler: Use BitSet for exported symbols set (#19023)
(sapphi-red)
- 2537924 semantic: Optimize scope resolution with fast paths and
inlining (#19029) (Boshen)
- 69a8d85 mangler: Use BitSet for keep_names symbols set (#19028)
(sapphi-red)
- f78c525 parser: Try hybrid parsing for jsx children and closing
element/fragments (#18789) (camchenry)

Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
2026-02-10 09:57:59 +00:00
Boshen 57917eef0f fix(parser): parse decorators on rest parameters (#18938)
## Summary
- Add `decorators` field to `FormalParameterRest` to support decorators on rest parameters in TypeScript
- Parse decorators before checking for rest parameters in formal parameter lists
- Update semantic builder to visit decorators on rest parameters

Fixes parsing of `typescript/tests/cases/compiler/sourceMapValidationDecorators.ts`

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-02-05 09:54:47 +00:00
Boshen 6114a1335e release(crates): oxc v0.112.0 (#18842)
### 💥 BREAKING CHANGES

- 445cb1a oxc_allocator: [**BREAKING**] Remove dangerous `Allocator`
methods (#18715) (overlookmotel)

### 🚀 Features

- 8670b18 parser: Error on ambient class accessor implementations
(#18592) (camc314)

### 🐛 Bug Fixes

- 2e34461 minifier: Prevent expression inlining into block-scoped for-in
declarations (#18651) (copilot-swe-agent)

### 📚 Documentation

- 3d01fa1 transformer: Update links to use Oxc docs (#18722)
(sapphi-red)

Co-authored-by: camc314 <18101008+camc314@users.noreply.github.com>
2026-02-02 09:44:24 +00:00
overlookmotel e39a983ce3 refactor(linter/plugins): store source text in end of buffer (#18714)
Previously we had to store source text at start of buffers sent to JS via raw transfer.

#18376 made changes to how raw transfer deserializer handles strings, in order to support files containing a BOM. Building on that, we're now able to remove the requirement that source text be at start of the buffer entirely.

This PR changes the deserializer used in Oxlint JS plugins to accept source text being anywhere in the buffer, *as long as no other strings are after it*. In practice this just means that the source text must be allocated before anything else, which is easy to satisfy.

Now the source text can be allocated with just the usual safe `allocator.alloc_str(source_text)` method.

This change removes a ton of dodgy workarounds and unsafe code we used previously to get source text at the start of buffer. It makes the code less labyrinthine and far less likely a slip up can inadvertently introduce UB.

Note: In `napi/parser`, source text still *is* at start of the buffer, as that's simpler and more efficient when the source text is written into the buffer on JS side. This change only affects Oxlint.
2026-01-30 00:52:09 +00:00
Boshen 9951f16184 release(crates): oxc v0.111.0 (#18572)
### 💥 BREAKING CHANGES

- 22dec6a semantic: [**BREAKING**] Remove
`Scoping::scope_build_child_ids` and all related APIs (#18362) (Dunqing)
- 30a4899 oxc: [**BREAKING**] Remove
`CompilerInterface::semantic_child_scope_ids` (#18361) (Dunqing)
- 777fc40 ast: [**BREAKING**] Add `Ident` type (#18354) (Boshen)
- af0ca46 span: [**BREAKING**] Use `ModuleKind::CommonJS` for
`SourceType::cjs()` (#18276) (sapphi-red)

### 🚀 Features

- 0a02026 semantic: Add TS1499 code to diagnostic (#18557) (camc314)
- 8b4618f parser: Add TS1500 code to diagnostic (#18547) (camc314)
- 866b6b3 parser: Add TS1048 code to diagnostic (#18546) (camc314)
- 1117c44 parser: Add TS1054 code to diagnostic (#18541) (camc314)
- e4fcdde semantic: Add TS1053 code to diagnostic (#18539) (camc314)
- bcbf396 semantic: Add TS1052 code to diagnostic (#18538) (camc314)
- 8155edf semantic: Add TS1049 code to diagnostic (#18535) (camc314)
- 51d3b3f parser: Add TS1502 code to diagnostic (#18534) (camc314)
- 00854e8 semantic: Add TS2337 error code to super call diagnostic
(#18531) (camc314)
- 993fd2b parser: Parse unambiguous await with better error messages
(#18480) (Boshen)
- 8db0e78 linter/plugins: Handle BOMs (#18376) (overlookmotel)
- 6ac09e2 linter/plugins: Support source text not being at start of
buffer (#18375) (overlookmotel)
- 2ef5647 ast: Add escape_raw parameter to template_element builders
(#18121) (Boshen)

### 🐛 Bug Fixes

- 74d0998 semantic: Update error msg for multiple `default` cases in
switch stmt (#18526) (camc314)
- c205b0d ast: Remove `ThisExpression` from `TSModuleReference` (#18489)
(Boshen)
- aed3669 parser: Parse HTML-like comments in unambiguous mode (#18442)
(Boshen)
- c4132fb parser: Validate accessor parameters in interface method
signatures (#18391) (Boshen)
- b0cd74d semantic: Allow `var` and `function` with same name in static
blocks (#18358) (Boshen)
- 6037995 semantic: Allow `new.target` in class field initializers
(#18349) (Boshen)
- 9a15c6a semantic: Do not rely on spans for node comparison in
`Function::bind` (#18296) (overlookmotel)

### ⚡ Performance

- 6b600c4 semantic: Skip parent lookup for function declarations in
`Function::bind` (#18293) (overlookmotel)
- c27ad2d semantic: Move check for function declaration out of
`is_function_part_of_if_statement` (#18292) (overlookmotel)
- 63eb89e semantic: Skip checking redeclarations for function
expressions (#18291) (overlookmotel)
- 7c12743 semantic: Skip checking unresolved exports in CommonJS files
(#18250) (overlookmotel)
- 2349031 allocator: Increase initial chunk size from 512B to 16KB
(#18234) (Boshen)

### 📚 Documentation

- 8ccd853 npm: Update package homepage URLs and add keywords (#18509)
(Boshen)
- 9b3165f napi/parser: Clarify when to use `parseAsync` vs `parseSync`
(#18486) (Boshen)
- 1b59f63 napi/parser: Correct typo in README (#18251) (overlookmotel)
- 00ff75f mangler: Fix `top_level` option in example (#18233)
(overlookmotel)
- 2ddc073 semantic: Fix typo in comment (#18238) (overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-01-26 10:49:55 +00:00