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.
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
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.
The duplicate class element check stored only the last element for each name. This allowed a public element to hide an earlier private declaration, and a setter to hide an earlier getter (or vice versa):
```js
class DuplicateField {
#x;
x;
#x;
}
class DuplicateGetter {
get #x() {}
set #x(value) {}
get #x() {}
}
```
Both classes were accepted despite declaring the same private field or getter twice. The same problem affected setter/getter/setter sequences.
Key a single map by both the element name and whether it is private, so public elements cannot replace private declarations. This keeps the namespaces separate while reserving map capacity only once. For each private name, retain separate slots for setters and other declarations. Look for a previous declaration in the matching slot first, then fall back to the other slot. This catches repeated getters and setters while still allowing one getter/setter pair when both have the same static status.
The existing diagnostic logic continues to reject private names shared by static and instance elements, including the TypeScript-specific diagnostic. Public elements continue through the existing public-element duplicate check.
The parameter check only recognized `FormalParameter`, allowing `await` and `yield` in destructuring defaults inside rest parameters:
```js
async function f(...[x = await 0]) {}
function* g(...[x = yield 0]) {}
```
Include `FormalParameterRest` so both cases report the existing parameter diagnostic. The check still stops at nested functions, where `await` and `yield` can be valid.
Reject labeled and unlabeled `break` and `continue` statements that cross an arrow-function boundary.
For example:
```ts
while (true) {
(() => {
break;
});
}
```
now reports an illegal break, while jumps targeting loops inside the arrow remain valid.
## Summary
Fix a parser panic when a string-named export decodes to a literal `\u` sequence.
Lone-surrogate validation now uses the metadata captured by the lexer instead of rescanning decoded string contents. This also prevents false diagnostics for names such as `"\\uD800"`.
## Before:
```
thread 'main' (10990283) panicked at crates/oxc_ast/src/ast_impl/literal.rs:89:42:
end byte index 4 is out of bounds for string of length 0
stack backtrace:
0: __rustc::rust_begin_unwind
1: core::panicking::panic_fmt
2: core::str::slice_error_fail_rt
3: core::str::slice_error_fail
4: <core::ops::range::RangeTo<usize> as core::slice::index::SliceIndex<str>>::index
5: <str as core::ops::index::Index<core::ops::range::RangeTo<usize>>>::index
6: <oxc_ast::ast::literal::StringLiteral>::is_string_well_formed_unicode
7: <oxc_parser::ParserImpl<oxc_parser::config::NoTokensParserConfig>>::parse_module_export_name
```
Rejects initialized `let` and `const` declarations in `for...in` during parsing while preserving Annex B's non-strict `var` form.
Adds misc coverage for valid and invalid initializer forms.
Verified with `just ready` and parser conformance.
Closes#25149.
AI-assisted.
Quoted JSX attribute values do not use ECMAScript escape sequences. Skip strict-mode legacy escape validation for JSX attributes and cover the behavior in misc conformance.
AI-assisted by Codex.
## Summary
- parse ordinary class field initializers as `AssignmentExpression`
- reject unparenthesized comma expressions while preserving
parenthesized sequence expressions
- add paired misc conformance fixtures and update the generated
snapshots
## Root cause
Ordinary class field initializers used `parse_expr`, which accepts a
top-level comma and constructs a sequence expression. The ECMAScript
`Initializer` grammar requires an `AssignmentExpression`, so the
unparenthesized comma must remain for the class-element terminator check
to reject.
## Summary
- Align auto-accessor initializer parsing with class-field initializer grammar.
- Add regressions for `in` in a `for` initializer and disallowed inherited `await`/`yield` contexts.
## Root cause
The auto-accessor path only enabled `new.target`. It therefore inherited outer `~In`, `Await`, and `Yield` grammar contexts: `a in b` could not parse in a `for` initializer, while `await` and `yield` could incorrectly parse from enclosing async/generator functions. The fix applies the same `In | NewTarget` addition and `Yield | Await` removal already used for class fields, while retaining AssignmentExpression parsing.
## References
- [ECMAScript `FieldDefinition` grammar](https://tc39.es/ecma262/#prod-FieldDefinition)
- [ECMAScript `Initializer` grammar](https://tc39.es/ecma262/#prod-Initializer)
- [TC39 decorators proposal: class auto-accessors](https://github.com/tc39/proposal-decorators#introduction)
## Summary
Narrow `TSInterfaceHeritage` from a general JavaScript `Expression` to `TSTypeName`.
```rust
pub struct TSInterfaceHeritage<'a> {
pub type_name: TSTypeName<'a>,
pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}
```
## Breaking Change
The Rust AST field changes from:
```rust
expression: Expression<'a>
```
to:
```rust
type_name: TSTypeName<'a>
```
## Why?
The current AST can represent shapes such as:
```ts
interface A extends foo() {}
interface A extends A + B {}
interface A extends new Foo() {}
interface A extends true {}
```
Which is an overly wide type since all of the above variants are invalid.
This PR tightens up the AST, making these invalid variants irrepresentable.
## How to migrate?
This should be a trivial migration:
1. instead of accessing `expression` on `TSInterfaceHeritage`, now access `type_name`
2. change any pattern matching on the `type_name` field to use `TypeName` (this is a smaller subset so should allow deleting code!)
closes https://github.com/oxc-project/backlog/issues/215
Fixes#24804.
Recognize `!` as a type-start token so `?` after `satisfies` remains the conditional-expression operator instead of being consumed as a JSDoc nullable type.
Validated with `just ready`.
AI-assisted.
## Summary
- allow `eval` and `arguments` as direct bindings in ambient declarations
- continue reporting strict-mode errors for destructured ambient bindings
- add focused coverage for declared functions, classes, object patterns, and array patterns
## Why
The semantic checker previously identified allowed bindings by walking to a `TSDeclareFunction`. That missed other ambient declarations, including declared class constructors and methods, and produced diagnostics for TypeScript cases that require no runtime code generation.
The check now uses the existing ambient-context state and rejects only destructuring binding shapes.
## Summary
- Report operator-specific diagnostics when unary and `await` expressions are used as exponentiation left operands.
- Reject unparenthesized TypeScript type assertions in the same position while preserving explicitly parenthesized forms.
- Add regression fixtures and update the affected conformance snapshots.
Recognize identifiers nested within `declare` variable declarations and declared class properties as being in an ambient context.
This prevents valid TypeScript from incorrectly producing reserved-keyword diagnostics.
### Examples
Function types inside ambient variable declarations are now accepted:
```ts
declare let f: (package: number) => interface;
```
Type references inside declared class properties are also accepted:
```ts
class C {
declare field: package;
}
```
Previously, package and interface were incorrectly reported as reserved keywords.
## Summary
Keep transformer semantic data in sync when TypeScript-only syntax is removed.
This change:
- removes resolved and unresolved references belonging only to stripped TypeScript syntax
- removes bindings for erased type-only and unused imports
- preserves runtime bindings when a type import is merged with a value declaration
- promotes the surviving declaration’s symbol flags, span, and redeclaration metadata
- adds coverage for type imports merged with runtime declarations
- refreshes semantic and transformer conformance snapshots
## Motivation
We are preparing to change the AST shape, including adding and rearranging scopes.
Currently, transformer semantic snapshots contain thousands of lines of mismatches caused by stale bindings and references from TypeScript syntax that has already been removed from the AST. That creates a large amount of unrelated snapshot noise.
With that noise present, snapshot changes caused by the upcoming AST work would be difficult to review: a real scope or semantic regression could easily be hidden among mechanical churn.
This PR fixes the underlying semantic inconsistencies first, giving us a much cleaner baseline for reviewing future AST and scope changes. It is not intended to change emitted JavaScript.
## Summary
- Report TS2398 when a TypeScript constructor parameter property is named `constructor`.
- Add coverage for the upstream TypeScript fixture plus dedicated misc pass/fail cases for ordinary `constructor` parameters versus invalid parameter properties.
```
× TS(2398): 'constructor' cannot be used as a parameter property name.
╭─[misc/fail/oxc-ts-2398.ts:2:31]
1 │ class A {constructor(public constructor){}}
2 │ class B {constructor(readonly constructor){}}
· ───────────
╰────
```
Port of [microsoft/typescript-go#4192](https://github.com/microsoft/typescript-go/pull/4192), which fixes [microsoft/TypeScript#63527](https://github.com/microsoft/TypeScript/issues/63527).
## Problem
`as`/`satisfies` are supposed to be erasable without changing runtime semantics. But for an expression of the form `a ## b as T $$ c`, where `##` has *lower* precedence than `$$`, erasing the assertion silently changes the parse:
```ts
const x = 1 + 1 as number * 2;
```
This parses as `((1 + 1) as number) * 2` (= `4`), but textually erasing `as number` yields `1 + 1 * 2` (= `3`). TypeScript now rejects such constructs as a syntax error, forcing explicit parentheses.
## Change
In `parse_binary_expression_rest`, after parsing an `as`/`satisfies` type, stop parsing if the following operator binds tighter than the operator that produced the running left-hand operand. This leaves the higher-precedence operator dangling and surfaces as a parse error.
A chained `a as T as U` keeps referring back to the original operator's precedence (the tracked precedence is intentionally not updated while consuming `as`/`satisfies`), so `1 + 1 as any as number * 2` is rejected too.
## Behavior
| Expression | Result |
| --- | --- |
| `1 as number * 2` | ok |
| `1 * 1 as number + 2` | ok (`+` does not bind tighter than `*`) |
| `1 + 1 as number * 2` | **error** |
| `1 + 1 as any as number * 2` | **error** |
| `1 >> 1 as number + 2` | **error** |
| `(1 + 1 as number) * 2` | ok |
`satisfies` behaves identically to `as`.
## Tests
Added `misc` coverage fixtures mirroring the upstream test matrix: `pass/ts-erasable-as-satisfies.ts` for the erasable cases and `fail/ts-unerasable-{as,as-chained,satisfies}.ts` for the rejected ones. Full conformance (test262 / babel / typescript) shows zero regressions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
In unambiguous (`.js` auto-detect) sources, `export var x = await + 1;` reported a spurious `Duplicated export 'x'` error. The parser first parsed the statement with `await` as an identifier, then re-parsed it under `Context::Await` to patch the AST — but the second pass re-invoked `visit_export_named_declaration`, recording the binding `x` a second time in the module record.
`export` is unambiguously module syntax per ECMA-262 §16.2.3 (`ExportDeclaration` is only a `ModuleItem`, never a Script production). So commit to the Module goal — enable `Context::Await` — as soon as the next top-level token is `Kind::Export`. The statement then parses correctly on the first attempt; no reparse and no duplicate record.
`Kind::Import` is intentionally **not** eager-switched. TypeScript's `import name = ns.path` is a script-compatible namespace alias (see `topLevelAwait.2.ts`), so treating `import` as a module starter would break that case.
The only TS snapshot churn is +16 lines of additional error messages for already-failing tests (`topLevelAwaitErrors.5.ts`, `topLevelAwaitErrors.6.ts` — `export ... await`). Summary counts unchanged.
Closes#22520Fixes#22158
Fixes parser recovery for class computed property names whose expression starts with an array literal, such as `class C { [[]]() {} }`.
The TypeScript index-signature lookahead previously treated a nested `[` after the opening computed-name bracket as an unambiguous index signature. That caused valid class computed keys containing array literals to be parsed through the wrong branch and rejected.
This changes the lookahead so only `...` remains an early unambiguous index-signature signal, allowing nested arrays and other assignment expressions to parse as computed property names.
fixes: #22157
> This PR fixes TypeScript parser errors 1266 and 1257 by properly handling the `optional` field of `TSNamedTupleMember` when validating tuple element ordering constraints.
Introduced in #18146 and #18145
## Summary
- Fix parsing of `await (...)` and other cases where `await` followed by
certain tokens was parsed as an identifier instead of an await
expression in unambiguous mode
- Commit 92e27b4 only handled `await /regex/` but missed other cases
## Changes
- Add `encountered_await_identifier` flag to `ParserState` to track when
await is parsed as identifier
- Set flag in `parse_identifier_reference` when parsing "await" as
identifier in non-await context
- Modify statement parsing to track checkpoints only for statements that
actually used await as identifier
- Once ESM detected, enable await context for remaining statements
## Test plan
- Added test cases in `tasks/coverage/misc/pass/`:
- `unambiguous-await-call.js` - Tests `await (async function () { })();`
- `unambiguous-await-in-declaration.js` - Tests `const x = await (async
function () { })();`
- All parser tests pass
- All conformance tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Implements TypeScript-compatible await expression reparsing for unambiguous mode
- When `sourceType` is `unambiguous`, `await / 0 /u; export {}` now correctly parses as `await /0/u` (await expression with regex)
- Follows TypeScript's two-pass approach: initially parse `await` as identifier, then reparse with await context if ESM is detected
## Test plan
- Added `tasks/coverage/misc/pass/unambiguous-await-regex.js` test case
- Moved `tasks/coverage/misc/fail/babel-16776-s.js` to pass (now parses as division in script mode)
- All parser tests pass
- Conformance tests show improvements in parser_babel and semantic_babel
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Implements speculative/unambiguous parsing for `.js`, `.jsx`, `.ts`, and `.tsx` files
- Instead of assuming module or script upfront, parses with `ModuleKind::Unambiguous` and upgrades to module mode when ESM syntax is detected
- ESM indicators: `import`, `export`, `import.meta` (NOT top-level await alone)
- Defers top-level await errors until module type is resolved
- Vue loader upgrades unambiguous scripts to module mode
- Updates linter tests to use explicit ESM extensions (`.mjs`/`.mts`) where needed
## Test plan
- [x] All parser tests pass
- [x] All linter tests pass
- [x] All semantic tests pass
- [x] Coverage conformance tests updated
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Add check to error on `using` declarations at the top level of scripts
- `using` declarations require block scope semantics and are not allowed
at the top level of scripts
`using` declarations are allowed:
- At the top level of ES modules
- At the top level of CommonJS modules (files are wrapped in a function)
- Inside any block scope in scripts
But NOT at the top level of scripts.
Closes#11862
## Test plan
- [x] Added test case
`tasks/coverage/misc/fail/script-top-level-using.js` for invalid usage
- [x] Added test case
`tasks/coverage/misc/pass/script-using-in-block.js` for valid usage in
block
- [x] All existing tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Add `ModuleKind::CommonJS` variant to support CommonJS module type in the parser
- Enable top-level `return` statements (CommonJS files are wrapped in a function)
- Enable top-level `new.target` (allowed in CommonJS function wrapper)
- Add proper errors for `import`/`export` statements in CommonJS
- Add proper errors for `import.meta` in CommonJS
The CommonJS source type can be set via:
- `SourceType::with_commonjs(true)` method
- Babel's `sourceType: "commonjs"` option
Closes#16200
## Test plan
- [x] Added test cases in `tasks/coverage/misc/pass/` for valid CommonJS features
- [x] Added test cases in `tasks/coverage/misc/fail/` for invalid CommonJS usage
- [x] Enabled Babel conformance tests for `sourceType: "commonjs"`
- [x] All existing tests pass
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Improved error message for `yield` as identifier usage.
**Before**
```
× Cannot use `yield` as an identifier in a generator context
╭─[test262/test/language/expressions/generators/yield-weak-binding.js:17:33]
16 │
17 │ var g = function*() { yield 3 + yield 4; };
· ─────
╰────
```
**After**
```
× Cannot use `yield` as an identifier in a generator context
╭─[test262/test/language/expressions/generators/yield-weak-binding.js:17:33]
16 │
17 │ var g = function*() { yield 3 + yield 4; };
· ─────
╰────
help: Wrap this in parentheses if you want to use a `yield` expression here
```
Fixes#16839.
Make parser parse `let?.x` successfully.
There's a failure in codegen idempotency tests, but that's an unrelated
and pre-existing bug, so am leaving that to another PR.
Co-authored-by: overlookmotel <theoverlookmotel@gmail.com>