Commit Graph

811 Commits

Author SHA1 Message Date
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
overlookmotel 43482c7bb1 perf(linter/plugins): use >> not >>> in binary search loops (#21129)
`>>` is cheaper than `>>>` because `>>` produces a 32-bit _signed_ integer which is V8's native number type (SMI). `>>>` produces a 32-bit _unsigned_ integer, which needs to be boxed and stored on the heap.

Source text in raw transfer is limited to 1 GiB, and therefore source offsets, number of lines, number of tokens, and number of comments after all less than `1 << 30`. Therefore even the sum of 2 of them cannot reach `1 << 31` (the maximum positive integer which can be stored as a positive signed 31-bit int. Therefore it's safe to use `>>` in these binary loops.
2026-04-07 17:10:32 +00:00
overlookmotel 990b73ad2f test(napi/parser): fix benchmarks (#21128)
Raw transfer benchmark had got broken. Fix it.
2026-04-07 17:10:32 +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
renovate 71604dda84 chore(deps): update dependency esbuild to ^0.28.0 (#21075)
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/) |
|---|---|---|---|---|---|
| [esbuild](https://redirect.github.com/evanw/esbuild) | [`^0.27.0` → `^0.28.0`](https://renovatebot.com/diffs/npm/esbuild/0.27.7/0.28.0) | ![age](https://developer.mend.io/api/mc/badges/age/npm/esbuild/0.28.0?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/esbuild/0.28.0?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/esbuild/0.27.7/0.28.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/esbuild/0.27.7/0.28.0?slim=true) |

---

### Release Notes

<details>
<summary>evanw/esbuild (esbuild)</summary>

### [`v0.28.0`](https://redirect.github.com/evanw/esbuild/blob/HEAD/CHANGELOG.md#0280)

[Compare Source](https://redirect.github.com/evanw/esbuild/compare/v0.27.7...v0.28.0)

- Add support for `with { type: 'text' }` imports ([#&#8203;4435](https://redirect.github.com/evanw/esbuild/issues/4435))

  The [import text](https://redirect.github.com/tc39/proposal-import-text) proposal has reached stage 3 in the TC39 process, which means that it's recommended for implementation. It has also already been implemented by [Deno](https://docs.deno.com/examples/importing_text/) and [Bun](https://bun.com/docs/guides/runtime/import-html). So with this release, esbuild also adds support for it. This behaves exactly the same as esbuild's existing [`text` loader](https://esbuild.github.io/content-types/#text). Here's an example:

  ```js
  import string from './example.txt' with { type: 'text' }
  console.log(string)
  ```

- Add integrity checks to fallback download path ([#&#8203;4343](https://redirect.github.com/evanw/esbuild/issues/4343))

  Installing esbuild via npm is somewhat complicated with several different edge cases (see [esbuild's documentation](https://esbuild.github.io/getting-started/#additional-npm-flags) for details). If the regular installation of esbuild's platform-specific package fails, esbuild's install script attempts to download the platform-specific package itself (first with the `npm` command, and then with a HTTP request to `registry.npmjs.org` as a last resort).

  This last resort path previously didn't have any integrity checks. With this release, esbuild will now verify that the hash of the downloaded binary matches the expected hash for the current release. This means the hashes for all of esbuild's platform-specific binary packages will now be embedded in the top-level `esbuild` package. Hopefully this should work without any problems. But just in case, this change is being done as a breaking change release.

- Update the Go compiler from 1.25.7 to 1.26.1

  This upgrade should not affect anything. However, there have been some significant internal changes to the Go compiler, so esbuild could potentially behave differently in certain edge cases:

  - It now uses the [new garbage collector](https://go.dev/doc/go1.26#new-garbage-collector) that comes with Go 1.26.
  - The Go compiler is now more aggressive with allocating memory on the stack.
  - The executable format that the Go linker uses has undergone several changes.
  - The WebAssembly build now unconditionally makes use of the sign extension and non-trapping floating-point to integer conversion instructions.

  You can read the [Go 1.26 release notes](https://go.dev/doc/go1.26) for more information.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 10am on monday" in timezone Asia/Shanghai, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Enabled.

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

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- 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:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDIuMTEiLCJ1cGRhdGVkSW5WZXIiOiI0My4xMDIuMTEiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbXX0=-->
2026-04-05 20:55:15 +00: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
overlookmotel c9cd809e50 refactor(str)!: rename Atom to Str (#20835)
Pure refactor.

Rename `Atom` to `Str`. The name "Atom" is a leftover from when we used to intern strings (years ago), and is misleading now.

Just call it `Str`. This follows the naming convention we have for arena-allocated versions of other types e.g. `Box`, `Vec`, `HashMap` - which we name the same as the native equivalents but with a lifetime (`Box<'a>` not `ArenaBox<'a>`, so `Str<'a>` not `ArenaStr<'a>`).

Rename all variables that contained "atom", and methods which relate to `Str`s to match the new type name.

Rename `as_atom` methods to `as_arena_str`. The longer name is required here to disambiguate from `as_str` methods which return a `&str`.

Some variables called "atom" still exist in `oxc_regular_expression` crate - that's a different meaning of "atom" - the meaning defined in regexp spec.

The diff of this PR is large but it's 100% renaming. No substantive or unrelated changes whatsoever.
2026-03-30 02:40:33 +00:00
renovate b4408a3dd3 chore(deps): update npm packages (#20845)
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/) |
|---|---|---|---|---|---|
| [@typescript-eslint/parser](https://typescript-eslint.io/packages/parser) ([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser)) | [`8.57.1` → `8.57.2`](https://renovatebot.com/diffs/npm/@typescript-eslint%2fparser/8.57.1/8.57.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2fparser/8.57.2?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@typescript-eslint%2fparser/8.57.2?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@typescript-eslint%2fparser/8.57.1/8.57.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2fparser/8.57.1/8.57.2?slim=true) |
| [@typescript-eslint/scope-manager](https://typescript-eslint.io/packages/scope-manager) ([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/scope-manager)) | [`8.57.1` → `8.57.2`](https://renovatebot.com/diffs/npm/@typescript-eslint%2fscope-manager/8.57.1/8.57.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2fscope-manager/8.57.2?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@typescript-eslint%2fscope-manager/8.57.2?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@typescript-eslint%2fscope-manager/8.57.1/8.57.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2fscope-manager/8.57.1/8.57.2?slim=true) |
| [@typescript-eslint/visitor-keys](https://typescript-eslint.io) ([source](https://redirect.github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/visitor-keys)) | [`8.57.1` → `8.57.2`](https://renovatebot.com/diffs/npm/@typescript-eslint%2fvisitor-keys/8.57.1/8.57.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@typescript-eslint%2fvisitor-keys/8.57.2?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@typescript-eslint%2fvisitor-keys/8.57.2?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@typescript-eslint%2fvisitor-keys/8.57.1/8.57.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@typescript-eslint%2fvisitor-keys/8.57.1/8.57.2?slim=true) |
| [@vitest/browser](https://vitest.dev/guide/browser/) ([source](https://redirect.github.com/vitest-dev/vitest/tree/HEAD/packages/browser)) | [`4.1.0` → `4.1.2`](https://renovatebot.com/diffs/npm/@vitest%2fbrowser/4.1.0/4.1.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fbrowser/4.1.2?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@vitest%2fbrowser/4.1.2?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@vitest%2fbrowser/4.1.0/4.1.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fbrowser/4.1.0/4.1.2?slim=true) |
| [@vitest/browser-playwright](https://vitest.dev/config/browser/playwright) ([source](https://redirect.github.com/vitest-dev/vitest/tree/HEAD/packages/browser-playwright)) | [`4.1.0` → `4.1.2`](https://renovatebot.com/diffs/npm/@vitest%2fbrowser-playwright/4.1.0/4.1.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/@vitest%2fbrowser-playwright/4.1.2?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@vitest%2fbrowser-playwright/4.1.2?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@vitest%2fbrowser-playwright/4.1.0/4.1.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@vitest%2fbrowser-playwright/4.1.0/4.1.2?slim=true) |
| [diff](https://redirect.github.com/kpdecker/jsdiff) | [`8.0.3` → `8.0.4`](https://renovatebot.com/diffs/npm/diff/8.0.3/8.0.4) | ![age](https://developer.mend.io/api/mc/badges/age/npm/diff/8.0.4?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/diff/8.0.4?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/diff/8.0.3/8.0.4?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/diff/8.0.3/8.0.4?slim=true) |
| [vitest](https://vitest.dev) ([source](https://redirect.github.com/vitest-dev/vitest/tree/HEAD/packages/vitest)) | [`4.1.0` → `4.1.2`](https://renovatebot.com/diffs/npm/vitest/4.1.0/4.1.2) | ![age](https://developer.mend.io/api/mc/badges/age/npm/vitest/4.1.2?slim=true) | ![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/vitest/4.1.2?slim=true) | ![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/vitest/4.1.0/4.1.2?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/vitest/4.1.0/4.1.2?slim=true) |

---

### Release Notes

<details>
<summary>typescript-eslint/typescript-eslint (@&#8203;typescript-eslint/parser)</summary>

### [`v8.57.2`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/parser/CHANGELOG.md#8572-2026-03-23)

[Compare Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.57.1...v8.57.2)

This was a version bump only for parser to align it with other projects, there were no code changes.

See [GitHub Releases](https://redirect.github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.2) for more information.

You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.

</details>

<details>
<summary>typescript-eslint/typescript-eslint (@&#8203;typescript-eslint/scope-manager)</summary>

### [`v8.57.2`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/scope-manager/CHANGELOG.md#8572-2026-03-23)

[Compare Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.57.1...v8.57.2)

This was a version bump only for scope-manager to align it with other projects, there were no code changes.

See [GitHub Releases](https://redirect.github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.2) for more information.

You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.

</details>

<details>
<summary>typescript-eslint/typescript-eslint (@&#8203;typescript-eslint/visitor-keys)</summary>

### [`v8.57.2`](https://redirect.github.com/typescript-eslint/typescript-eslint/blob/HEAD/packages/visitor-keys/CHANGELOG.md#8572-2026-03-23)

[Compare Source](https://redirect.github.com/typescript-eslint/typescript-eslint/compare/v8.57.1...v8.57.2)

This was a version bump only for visitor-keys to align it with other projects, there were no code changes.

See [GitHub Releases](https://redirect.github.com/typescript-eslint/typescript-eslint/releases/tag/v8.57.2) for more information.

You can read about our [versioning strategy](https://typescript-eslint.io/users/versioning) and [releases](https://typescript-eslint.io/users/releases) on our website.

</details>

<details>
<summary>vitest-dev/vitest (@&#8203;vitest/browser)</summary>

### [`v4.1.2`](https://redirect.github.com/vitest-dev/vitest/releases/tag/v4.1.2)

[Compare Source](https://redirect.github.com/vitest-dev/vitest/compare/v4.1.1...v4.1.2)

This release bumps Vitest's `flatted` version and removes version pinning to resolve `flatted`'s CVE related issues ([#&#8203;9975](https://redirect.github.com/vitest-dev/vitest/issues/9975)).

#####    🐞 Bug Fixes

- Don't resolve `setupFiles` from parent directory  -  by [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) in [#&#8203;9960](https://redirect.github.com/vitest-dev/vitest/issues/9960) [<samp>(7aa93)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/7aa937776)
- Ensure sequential mock/unmock resolution  -  by [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) and **Claude Opus 4.6** in [#&#8203;9830](https://redirect.github.com/vitest-dev/vitest/issues/9830) [<samp>(7c065)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/7c06598db)
- **browser**: Take failure screenshot if `toMatchScreenshot` can't capture a stable screenshot  -  by [@&#8203;macarie](https://redirect.github.com/macarie) in [#&#8203;9847](https://redirect.github.com/vitest-dev/vitest/issues/9847) [<samp>(faace)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/faace1fbe)
- **coverage**: Correct `coverageConfigDefaults` values and types  -  by [@&#8203;Arthie](https://redirect.github.com/Arthie) in [#&#8203;9940](https://redirect.github.com/vitest-dev/vitest/issues/9940) [<samp>(b3c99)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/b3c992cb2)
- **pretty-format**: Fix output limit over counting  -  by [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) in [#&#8203;9965](https://redirect.github.com/vitest-dev/vitest/issues/9965) [<samp>(d3b7a)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/d3b7a40fa)
- Disable colors if agent is detected  -  by [@&#8203;sheremet-va](https://redirect.github.com/sheremet-va) and [@&#8203;AriPerkkio](https://redirect.github.com/AriPerkkio) in [#&#8203;9851](https://redirect.github.com/vitest-dev/vitest/issues/9851) [<samp>(6f97b)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/6f97b55dd)

#####     [View changes on GitHub](https://redirect.github.com/vitest-dev/vitest/compare/v4.1.1...v4.1.2)

### [`v4.1.1`](https://redirect.github.com/vitest-dev/vitest/releases/tag/v4.1.1)

[Compare Source](https://redirect.github.com/vitest-dev/vitest/compare/v4.1.0...v4.1.1)

#####    🚀 Features

- **experimental**:
  - Expose `matchesTags` to test if the current filter matches tags  -  by [@&#8203;sheremet-va](https://redirect.github.com/sheremet-va) in [#&#8203;9913](https://redirect.github.com/vitest-dev/vitest/issues/9913) [<samp>(eec53)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/eec53d9f5)
  - Introduce `experimental.vcsProvider`  -  by [@&#8203;sheremet-va](https://redirect.github.com/sheremet-va) in [#&#8203;9928](https://redirect.github.com/vitest-dev/vitest/issues/9928) [<samp>(56115)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/561150036)

#####    🐞 Bug Fixes

- Mark `TestProject.testFilesList` internal properly  -  by [@&#8203;sapphi-red](https://redirect.github.com/sapphi-red) in [#&#8203;9867](https://redirect.github.com/vitest-dev/vitest/issues/9867) [<samp>(54f26)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/54f2660f5)
- Detect fixture that returns without calling `use`  -  by [@&#8203;oilater](https://redirect.github.com/oilater) in [#&#8203;9831](https://redirect.github.com/vitest-dev/vitest/issues/9831) and [#&#8203;9861](https://redirect.github.com/vitest-dev/vitest/issues/9861) [<samp>(633ae)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/633ae2303)
- Drop vite 8.beta support  -  by [@&#8203;AriPerkkio](https://redirect.github.com/AriPerkkio) in [#&#8203;9862](https://redirect.github.com/vitest-dev/vitest/issues/9862) [<samp>(b78f5)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/b78f5389d)
- Type regression in vi.mocked() static class methods  -  by [@&#8203;purepear](https://redirect.github.com/purepear) and [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) in [#&#8203;9857](https://redirect.github.com/vitest-dev/vitest/issues/9857) [<samp>(90926)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/90926641b)
- Properly re-evaluate actual modules of mocked external  -  by [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) in [#&#8203;9898](https://redirect.github.com/vitest-dev/vitest/issues/9898) [<samp>(ae5ec)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/ae5ec03ef)
- Preserve coverage report when html reporter overlaps  -  by [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) in [#&#8203;9889](https://redirect.github.com/vitest-dev/vitest/issues/9889) [<samp>(2d81a)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/2d81ad897)
- Provide `vi.advanceTimers` to the preview provider  -  by [@&#8203;sheremet-va](https://redirect.github.com/sheremet-va) in [#&#8203;9891](https://redirect.github.com/vitest-dev/vitest/issues/9891) [<samp>(1bc3e)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/1bc3e63be)
- Don't leak event listener in playwright provider  -  by [@&#8203;sheremet-va](https://redirect.github.com/sheremet-va) in [#&#8203;9910](https://redirect.github.com/vitest-dev/vitest/issues/9910) [<samp>(d9355)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/d93550ff7)
- Open browser in `--standalone` mode without running tests  -  by [@&#8203;sheremet-va](https://redirect.github.com/sheremet-va) in [#&#8203;9911](https://redirect.github.com/vitest-dev/vitest/issues/9911) [<samp>(e78ad)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/e78adcf97)
- Guard disposable and optional `body`  -  by [@&#8203;sheremet-va](https://redirect.github.com/sheremet-va) in [#&#8203;9912](https://redirect.github.com/vitest-dev/vitest/issues/9912) [<samp>(6fdb2)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/6fdb2ba61)
- Resolve `retry.condition` RegExp serialization issue  -  by [@&#8203;nstepien](https://redirect.github.com/nstepien) and [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) in [#&#8203;9942](https://redirect.github.com/vitest-dev/vitest/issues/9942) [<samp>(7b605)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/7b6054328)
- **collect**:
  - Don't treat extra props on `test` return as tests  -  by [@&#8203;sheremet-va](https://redirect.github.com/sheremet-va) in [#&#8203;9871](https://redirect.github.com/vitest-dev/vitest/issues/9871) [<samp>(141e7)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/141e72aa1)
- **coverage**:
  - Simplify provider types  -  by [@&#8203;AriPerkkio](https://redirect.github.com/AriPerkkio) in [#&#8203;9931](https://redirect.github.com/vitest-dev/vitest/issues/9931) [<samp>(aaf9f)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/aaf9f18ae)
  - Load built-in provider without module runner  -  by [@&#8203;AriPerkkio](https://redirect.github.com/AriPerkkio) in [#&#8203;9939](https://redirect.github.com/vitest-dev/vitest/issues/9939) [<samp>(bf892)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/bf8920817)
- **expect**:
  - Soft assertions continue after .resolves/.rejects promise errors  -  by [@&#8203;mixelburg](https://redirect.github.com/mixelburg), **Maks Pikov**, **Claude Opus 4.6 (1M context)** and [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) in [#&#8203;9843](https://redirect.github.com/vitest-dev/vitest/issues/9843) [<samp>(6d74b)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/6d74b4948)
  - Fix sinon-chai style API  -  by [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) in [#&#8203;9943](https://redirect.github.com/vitest-dev/vitest/issues/9943) [<samp>(0f08d)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/0f08dda2c)
- **pretty-format**:
  - Limit output for large object  -  by [@&#8203;hi-ogawa](https://redirect.github.com/hi-ogawa) and **Claude Opus 4.6 (1M context)** in [#&#8203;9949](https://redirect.github.com/vitest-dev/vitest/issues/9949) [<samp>(0d5f9)</samp>](https://redirect.github.com/vitest-dev/vitest/commit/0d5f9d6ef)

#####     [View changes on GitHub](https://redirect.github.com/vitest-dev/vitest/compare/v4.1.0...v4.1.1)

</details>

<details>
<summary>kpdecker/jsdiff (diff)</summary>

### [`v8.0.4`](https://redirect.github.com/kpdecker/jsdiff/blob/HEAD/release-notes.md#804)

[Compare Source](https://redirect.github.com/kpdecker/jsdiff/compare/v8.0.3...8.0.4)

- [#&#8203;667](https://redirect.github.com/kpdecker/jsdiff/pull/667) - **fix another bug in `diffWords` when used with an `Intl.Segmenter`**. If the text to be diffed included a combining mark after a whitespace character (i.e. roughly speaking, an accented space), `diffWords` would previously crash. Now this case is handled correctly.

</details>

---

### Configuration

📅 **Schedule**: Branch creation - "before 10am on monday" in timezone Asia/Shanghai, 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:eyJjcmVhdGVkSW5WZXIiOiI0My45NC4xIiwidXBkYXRlZEluVmVyIjoiNDMuOTQuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==-->
2026-03-29 20:01:04 +00:00
Boshen 9dd33b080c release(crates): oxc v0.122.0 (#20648) 2026-03-23 17:59:21 +08:00
renovate[bot] 5e0c7e518b chore(deps): update npm packages (#20409) 2026-03-23 14:01:35 +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 e98466632e refactor(ast): re-order layout assertions in memory order (#20583)
Refactor. Generate assertions for type sizes in the order which they are in memory. This makes it easier to see how these types are laid out in memory.
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
overlookmotel 1606bd1feb refactor(apps, napi, tasks): remove noUnusedLocals from tsconfigs (#20155)
Remove `noUnusedLocals` option from `tsconfig.json` files. It's redundant, as Oxlint `no-unused-vars` covers this for us.

It's an annoyance to have `noUnusedLocals` enabled, as in VS Code TypeScript complains about unused vars, even if they're prefixed with `_`.

Removing this option allows simplifying type tests added in #20066. Only reason these types had to be exported was to stop TS complaining that they were unused.
2026-03-09 13:42:12 +00: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
camc314 ee0491e937 fix(apps,napi): explicitly specify libs in tsconfigs (#20071)
Replaces #20064

The `dom` lib was implicitly being injected, this means stuff like `Token` and `Comment` were in the global scope, which was incorrect.

cc @overlookmotel
2026-03-06 14:46:23 +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 05ccf9f21e perf(linter/plugins): transfer tokens via raw transfer (#19893)
Transfer tokens via raw transfer in Oxlint JS plugins.

Deserializer is written by hand. `Token` is not a normal struct, so deserializer cannot be generated.
2026-03-01 21:47:15 +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