Allow escaped value names such as `import { t\u0079pe as x } from "m"` and the equivalent named exports. Reject escapes only after resolving `type` as a type-only modifier.
Preserve the decoded identifier name in the ambiguous `type as as` form so its AST name remains `type`.
`class C { async x; }` was accepted in JavaScript and TypeScript, silently discarding `async` from the resulting field. Reject the modifier after parsing a class field, once fields have been distinguished from methods.
Async methods, fields named `async`, and newline-separated fields remain valid.
Return early for calls whose last argument is not `undefined`, avoiding unnecessary callee and `bind` checks. Collect trailing undefined spans with pushes followed by one reversal instead of repeated front insertions, preserving diagnostic and fix order.
Require a comma after a TypeScript `this` parameter when the parameter list continues. Previously, malformed syntax such as `function f(this: T x: T) {}` parsed without a diagnostic.
Pure refactor.
Add `bm_clear` function that clears a bit in a bitmap at specified index. This is the converse of `bm_set`, and single-bit version of `bm_clear_range`.
This operation is performed in various places across `classify`, `carve`, and `coalesce` stages. Replace the duplicated code with `bm_clear` calls. This makes the logic easier to read.
`bm_clear` is `#[inline(always)]` so this change makes zero difference to codegen. The changes in `carve_jsx` look more substantive, but compiling to ASM and diffing it before/after this PR shows 0 difference. The compiler is able to identify the repeated terms across the 4 calls - there's no need to perform that optimization manually.
Code to read the nth bit of a bitmap was duplicated in several places across pipeline code:
- `wordbit` in `carve`
- `bit_at` in `regex_div`
- Various places inline in `carve`, `classify`, and `coalesce`
Add a function `bm_get` for this purpose to `bitmap` module (which feels like its natural home) and replace all usages with calls to this function.
This reduces repetition, and makes the call sites easier to read.
`bm_get` is marked `#[inline(always)]`, so extracting it into a function should have no effect on perf.
Pure refactor. No code changes, just moving code around.
Re-order the functions in `bitmap` module, moving `bm_next0` up to the top with the other getters.
Add line breaks between all the functions.
Pure refactor. No code changes, just moving code around.
After #26443, main exported logic for `compress` stage is in `compress/mod.rs`.
`write_sentinels` is also exported from the module, so move it out of `common.rs` and into `mod.rs`, alongside `compress`. `common.rs` not only contains functions internal to the `compress` stage, shared between AVX2 and generic implementations.
Pure refactor. No code changes, just moving code around.
`compress` stage of pipeline involves several separate steps. Previously this compress-specific logic was in `lex_raw`, mixed with the logic for other stages.
This PR moves it into the `compress` module, so all that remains in `lex_raw` is a single call to `compress`.
The function that was previously called `compress` is renamed to `compress_blocks` to avoid duplicated names.
Pure refactor. No code changes, just moving code around.
Split `compress` pipeline stage into multiple files.
- `avx2.rs` / `generic.rs` - SIMD and generic impls.
- `common.rs` - Code shared between both impls.
- `mod.rs` - Minimal entry point, chooses which impl to load dependent on target.
Additionally, apply the same kinds of stylistic changes as #26390.
Pure refactor. No code changes, just moving code around.
Move all code for `coalesce` pipeline stage into a directory together.
`coalesce/mod.rs` contains same code as `coalesce.rs` did previously. `keyword.rs` moves into the same directory, since `kw_verify_batch` is only used in `coalesce` stage. `kw_flush` moves into `keyword.rs` as it's keyword-related.
Additionally, apply the same kinds of stylistic changes as #26390.
Pure refactor.
`carve` has 2 different implementations for JS and JSX. Move selecting which to use to within the `carve` module, to make `lex_raw` clearer to read.
`carve` function is marked `#[inline]` so this should make no difference whatsoever to codegen.
Pure refactor. No code changes, just moving code around.
Split `carve` pipeline stage into multiple files.
Unlike #26390, here there's little platform-specific code to split up. Aim is just to separate out code which is specific to JS / JSX, and split JSX code up into separate sub-systems.
Additionally, apply the same kinds of stylistic changes as #26390.
Pure refactor. No code changes, just moving code around.
Split `classify` pipeline stage into multiple files.
- `avx2.rs` / `generic.rs` - SIMD and generic impls.
- `common.rs` - Code shared between both impls.
- `mod.rs` - Minimal entry point, chooses which impl to load dependent on target.
Additionally, a few stylistic changes in Oxc "house style":
- Re-order code so it reads top-to-bottom - imports and consts first, then entry points, private fns/helpers lower down.
- Combine imports into a single `use` statements.
- Line breaks between functions.
The split of SIMD/generic into separate files is the main point. This is a precursor to switching over to runtime feature detection, instead of compile-time.
Allow inversion of binary operation when comparing `'u'` string with
`typeof
`!(typeof a < "u")` => `typeof a > "u"`
`!(typeof a > "u")` => `typeof a < "u"`
this is needed to support negation of short version of `typeof x ===
"undefined"` -> `typeof a > "u"` generated by
`substitute_typeof_undefined`
fixes#25294
related #26460
Keep the precedence and source-map availability checks in an inline wrapper, and move the remaining postfix mapping work into an `#[inline(never)]` helper. This keeps the call sites unchanged while separating the mapping work from the inline path.
Skip the scope-ancestor walk in `is_in_declare_global` when the file contains no `TSGlobalDeclaration` nodes. Files containing global declarations retain the existing scope checks, using `nodes().kind()` directly when inspecting ambient module scopes.
Check configured globals once per unresolved name instead of once per reference. Names with only type references skip the global lookup, while undefined value references retain their individual `typeof` and function-scope `arguments` checks.
Skip the rule when neither paths nor patterns are configured. For side-effect import detection, build a set of import statement spans once instead of scanning every import entry for each request, and return early when the file has no requested modules.
Check component parameter validity before scanning for hooks or JSX. Invalid parameter lists now short-circuit the body scan without changing component classification.
Skip exported symbols before collecting their operations and assigning liveness bitset indices. These symbols were already excluded from diagnostics, so tracking them added reference scans and dataflow work without producing reports. Local symbols referenced inside exported declarations and assignments continue to be analyzed independently.
Compare configured function paths directly through their split iterators instead of collecting a temporary vector for each candidate call. The shared matcher accepts iterators while preserving exact segment and length matching; its other caller, `no-array-method-this-argument`, is adapted to the new signature.
Check the terminal callee name's capitalization before constructing its full property path, so ordinary calls and constructors return early. Borrow terminal names directly from the AST to avoid copying identifiers and computed property names; full paths are still constructed when exception matching requires them.
Format non-negative integers below 1000 directly with `itoa`, avoiding Dragonbox formatting and the intermediate `InlineString` copy. Larger numbers and fractions retain the existing shortest-literal logic.
Use `HashMap::entry` to reuse the lookup when inserting a new source-map name, avoiding the second hash and table search from `get` followed by `insert`. Existing name IDs and insertion order are preserved.
Preserve parentheses around TypeScript instantiation expressions in postfix contexts. Previously, `(f<T>).x` and `(f<T>)[x]` were emitted as `f<T>.x` and `f<T>[x]`, producing invalid syntax or changing how the expression is parsed.
The printer now respects the surrounding precedence while retaining grouping for lower-precedence operands.
Class static blocks inherit the disabled `in` context of a surrounding `for` initializer, causing valid code such as `for (class { static { a in b; } };;);` to report a missing semicolon at `in`. Enable `Context::In` while parsing the static block, with the existing context helper restoring the surrounding flags afterward.
## Problem
Named tuple rest elements bypass the parser's existing checks for optional elements after an array rest and for multiple array rests. For example, both of these declarations currently parse successfully even though TypeScript rejects them:
```ts
type OptionalAfterRest = [...a: string[], b?: number];
type RestAfterRest = [...a: string[], ...b: number[]];
```
The equivalent unnamed forms already produce TS1266 and TS1265, respectively. Mixed named and unnamed array rests have the same gap when either rest is named.
## Cause and change
`parse_tuple_type` inspects `TSRestType.type_annotation` to decide whether an element is an array rest. For a named element, that annotation is a `TSNamedTupleMember`, and the actual type is stored in its `element_type`. Matching directly on the outer annotation therefore skips the array check and fails to track the rest element.
Unwrap the named member with `as_ts_type()` before applying the existing classification. Named and unnamed elements then share the same validation and existing diagnostics:
- An optional element after an array rest reports TS1266.
- A second array rest reports TS1265, including mixed named and unnamed forms.
The diagnostic spans continue to use the complete tuple elements, including their labels. The change borrows the underlying type without changing the AST representation or introducing allocations.
## Preserved behavior
A rest marker alone does not make a spread an unbounded array. Fixed-tuple and generic variadic spreads continue to be accepted, including:
```ts
type FixedBeforeOptional = [...a: [string], b?: number];
type RestBeforeFixed = [...a: string[], ...b: [number]];
type VariadicBeforeOptional<T extends unknown[]> = [...a: T, b?: number];
type RestBeforeVariadic<T extends unknown[]> = [...a: string[], ...b: T];
type MultipleVariadic<T extends unknown[], U extends unknown[]> = [...a: T, ...b: U];
```
This change preserves the existing type-reference classification; it does not add type resolution or change how shadowed `Array` references are handled.
## Problem
A `yield` expression in a classic `for` loop initializer can lose the parentheses required around an `in` operator in its argument. This turns valid JavaScript into output that cannot be parsed, in both default and minified code generation.
For example, this input is valid:
```js
function *g(o) {
for (yield (1 in o); false;);
}
```
Previously, codegen emitted:
```js
// Default output
function* g(o) {
for (yield 1 in o; false;);
}
// Minified output
function*g(o){for(yield 1 in o;false;);}
```
The unparenthesized `in` is not allowed at this position in a classic `for` initializer. The same restriction applies to the argument of `yield*`, and can also be lost through nested yields or an assignment inside the yield argument.
## Change
`ForStatement` already prints its initializer with `Context::FORBID_IN`. However, `YieldExpression::gen_expr` ignored its incoming context and always printed its argument with `Context::empty()`.
Preserve `FORBID_IN` when printing the argument of an unwrapped yield expression. This lets the existing expression printers retain the required grouping:
```js
// Default output
function* g(o) {
for (yield (1 in o); false;);
}
// Minified output
function*g(o){for(yield(1 in o);false;);}
```
Only the relevant `FORBID_IN` flag is forwarded; the argument's existing precedence remains `Precedence::Yield`. Both `yield` and `yield*` use this path.
## Parentheses around yield
When precedence requires parentheses around the entire yield expression, those parentheses already allow `in` in the argument. Clear the restriction in that case to avoid redundant inner grouping:
```js
// Input
function *g(o) {
for ((yield (1 in o)) + 1; false;);
}
// Default output
function* g(o) {
for ((yield 1 in o) + 1; false;);
}
// Minified output
function*g(o){for((yield 1 in o)+1;false;);}
```
Outside a restricted initializer, ordinary yield expressions continue to omit unnecessary parentheses: `yield (1 in o)` prints as `yield 1 in o`.
Private-in expressions can lose required parentheses around their right-hand operand in both default and minified code generation. When the operand is another relational expression, removing the grouping changes which value the private-field check receives and can change observable behavior.
For example:
```js
class D {
#x;
test(a, b) {
return #x in (a instanceof b);
}
}
new D().test({}, Object);
```
The original throws a `TypeError`: `a instanceof b` evaluates to `true`, and the private-field check requires an object as its right-hand operand. Previously, codegen emitted:
```js
return #x in a instanceof b;
```
Relational operators associate to the left, so this is parsed as `(#x in a) instanceof b`. The private-field check on `{}` produces `false`, and `false instanceof Object` then returns `false` instead of throwing.
The corrected output preserves the grouping in both modes:
```js
// Default output
return #x in (a instanceof b);
// Minified output
return#x in (a instanceof b)
```
`PrivateInExpression::gen_expr` now prints its right-hand operand with `Precedence::Compare` instead of `Precedence::Equals`. The printer wraps expressions whose precedence is equal to or lower than the requested precedence. Using `Compare` therefore preserves parentheses around relational operands, including `<`, `<=`, `>`, `>=`, `instanceof`, and nested private-in expressions. Equality and lower-precedence expressions continue to retain their required grouping, while shift, arithmetic, and exponentiation operands can still omit unnecessary parentheses.
This addresses the RHS grouping issue separately from #26383, which fixed the precedence passed to private-in expressions appearing as the left operand of another binary expression. The change is confined to the Rust `oxc_codegen` crate.
TypeScript allows a default import binding named `type` to contain Unicode escapes. Oxc currently reports `Keywords cannot contain escape characters` for these valid imports:
```ts
import t\u0079pe from "m";
import t\u0079pe, { x } from "m";
import t\u0079pe, * as ns from "m";
```
In each example, `t\u0079pe` is an identifier whose decoded name is `type`, and the declaration is a value import.
The parser initially recognizes the decoded `type` token before it has enough context to distinguish a default binding from a type-only import modifier. The escape check ran at that point, so it rejected the binding even when the following tokens established that this was a value import.
Move the escape check into the two branches that interpret `type` as a modifier: before a named or namespace import, and before another binding identifier after ruling out `import type from "m"`. The latter check also runs before the early return for a type-only import-equals declaration.
Escaped modifiers remain invalid, including these forms:
```ts
import t\u0079pe Foo = require("m");
import t\u0079pe { x } from "m";
import t\u0079pe * as ns from "m";
import { t\u0079pe Bar } from "m";
```
The change is limited to when the declaration-level escape diagnostic is emitted; import classification and specifier parsing retain their existing behavior.
## Summary
Getter accessors must have no formal parameters, but Oxc currently accepts a getter whose only parameter is a rest parameter. This affects class and object getters, as well as getter signatures in TypeScript interfaces and type literals.
```ts
class C { get x(...args) {} }
({ get x(...args) {} });
interface I { get x(...args: any[]): string }
type T = { get x(...args: any[]): string };
```
Each declaration above previously parsed without a diagnostic. It now reports the existing error: `A 'get' accessor must not have any formal parameters.`
## Cause and correction
`FormalParameters` stores ordinary parameters in `items` and the rest parameter separately in `rest`. Both getter checks inspected only whether `items` was empty, so a rest-only parameter list looked like a zero-parameter list.
Use `parameters_count() != 0` in both checks so the count includes the rest parameter. The shared `check_getter` path covers class and object getters; `parse_getter_setter_signature_member` covers interface and type-literal getter signatures.
The diagnostic still points at the full parameter list. Zero-parameter getters remain valid, ordinary getter parameters retain their existing diagnostic, and class/object accessors with type parameters retain the current type-parameter diagnostic precedence. Setter validation is unchanged.
## Summary
Allow the `in` operator inside an arrow function’s block body when the arrow appears in a `for` initializer:
```js
for (() => { a in b; };;);
```
This is valid JavaScript, but the parser previously reported a missing semicolon at `in`. The block now parses successfully, including async arrows and arrows with a single unparenthesized parameter.
## Cause and fix
A `for` initializer disables `Context::In` so that an unparenthesized `in` can delimit a `for…in` statement. Arrow block bodies inherited that disabled context through `parse_arrow_function_expression_body`, which called `parse_function_body` directly.
The block-body branch now calls `parse_function_body` through `context_add(Context::In, ...)`. This enables `in` throughout the function body and restores the enclosing context afterward. It also permits nested concise arrows inside that block to use `in`.
The change is confined to block bodies because concise expression bodies inherit the surrounding grammar’s `In` parameter. For example, this remains invalid:
```js
for (var f = () => a in b;;);
```
Restoring the enclosing context also keeps this invalid, rather than accidentally treating `a in b` as a regular initializer expression after parsing the first arrow:
```js
for (var f = () => {}, value = a in b;;);
```
The parser currently accepts mixed nullish and logical expressions when parentheses cover only the first operand of the logical expression:
```js
(a) || b ?? c;
a ?? (b) || c;
(a) && b ?? c;
a ?? (b) && c;
```
These expressions require a syntax error because `??` cannot be mixed directly with `||` or `&&`. Parentheses around `a` or `b` do not group the complete logical expression. This change reports the existing mixed-coalesce diagnostic for all four forms.
Fully grouped operands remain valid:
```js
(a || b) ?? c;
a ?? (b || c);
(a && b) ?? c;
a ?? (b && c);
```
### Why the existing check misses these expressions
`parse_binary_expression_rest` used `lhs_parenthesized` and `rhs_parenthesized` flags captured before parsing an operand. Those flags describe whether the initial token is `(`, but the parser can subsequently accumulate a larger logical expression outside those parentheses. Applying the initial flag to that complete expression incorrectly suppresses the diagnostic.
### Complete-operand checks
Check both operands independently for an `And` or `Or` logical expression whose span starts at the operand's starting position. A logical expression accumulated outside initial parentheses starts at that position and must be rejected. Checking both operands also prevents a grouped right operand from hiding an invalid left operand.
This works with either `preserve_parens` setting. When parentheses are preserved, a fully grouped operand is a `ParenthesizedExpression`. When they are omitted, its inner logical expression retains a span beginning after the opening `(`, so its start differs from the operand start and it remains allowed. For `(a) || b`, the complete logical expression instead starts at the opening `(` and is correctly rejected.