Pure style change.
Remove `use super::*;` statements from `disambiguate` and explicitly import all types and functions from where they come from. This makes it easier to trace dependencies.
Pure refactor.
Remove `pair!` macro in `compress` and replace it with a function. This produces identical codegen, but is IMO a bit clearer to read because you get type annotations in IDE.
Also reorder a few lines and add line breaks to make the code clearer.
Pure refactor.
`lanes` is a submodule of `pipeline`, and `pipeline/mod.rs` has the same `#![allow]` attributes, which apply to all its submodules. So the ones in `lanes.rs` have no effect. Remove them.
Tiny optimization to macros.
`Cpu::from_ident` finds the CPU feature set named by an identifier (e.g. the `Avx2` in `#[cpu_version(Avx2)]`). It compared the `Ident` with each feature set's name in turn.
Inside a proc macro, comparing a `proc_macro2::Ident` with a `&str` converts the `Ident` to a `String` every time. So that was up to 3 allocations. Convert it to a `String` once, and compare that instead.
This only affects how fast the macros run, not their output.
Define `DynCpu` separately for each architecture, so its variants can be in a different order on each. `Generic` is always first, then the SIMD feature set which the platform has, then the variants which can't exist on the platform:
- x86_64: `Generic`, `Avx2`, `Neon`.
- aarch64: `Generic`, `Neon`, `Avx2`.
- Other platforms: `Generic`, `Avx2`, `Neon`.
`Generic` is always 0, and the platform's SIMD feature set is 1. Uninhabited variants don't exist at runtime, so `DynCpu` is like a `bool`. On aarch64 with Neon enabled at compile time, and on other platforms, only one variant can exist, so `DynCpu` is zero-sized.
Each variant which can't exist on the platform is `#[expect(dead_code)]`, with no `cfg_attr` needed.
This alters codegen slightly in a few places, but those changes are entirely incidental - same instructions but in a different order, and only in code which only runs once per lex.
Pure refactor - no changes to codegen.
Now that `DynCpu` has all its variants on every platform (#28769), and all arms are compiled everywhere (#28772), `cpu_select!` macro does nothing which a plain `match` doesn't.
Replace it with `match to_dyn(cpu) { ... }` everywhere, and remove the macro.
A `match` is just Rust, so there's no macro syntax to learn, and rustfmt and Rust Analyzer handle it fully. Arms which do the same thing can also be combined with `_`, which `cpu_select!` didn't allow:
```rust
// Before
cpu_select!(cpu, {
Avx2(cpu) => bm_any_avx2(bm, nw, cpu),
Neon(_) => bm_any_generic(bm, nw),
Generic(_) => bm_any_generic(bm, nw),
})
// After
match to_dyn(cpu) {
DynCpu::Avx2(cpu) => bm_any_avx2(bm, nw, cpu),
_ => bm_any_generic(bm, nw),
}
```
This uses the `to_dyn` function, not the `Cpu::to_dyn` method. The method is an extra `#[inline(always)]` function around `to_dyn`, which rustc's MIR inliner can't inline into code which is generic over the CPU type. That changes the codegen of some callers. Those codegen changes may be incidental and have no perf impact, but impossible to know for sure without benchmarking, so using `to_dyn` is the conservative choice to avoid any accidental perf regressions.
Remove all `#[cfg(target_arch = ...)]` attributes from the lexer, except in the `cpu` and `simd` modules.
The code for every CPU feature set (AVX2, Neon and generic) is now compiled, type-checked and linted on every platform, including the tests. Rust Analyzer sees all of the code for all platforms "as one". So e.g. AVX2 code can be worked on with full IDE support on an ARM Mac, and `cargo check` / `cargo clippy` on either kind of machine catches errors in the code for the other.
Code for a feature set which the platform doesn't have still can't run, because that feature set's type is uninhabited there (see #28769). The compiler removes it.
The macros change to support this:
- `#[cpu_version]` no longer adds `#[cfg(target_arch = ...)]`. Its `#[target_feature(...)]` becomes `#[cfg_attr(target_arch = ..., target_feature(...))]`, because enabling another platform's target features is an error.
- `#[cpu_multiversion]` generates all 3 entry points (`__avx2`, `__neon` and `__generic`) on every platform, with their target features in a `cfg_attr` in the same way.
- `cpu_select!` compiles all 3 arms on every platform, instead of replacing the bodies of other platforms' arms with `unreachable!()`.
On a platform where a feature set's type is uninhabited, its `#[cpu_multiversion]` entry point starts with `unreachable!()`, before it calls the function body:
```rust
#[inline(never)]
#[cfg_attr(target_arch = "aarch64", target_feature(enable = "neon"))]
#[allow(unreachable_code)]
unsafe fn __neon(src: &[u8], n: usize, cpu: Neon) -> usize {
#[cfg(not(target_arch = "aarch64"))]
unreachable!();
__impl(src, n, cpu)
}
```
Without it, the copies of every function for that feature set (e.g. `__impl::<Neon>` on x86_64) would be part of the call graph, even though they can never run. That changes how rustc's MIR inliner treats other code, which changes the codegen of functions which only have a generic version. rustc removes code after `unreachable!()` early enough that this doesn't happen.
The call to `__impl` stays, so the entry point still uses its parameters. Without it, clippy reports lints on their types which don't apply, e.g. `ptr_arg` warning on a `&mut Vec` parameter.
With these workarounds, this PR makes no difference to generated ASM - it's a pure refactor.
Pure refactor - no change to codegen.
Preparatory step for compiling all of the lexer's code on all platforms (#28772).
`prefetch` was in `pipeline/coalesce`, and was the only code outside `simd` which used `std::arch::x86_64` intrinsics directly. Move it into `simd/prefetch.rs`, as a public free function.
Pure refactor - no change to codegen.
Preparatory step for compiling all of the lexer's code on all platforms (#28772).
Previously, the `simd/avx2` module was only compiled on x86_64, and `simd/neon` only on aarch64. So `Vec128<Avx2>` and `Vec256<Avx2>` only existed on x86_64, and `Vec128<Neon>` and `Vec256<Neon>` only on aarch64. Other platforms had no vector types at all.
Compile both modules on all platforms. On other platforms, a vector wraps a `VecData128` / `VecData256` instead of the platform's SIMD type, which has the same size and alignment:
```rust
type Vec128Inner = cfg_select! {
target_arch = "x86_64" => __m128i,
_ => VecData128,
};
```
There, the CPU type is uninhabited, so the vector can never be created.
Add a `#[cpu_impl]` attribute macro, for each `impl` block of a CPU type's vector methods. It wraps each method's body in `cfg_select!`, so the body is only compiled on that CPU's platform, and is `unreachable!()` elsewhere. Signatures don't change, so the methods exist on every platform, and code which calls them compiles everywhere.
```rust
#[cpu_impl(Avx2)]
impl Vec256<Avx2> {
pub fn movemask(self) -> u32 {
// SAFETY: `Vec256` type guarantees platform supports AVX2
unsafe { _mm256_movemask_epi8(self.inner).cast_unsigned() }
}
}
```
expands to:
```rust
#[cfg_attr(
not(target_arch = "x86_64"),
allow(dead_code, unused_variables, clippy::unused_self, clippy::needless_pass_by_value)
)]
impl Vec256<Avx2> {
pub fn movemask(self) -> u32 {
cfg_select! {
target_arch = "x86_64" => {
// SAFETY: `Vec256<Avx2>` type guarantees platform supports AVX2
unsafe { _mm256_movemask_epi8(self.inner).cast_unsigned() }
}
_ => unreachable!(),
}
}
}
```
The `simd` tests are compiled on all platforms too.
Pure refactor - no change to codegen.
Preparatory step for compiling all of the lexer's code on all platforms (#28772).
Previously, `Avx2` only existed on x86_64, and `Neon` only on aarch64, and the variants of `DynCpu` had `#[cfg]` attributes to match. `Generic` existed on all platforms, but was uninhabited on aarch64 with Neon enabled.
Make `Avx2` and `Neon` work the same way as `Generic`. All these types now exists on every platform. Each has a private field which is `()` on its own platform, and `Never` (an enum with no variants) everywhere else:
```rust
pub struct Avx2(
cfg_select! {
target_arch = "x86_64" => (),
_ => Never,
},
);
```
On platforms which can't support the CPU feature set, the type is uninhabited, so it can never be created, and the compiler removes code which handles it.
`DynCpu` has all 3 variants on every platform. Variants which can't exist on a platform are `#[expect(dead_code)]` there.
The field is private, so only code in the `cpu` module knows that a type is uninhabited. A `match` anywhere else needs an arm for every variant, including ones which can't exist. So `cpu_select!` and `#[cpu_multiversion]` add arms for them, e.g. `DynCpu::Neon(_) => unreachable!()` on x86_64.
Pure refactor - no change to codegen.
Preparatory step for compiling all of the lexer's code on all platforms (#28772).
Previously, the methods of `Vec128` and `Vec256` were inherent methods, defined separately for each CPU type in the `avx2` and `neon` modules. Code which is generic over the CPU type couldn't call them.
Move every method which has both AVX2 and Neon versions into the `Vec128Impl` and `Vec256Impl` traits (introduced in #28766) - 31 methods for `Vec128`, and 36 for `Vec256`. Each CPU type implements the traits in its own module.
`Vec128<C>` and `Vec256<C>` also have generic inherent methods with the same names, which call the trait methods. So callers don't need the traits in scope, and call sites don't change.
The structs, the traits and the generic methods are in new `simd/vec128.rs` and `simd/vec256.rs` files.
The `!`, `&`, `|` and `^` operators, and the `Operand128` / `Operand256` traits, are generic over the CPU type too. The operators move from `bit_traits.rs` into `vec128.rs` and `vec256.rs`. The `CpuType` alias, which picked the platform's CPU type with `#[cfg]`, is removed.
Methods which only one CPU has stay inherent methods of that CPU's types:
- AVX2 only: `movemask` and `Vec256::shuffle_u32s`.
- Neon only: `shl_u8`, `shr_u8`, `pairwise_add_u8` and `test_u8`.
`Vec256::shuffle_u8s` stays separate for each CPU too, because it does different things on each: 2 lookups in 16-entry tables on AVX2, 1 lookup in a 32-entry table on Neon.
Neon's `Vec256` gets the methods it didn't have yet, which work on its 2 `Vec128` halves.
Tests of the common methods move from `avx2/tests.rs` and `neon/tests.rs` into `simd/tests.rs`, and run with the CPU type which the platform has. `avx2/tests.rs` and `neon/tests.rs` keep the tests for methods which only one CPU has, or which behave differently on each.
Introduce `VecData128` and `VecData256` types, in `simd/data.rs`.
They're byte arrays aligned on 16 and 32 bytes, for lookup tables and other data which is read into SIMD vectors.
- `Vec128::read` / `Vec256::read` read one into a vector, with an aligned load.
- `Vec128::write` / `Vec256::write` write a vector into one, with an aligned store.
- `VecData128::read(cpu)` / `VecData256::read(cpu)` are the same as `Vec128::read` / `Vec256::read`, but read better for tables, e.g. `PH_A.read(cpu)`.
Convert the lookup tables which SIMD code reads into vectors: `PH_A`, `PH_B`, `PH_T`, `MERGED_LUTS`, `WORD_LUTS`, `BITS` (Neon), `QCOMPACT` and `BCOMPACT`.
The aligned read and store operations have minimal advantage over their unaligned versions. The realbenefit is ensuring that loads/stores do not touch multiple cache lines, which has a perf cost.
The change to aligned types has a positive impact for `QCOMPACT` and `BCOMPACT`, which were previously under-aligned, so reads could cross cache lines. Now that they're made up of aligned `VecData256` / `VecData128` blocks, every load is guaranteed to be within a single cache line.
`VecData128::read` and `VecData256::read` are generic over the CPU type. To support that, `CpuSimd` gets `Vec128` and `Vec256` associated types, and there are new `Vec128Impl` and `Vec256Impl` traits, which only contain `read` for now.
Both types deref to a byte array, so they can be indexed like an array, except in `const` contexts. `as_bytes_mut` and `as_u32s_mut` are for building tables in a `const` or `static`.
Noticed that `unicorn/prefer-single-call` rule declared both `pending`
and `fix` as its fix status, this PR removes `pending` and also changes
to `fix` to `conditional_fix` since the fix is skipped in 2 cases.
Also small change to `declare_oxc_lint` so that it rejects a second fix
capability
Reading `object_name[..1]` panics when a ref-like identifier starts with a multibyte character, such as `한글Ref`. Use `.chars().next()` to preserve the ASCII name check without slicing through a UTF-8 character. Ports the fix and regression fixture from https://github.com/facebook/react/pull/38025.
Fixes#28278.
Cases ending in `while (true)`, `do ... while (true)`, or `for (;;)` cannot fall through unless a reachable break exits the loop. Oxlint reports these cases even though ESLint accepts them.
Traverse CFG edges within each switch and skip the normal exit from statically true loop conditions. Preserve reachable break paths and unused fallthrough comment diagnostics, with regression coverage for async polling, nested breaks, parenthesized conditions, and unreachable breaks.
Linter tests and lint pass. The broader Oxlint suite fails in type-aware suppression tests: one produces different suppression counts, and another aborts because its expected suppression file is missing.
Fixes#28579
`prefer-spread` now reports `(c ? a : b).apply(null, args)` and `m!.apply(null, args)`, matching ESLint. Check whether the applied expression is a member expression rather than requiring an identifier, while retaining receiver checks for method calls.
Fixes#28728
`oxlint --init` now exits with an error if any supported config filename already exists in the current directory, preserving existing configs and avoiding conflicting ones. Exclusive file creation also prevents overwrites if `.oxlintrc.json` appears after the check.
Fixes#28741
Trim trailing zeros from the fractional part while preserving the exponent suffix verbatim. This avoids false positives on literals such as `4.6566128731E+100` and fixes `1.0E+10` to `1E+10`, matching Unicorn 62.
Fixes#28743
Treat contextual keywords spelled with a Unicode escape (`st\u0061tic`,
`\u0061sync`, `g\u0065t`, …) as property names rather than modifiers.
Such a token is an IdentifierName and cannot match a keyword terminal,
so `class A { st\u0061tic
x }` declares two fields named `static` and `x`. Previously it was
parsed as `static x` and reported "Keywords cannot contain escape
characters".
`try_parse_modifier` and `parse_contextual_modifier` now skip escaped
tokens. Invalid forms such as `class X { st\u0061tic y() {} }` are still
rejected. The error is now reported at the token after the name, as
Babel does, which accounts for the updated parser snapshots.
The new `misc/pass` fixture covers fields named `static`, `async`,
`get`, `set`, and `accessor` that end at a line break. The rejected
forms are already covered by Babel and test262.
Validated with scoped Rust checks and full parser conformance.
---------
Co-authored-by: Cameron <cameron.clark@hey.com>
fixes https://github.com/oxc-project/oxc/issues/28729
Generated by Claude Sonnet 5.5 🤖
The upstream rules uses regex to detect if it is a combining character with the `\p` shorthand:
https://github.com/eslint/eslint/blob/main/lib/rules/utils/unicode/is-combining-character.js#L11
Because there is no similar utility in rust, we currently have a self-managed list of character detection.
This list now get extended to detect more characters for other languages like Arabic, Hebrew, Thai ...
Verified manually that the failing test cases also fails upstream.
Did not verfify if the list is really valid and covers all possible combining characters.
I hope that the AI did no mistakes and included characters which are still valid.
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
With `--import-plugin`, `main` builds the module graph in batches of `threads × 4` entry files and waits for each batch and its dependency closure to finish before starting the next. The waiter is a pool worker that polls with `try_recv` + `yield_now`. In the profiles, the other workers spend much of the run spinning in rayon's work-stealing loop, the spinning grows toward the end of the run, and the run ends with a long tail where only a few cores are busy.
The graph loop now runs on the calling thread (`rayon::in_place_scope`) and blocks on `recv`. A new batch can start while earlier ones are still open. When one open batch imports from another, union-find merges them and they finish together, so at most `3 × group_size − 1` entry files (plus their dependencies) are in flight. Without the import plugin, the loop keeps `rayon::scope` and is unchanged; scope selection and module resolution share one resolver predicate. The language server (`run_source`) and the rule test harness (`run_test_source`) choose the scope the same way, so with the import plugin they also run the graph loop on the calling thread.
The speedup comes mostly from less total CPU work and a shorter tail, not from higher core utilization. samply profiles (4 threads, `-A all --import-plugin`, median of 5 runs):
| Corpus | Wall | CPU | Spent in `steal` / `find_work` / `try_recv` |
|---|---|---|---|
| next.js | 799 → 482 ms | 1,728 → 1,272 ms | 25.3% → 0.1% of samples (438 → 2 ms) |
| VS Code | 1,479 → 1,183 ms | 4,240 → 3,887 ms | 9.6% → 1.3% (402 → 51 ms) |
On `main`, one worker is on-CPU for almost the whole run (next.js: 3,098 samples vs 899–1,493 on the other three workers). With this PR the four workers are even (1,124–1,405), and the top of the inverted call tree is lexer, allocator, and semantic code instead of crossbeam's deque.
<img width="1290" height="358" alt="next.js: main vs this PR" src="https://github.com/user-attachments/assets/ffd00f15-4ad9-46e1-bd60-ca23c0efa696" />
<img width="1290" height="358" alt="VS Code: main vs this PR" src="https://github.com/user-attachments/assets/b917dbee-21ce-4262-b374-93af0df0beb7" />
Thread timelines from Firefox Profiler, `main` above and this PR below, same time scale.
## Results
`-c empty.json --disable-nested-config --silent -A all --import-plugin`. VS Code `src` at `1306698e` (11,260 files) and next.js `packages/` at `3bccd4df` (3,404 files). Time is median (min … max). Peak RSS is the median of `wait4` max RSS. Δ time is the median of paired per-round differences.
| Machine | Threads | Corpus | main | this PR | Δ time | Δ peak RSS |
|---|---|---|---|---|---:|---:|
| Linux x86-64, 8 vCPU | 4 | VS Code | 1,673 ms (1,566 … 2,282) | 1,403 ms (1,231 … 1,754) | −19.7% | +3.8% |
| | 4 | next.js | 744 ms (649 … 949) | 444 ms (396 … 904) | −40.4% | +16.7% |
| | 8 | VS Code | 960 ms (832 … 1,256) | 771 ms (661 … 900) | −18.5% | +5.6% |
| | 8 | next.js | 626 ms (539 … 821) | 426 ms (374 … 509) | −30.1% | +14.1% |
| Apple M4 Max (12P + 4E) | 8 | VS Code | 440 ms (427 … 494) | 359 ms (354 … 404) | −18.3% | +4.4% |
| | 8 | next.js | 263 ms (248 … 282) | 191 ms (183 … 202) | −28.0% | +4.2% |
| | 16 | VS Code | 378 ms (362 … 571) | 328 ms (321 … 530) | −12.6% | +8.9% |
| | 16 | next.js | 238 ms (228 … 306) | 168 ms (164 … 249) | −29.1% | +2.6% |
Linux: 20 rounds per row. M4 Max: 40 rounds (two passes of 20). In every 20-round set, this PR was faster in at least 17 rounds.
CPU time also drops on the 8-thread Linux run: user + sys 1.73 → 1.26 s on next.js and 4.10 → 3.72 s on VS Code.
Without `--import-plugin`, time and RSS are within noise on both machines: paired medians range from −6% to +3%, and this PR was faster in 16–26 of 40 rounds. On the M4 Max, lint output (`-D correctness --import-plugin -f unix`, sorted) is byte-identical to `main`.
## Memory
Peak RSS is 3–17% higher with the import plugin. The largest increase is next.js on Linux; on the M4 Max the same corpus is +3% to +4%. Three things contribute:
- Overlapping batches keep up to `3 × group_size − 1` entry files and their dependencies in flight, compared with one batch on `main`.
- Lint jobs are spawned from the calling thread, so they go to rayon's global FIFO queue behind parses that are already queued. ASTs are linted and freed later.
- The per-file arena allocators keep their capacity and the pool does not shrink, so the higher peak persists until exit. On next.js, `MIMALLOC_PURGE_DELAY=0` reduced the gap from 86 MB to 28 MB in an earlier measurement.
Runs without the import plugin are unaffected. A lint-first scheduler did not reduce peak RSS on 4 cores. A cap on in-flight work is left for a follow-up.
Linking `loaded_modules` stays serial on the graph thread. A parallel iterator started from a non-worker thread would queue behind pending parses and block the loop.
## Tests
A unit test covers merging into a larger dependency group when `group_id` changes: it checks the new root, the accumulated counts and paths, and that the donor is cleared. Other `ModuleGroups` tests cover adding a group, path compression, and refusing to merge a finished group. The `import_plugin_multi_group` CLI fixture (9 files, `-D import/no-cycle`) matches `main`.
<details>
<summary>Reproduce</summary>
```sh
cargo build --release -p oxlint --bin oxlint --no-default-features --features allocator
echo '{}' > empty.json
# Linux: taskset -c 0-3 or 0-7. macOS: --threads 8 or --threads 16.
oxlint -c empty.json --disable-nested-config --silent -A all --import-plugin .
```
Run from VS Code `src` (`1306698e`) or next.js `packages/` (`3bccd4df`). Each build has its own `CARGO_TARGET_DIR`. Binaries run round-robin in rotating order with 2 warmups each. On Linux, rounds where other processes used more than 0.5 core were redone. On macOS, each round waited until other processes used less than 1.5 cores. Profiles: `samply record --rate 4000` pinned to separate cores, 1 warmup and 5 runs per binary.
`main` is `31cbe378d4`. Timings were taken on an earlier revision of this branch (`be6ef780c8`), before the branch was squashed into one commit. Changes made after that revision were not re-timed: tests and refactors, a deduplicated resolver check, the same scope selection for the language server and rule tests, one fewer `Arc` clone per already-queued import, and a simpler debug check.
</details>
<!-- CURSOR_AGENT_PR_BODY_END -->
If the target is `es2021` or lower, `this.#x += k`, `this.#x ??= k`, and
`++this.#x` on a private accessor give the wrong value. They should give
the new value of `#x`, but they give the return value of the setter
instead. Most setters return nothing, so the value is usually
`undefined`.
```js
class Counter {
#n = 1;
get #count() { return this.#n; }
set #count(v) { this.#n = v; }
add(k) { return (this.#count += k); }
}
console.log(new Counter().add(2)); // 3
```
The transformed code prints `undefined`, because the result of the
expression is the setter call:
```js
// before
return _set_count.call(_assertClassBrand(_Counter_brand, this), _get_count.call(_assertClassBrand(_Counter_brand, this)) + k);
// after
return _toSetter(_set_count.bind(_assertClassBrand(_Counter_brand, this)))._ = _get_count.call(_assertClassBrand(_Counter_brand, this)) + k;
```
Plain `=` and postfix `x++` are already correct, and plain `=` already
uses `_toSetter`. This PR uses the same form if the value of the
expression is used. If the expression is a statement, the output does
not change.
I did not use a temp var, because a temp var in a default parameter is
shared between calls, and a recursive setter overwrites it.
The new fixture `oxc/private-accessor-assignment-value` covers instance
and static accessors, all the operators above, an object from a function
call, arrow function bodies, and the recursive case. Its `exec.js` fails
on main with `expected 'from setter' to be 3`.
---
AI assistance was used for this change. I reviewed and tested it.
---------
Co-authored-by: qing deng <dengqing0821@gmail.com>
Each call in an array `concat` chain reads its arguments before the next call evaluates its own. Merging the calls moves those reads later, so a later argument can change an earlier one first:
```js
function f(arr) { return [].concat(arr).concat(arr.pop()); }
```
With `arr = [1, 2, 3]` this returns `[1, 2, 3, 3]`, but main merges it into `[].concat(arr, arr.pop())`, which returns `[1, 2, 3]`.
A chain now merges only when no later call has an argument with side effects after an earlier call has an argument that `concat` may read. Primitives and array literals don't count as reads. This is the array half of #27580; the string half landed in #27098.
When the merge is refused, the innermost call still folds its leading literals into its array: `[1].concat(1, arr).concat(arr.pop())` → `[1, 1].concat(arr).concat(arr.pop())`.
## Size
`echarts.js` grows by 8 B minified and 6 B gzipped; nothing else in `minsize` changes. The chain `[].concat(ecIns._componentsViews).concat(ecIns._chartsViews)` no longer merges, because with the default `propertyReadSideEffects` a getter on `_chartsViews` could change `_componentsViews`. With `propertyReadSideEffects: false` it merges as before.
monitor-oxc: 48 files larger (+960 B raw, +76 B gzip), 8 B per `concat` call that no longer merges.
## Not covered
With `propertyWriteSideEffects: false`, `arr[0] = 9` counts as free of side effects, so `[].concat(arr).concat(arr[0] = 9)` still merges. The string guard has the same gap; it needs a separate fix.
Part of #26242.
Implemented with AI assistance.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_018ogydZW3PPvmx3orpBQot4
Fixes#28600.
`var I; interface I {}` in a nested block merged the interface into the
hoisted `var`, so the type moved out of the block. TypeScript keeps the
interface in the block. #27936 fixed the opposite order, `interface I {}
var I;`.
## Cause
Hoisting a `var` records it in `hoisting_variables` for each
intermediate scope. `check_redeclaration` falls back to that map, and
`declare_symbol_on_scope` then reused the returned symbol, even for a
type declaration.
## Changes
- `declare_symbol_on_scope`: a declaration with no value flags only
reuses a symbol bound in the target scope. Otherwise it creates a new
symbol there.
- `check_redeclaration`: a binding in the scope and a hoisted `var` are
now checked separately. Without this, the block's type binding would
hide the conflict and the first change would lose the error in `{ var X;
interface X {} let X; }`.
- `check_redeclaration`: only the value flags of a hoisted symbol are
compared. The symbol can also carry type flags from a declaration in the
outer scope, and those do not conflict with a declaration in the block.
The second change also fixes a missed error in `{ interface X {} var X;
let X; }`. tsc rejects both forms with TS2481.
The value-flag restriction also fixes a false error on main: `type X =
number; { var X = 0; interface X {} }` was reported as a redeclaration,
but tsc accepts it.
An uninstantiated `namespace` in a block also has no value flags, so it
now gets its own block symbol too. tsc already rejects namespaces in
blocks (TS1235).
## Tests
- `tests/integration/symbols.rs`: symbol separation and reference
resolution, including the switch case, plus the redeclaration errors and
the valid programs where an outer type shares a symbol with the hoisted
`var`.
- `tests/fixtures/oxc/ts/interfaces/hoisted-var-then-interface.ts`: a
new snapshot of the scope placement. No existing semantic snapshots
changed.
---
AI assistance was used for this change. I reviewed and tested it.
---------
Co-authored-by: qing deng <dengqing0821@gmail.com>
When an unnamed parent node and a mangled identifier begin at the same source position, codegen deduplicated the identifier mapping. This retained location data but discarded the original identifier name, leaving consumers unable to restore inferred function names in source-mapped Node stack traces.
```mermaid
flowchart LR
A[AssignmentExpression mapping<br/>unnamed] --> B[Token at source position P<br/>name: none]
C[Identifier mapping<br/>name: longName] --> D{Same source position P?}
D -->|before| E[Drop duplicate<br/>names: empty]
D -->|after| F[Enrich existing token<br/>name: longName]
```
### Changes
- Cache whether the last emitted token has a name, keeping duplicate-position handling cheap in the source-map builder hot path.
- Enrich an unnamed duplicate token when a later identifier mapping supplies the original name.
- Add a unit regression test for the builder behavior.
- Extend the existing Node source-map stack-trace snapshot test with a mangled ES module fixture.
The Node fixture verifies:
```text
function longFunctionName(longName) { ... }
↓ mangled
function e(e) { ... }
```
and asserts that the source map includes both original names while Node restores the inferred inner frame as:
```text
at longName (/project/input.js:6:15)
```
<!-- claude-projects-attribution: {"github_login":"Brooooooklyn"} -->
_Requested by **Brooklyn** · [project thread](https://claude.ai/code/project/chan_018s9RxRdtH424j2EhXdzV3N?thread=cmsg_018s9RxRdtH424j2EhXdzV3NQxbeR8Pt7qaqSQwoq9rEFo)_
<!-- routine:prune-tests:duplicate-lsp-capability-test -->
Before: `capabilities::test::test_dynamic_watchers_intellij` and `test_dynamic_watchers_vscode` in `crates/oxc_language_server/src/capabilities.rs` have been byte-identical since both were added in #11075: the same `ClientCapabilities` with `workspace.did_change_watched_files.dynamic_registration: Some(true)`, and the same `assert!(capabilities.dynamic_watchers)`.
After: the IntelliJ copy is gone. Only lines inside `#[cfg(test)] mod test` change.
### Totals
- Files touched: 1.
- Cases deleted: **1 of 111** collected by `cargo test -p oxc_language_server -- --list` (110 run, 1 ignored); after: 110.
- Cases merged: 0. Net lines: **−17**.
- Runtime saved: 0.09 ms, the removed case's duration.
### Why it is safe
- Mutation M1, `capabilities.rs:41`: `dynamic_registration.is_some_and(...)` → `relative_pattern_support.is_some_and(...)`. Both capability tests fail identically; since the bodies are identical, every mutation affects both the same way.
- Still covered by `test_dynamic_watchers_vscode`. The 4 `tests::test_suite` dynamic-watcher tests also notice M1, but by hanging (see below).
Kept after defense: none.
| suite | before | after | delta |
|---|---|---|---|
| `oxc_language_server` (lib) | 111 (110 pass, 1 ignored) | 110 (109 pass, 1 ignored) | −1 |
0 failures before and after; the same test is ignored in both runs.
### What ran and what did not
- Ran at `d14dbd1e1` and on this branch: `cargo test -p oxc_language_server` (with `--list`, and JSON output with `--report-time`); M1, reverted; `cargo clippy -p oxc_language_server --all-targets -- --deny warnings`; `rustfmt --check --edition 2024` on the file; `git diff --check`. All clean.
- Not run: the rest of the workspace and the coverage/conformance suites.
Open pull requests: none of the open PRs touches `capabilities.rs`. Age: the test dates from 6b68de0da (#11075, 2025-05-18) and was not added for a bug.
To revert: `git revert 4bf9d3fea`.
---
_Generated by [Claude Code](https://claude.ai/code/session_01PxVsmvB9xfZKfVnUonY8kf)_
Two checks in `prefer_const.rs` collected resolved references into temporary vectors before scanning them. Borrow the resolved reference ID slices and map IDs to references in each existing pass, removing both allocations while preserving reference order, predicates, and behavior.
Sync the complete published `@babel/runtime@8.0.8` helper tree so future updates have a recorded upstream baseline.
Import all 95 CommonJS/ESM helper pairs, the MIT license, conditional exports, and `sideEffects: false`. Preserve 26 legacy Babel helper pairs, three TypeScript decorator helper pairs, and existing import paths.
189 of the 191 upstream files are byte-identical. The two `setFunctionName` implementations retain Oxc's existing behavior and match main. Add a hash manifest, a repeatable sync script, CJS/ESM tests in CI, and an async-generator execution regression. No new helper optimizations or correctness patches are included.
Validation: upstream hashes, compatibility preservation, sync idempotence and drift refusal, formatting, lint, syntax, and whitespace checks passed. Runtime tests were not rerun after restoring this tree because automatic approval blocked helper execution; the newer Babel fixture revision pinned by main was not run locally.
AI assistance: Claude.
The leading parens only guard a statement or arrow body start, so
Prettier prints the same object lookup idiom without them elsewhere
(`const get = ({ a: 1, b: 2 } as const)[key];`).
List that sibling and the assignment arrow body in `Why`, and replace
the `export default` example with a measured one.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
`should_add_parens` skipped the parens of an arrow's conditional body
when it starts with an object, function or class, assuming the leading
node prints its own.
Only an object does in an arrow body, so `() => (function () {}).a ? b :
c` printed with no parens at all.
Matches Prettier: skip them only for a leading object.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
- bump oxc-markdown-parser 0.0.4 which skips autolink literal attempts that cannot match
- Compute a text's next words only under `proseWrap: always`, the only mode that reads them
- Print link destinations with nothing to escape as is
About 15% CPU instruction reduction.
The runtime snapshot still records a transformer timeout for async-private-gen-meth-static-ary-ptrn-elem-id-init-fn-name-arrow.js, which now passes. Regenerate the snapshot, increasing the passing count from 17,314 to 17,315 of 18,277.
Verified with cargo coverage runtime, which reproduced the monitor-oxc CI diff exactly, and an isolated run of the affected test.
AI assistance was used to prepare and verify this update.
- Only a spread dot precedes an operand, so a?.<T>(b) opens type
arguments instead of JSX and a./x/ is a division, as in tsc
- Split a << after a dot or a non-null mark into two type lists
- Accept const in a type parameter list nested in type arguments