Summary:
- Upgrade oxc_sourcemap to 8.0.1.
- Return borrowed SourceMap from CodegenReturn.
- Support no-sourcemap feature builds used by benchmarks.
AI-assisted: yes
- Emit TS1228 for invalid type predicate positions from semantic syntax checks.
- Treat valid predicates as only those that are the immediate return type of a function, method, function type, call signature, or non-accessor method signature.
## Why semantic
TypeScript parses type predicates as part of the normal type grammar, then validates their placement later in checker logic. This is not a parser recovery decision: the syntax can be represented in the AST, but whether it is legal depends on the predicate node's parent chain.
Keeping this check in semantic avoids parser mode plumbing and matches that model. The semantic checker can inspect ancestry to reject predicates in property annotations, constructor types, accessors, construct signatures, and nested or parenthesized type positions while reporting the span on the predicate node itself.
## 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){}}
· ───────────
╰────
```
Part of the effort to remove all checkpoint usage from the parser.
Replace the speculative JSX parse (checkpoint + rewind) in `parse_jsx_in_non_jsx_error` with a direct fatal error on the `<` token, with help to enable JSX in the parser options.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Introduces `oxc_diagnostics::Diagnostics` — a `Vec<OxcDiagnostic>` newtype that knows the difference between errors and warnings — and makes it the standard diagnostics return value across oxc.
- **New type**: `Diagnostics` with `has_errors()`, `has_warnings()`, `errors()`, `warnings()`, plus `Vec`-like ergonomics (Deref, `IntoIterator`, `Extend`, `From`/`Into<Vec>`) so most call sites are unchanged.
- **Return types**: `ParserReturn`, `SemanticBuilderReturn`, `IsolatedDeclarationsReturn`, the React Compiler `TransformResult`/`LintResult`, and `TransformerReturn` now expose `diagnostics: Diagnostics` instead of `errors: Vec<OxcDiagnostic>` (the React Compiler's separate `errors`/`warnings` are merged into one severity-tagged list).
- **Behaviour**: a React Compiler **error** now stops the transform (no output), while **warnings** flow through and the transform continues. `compile()` aborts codegen only when `diagnostics.has_errors()` — warnings alone no longer abort (previously any diagnostic did).
## Breaking change
Consumers reading `*.errors` on these return values must read `*.diagnostics`. React Compiler consumers that read `.errors`/`.warnings` separately should use `diagnostics.has_errors()` / `diagnostics.errors()` / `diagnostics.warnings()`.
Parser/semantic/isolated-declarations fatality is otherwise unchanged. Builds on the React Compiler transform feature (#23201).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
Exposes the React Compiler from `oxc_transformer` behind a Cargo feature so callers no longer need a separate `oxc_react_compiler::transform` + scoping-rebuild step before transforming.
- **`oxc_transformer`**: new `react_compiler` Cargo feature (off by default) + `TransformOptions::react_compiler` field. When set, `Transformer::build_with_scoping` runs the React Compiler first (via a private `run_react_compiler` method), replacing the program and rebuilding scoping for the downstream transforms.
- **Opt-in**: the feature is **not** enabled by `oxc_transformer`'s defaults, nor by `oxc`'s `transformer`/`full` features — enabling the transformer does not pull the heavy `oxc_react_compiler` dependency. It is a separate `react_compiler` feature on `oxc` (which implies `transformer`); the napi transform binding opts in explicitly.
- **Dedupe**: removes the inline React-Compiler orchestration (and the `react_compiler_options()` hook) from `oxc`'s `CompilerInterface` and the napi transform binding; both now drive it through `TransformOptions::react_compiler`.
- **Diagnostics**: the compiler's errors and warnings are merged into `TransformerReturn::errors` (each keeps its severity; downstream filters). `compile()` reports them but no longer aborts codegen — the transformer always leaves a valid program, matching the documented "code may still be available even if there are errors" behaviour.
- **Links**: updates the React Compiler references to the merged upstream location (`react/react/tree/main/compiler`).
## Notes
Transform/codegen output is unchanged when the feature is off (the field defaults to `None` everywhere). The diagnostics behaviour change (no codegen abort) is conformance-safe — the transform-conformance harness keys off reported diagnostics, not printed output.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## Summary
- Pass the definite-assignment assertion token offset through the class accessor-property parser path.
- Report invalid ambient and abstract `accessor prop!` diagnostics on the `!` token, matching the span behavior used by variable declarators and normal class properties.
- Add focused misc parser coverage for the accessor-specific invalid cases.
## Rationale
This follows up #23170. That parser change already narrowed definite-assignment diagnostics for variables and normal class properties, but accessor properties still kept only a boolean. That meant `accessor prop!` could only report TS1255 using the full class element span. Carrying the offset into `parse_class_accessor_property` aligns accessor diagnostics with the other definite-assignment diagnostics and keeps the AST shape unchanged by storing only `definite.is_some()`.
The misc fixture is needed because upstream TypeScript coverage exercises invalid definite assertions on normal fields, but not invalid accessor properties. Existing misc coverage only had a valid `accessor y!` pass case, so `just coverage` did not specifically protect the accessor diagnostic span this fixes.
- Store class property definite assignment assertion positions as `u32` offsets instead of carrying a full `Span`.
- Reconstruct the one-character diagnostic span only when emitting class definite-assignment diagnostics.
- aligns the behaviour for definite assertion errors with variables by reporting on the definite assertion rather than on the whole class member
- Match TypeScript’s diagnostic precedence for class property definite assignment assertions.
- Report TS1264 when a class property uses ! without a type annotation.
- Report TS1255 when a definite assignment assertion is used in disallowed class property contexts, including static.
Inside an internal `namespace N {}` / `module N {}`, statements that
reference a
module are not permitted — this matches both babel and TypeScript:
- `import ... from "m"` / `import x = require("m")` → TS1147
- `export ... from "m"` / `export * from "m"` → TS1194
- `export default ...` → TS1319
- `export = ...` → TS1063
Bare `export { x }` (re-export of locals, no module source) and `import
x = A.B`
(entity alias) stay valid, as do external modules (`declare module "x"
{}`) where
these forms are allowed — the check only runs for internal namespaces,
so those
never reach it.
Also removes TS1147 from the coverage denylist
(`NOT_SUPPORTED_ERROR_CODES`), since
oxc now detects it.
Conformance (no regressions, positive/AST stay 100% everywhere):
- parser_babel negative 1696 → 1704
- parser_typescript negative 1585 → 1594
## What
Make the full `AstNodes` store **opt-in**, so the compiler pipeline (transformer / codegen / minifier) can run semantic analysis without building it at all.
Previously every `SemanticBuilder` run allocated and populated the full `AstNodes` store, even for consumers that only need scoping + the syntax checker. This separates the node-related builder state into three independent pieces:
- a standalone `NodeId` counter — the source of truth for node ids, valid in either mode;
- an **ancestry stack** — an upward-walk-only stack of the current node's ancestors, the only ancestry the syntax checker and binder actually read;
- the full **`AstNodes`** store — random access to every node, needed by the linter and mangler.
## How
- A new `AstNodeStore` (in `crates/oxc_semantic/src/node/store.rs`) bundles the node-id counter, the current-node cursor/flags, and the storage. The storage is an enum (`AstNodeStoreKind`) with two variants — `Full(AstNodes)` and `Ancestry(AncestryStack)` — so the full store and the ancestry stack are **mutually exclusive by construction**; they can never both be maintained at once.
- The checker and binder read ancestry through an `Ancestry` view (`node/ancestry.rs`) that dispatches between the full store (linter / mangler) and the stack (compiler pipeline). They no longer do random `NodeId` access into the store — all ancestry access is upward-walk-only.
- `SemanticBuilder` gains `with_build_nodes(bool)`. The default is **off** (ancestry stack only). `new_linter()` turns it on (the linter needs the full store); `new_compiler()` leaves it off.
- `CompilerInterface::build_semantic_nodes()` lets `oxc::Compiler` consumers opt back in if their `after_semantic` hook reads `Semantic::nodes`.
## Notes
- Behaviour-neutral: semantic conformance snapshots are unchanged.
- Allocation snapshots updated — the default (ancestry) path allocates slightly less (the growing `Stack` trades a couple of fixed allocations for a few reallocs).
- Builds on the already-merged `SymbolFlags`-based redeclaration checks (#23060) and the `new_linter`/`new_compiler` preset adoption (#23057), which removed the last random node reads from the redeclaration path so the checker works without the store.
## What
Moves the reserved built-in type name check (a type's name may not be `any`, `string`, `number`, …) out of the semantic checker into the parser, via a reusable `check_reserved_type_name` helper. Covers **all** declaration forms:
- class → TS2414
- interface → TS2427
- type alias → TS2457
- enum → TS2431
- type parameter → TS2368
The semantic `check_type_name_is_reserved` helper and its diagnostic are removed.
## Why it moves cleanly
- **Zero state:** each is a name-vs-reserved-list match at the parse site where the identifier is parsed.
- **Node-final:** these declarations are never reinterpreted by cover grammar.
- **Negative-only:** confirmed by `cargo coverage` — no positive/negative count changes; only the errors relocating in `parser_typescript.snap`.
## Class needs an `is_ts` guard + a fixture fix
`class string {}` is valid JavaScript (these names are reserved only as *type* names), so the class check is guarded by `is_ts`. The interface/type-alias/enum/type-parameter forms are TypeScript-only parser paths, so they need no guard.
The `no-shadow-restricted-names` rule had `class undefined {}` / `(class undefined {})` fixtures. Since the lint test harness parses fixtures as TSX, those are now parse errors (TS2414). They're dropped — class-name shadowing is still covered by `class globalThis {}` (globalThis isn't a reserved type name), and `undefined` shadowing by the `var`/`import` fixtures.
## Verification
- `class number {}`, `interface any {}`, `type any = number`, `enum string {}`, `function f<any>() {}` → error; `class string {}` in `.js` → clean; valid names → clean.
- Parser/semantic/linter (1155) tests pass; clippy clean.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## What
Moves the "'abstract' modifier cannot be used with a private identifier" check (TS18019) for **class fields** out of the semantic checker (`check_property_definition`) into the parser.
## Why it's a clean move
- **Zero state:** the check is `r#abstract && name.is_private_identifier()` — only node fields, evaluated where the class element's modifiers and key are already in hand (next to the existing `abstract_property_cannot_have_initializer` check).
- **Node identity is final at parse time:** a `PropertyDefinition` is a class field; classes are never reinterpreted by cover grammar (unlike object literals → patterns), so there's no value-vs-pattern ambiguity.
- **Negative-only:** `abstract #x` is broken code, confirmed by `cargo coverage` — no positive-suite regressions.
The identical diagnostic for abstract **methods** (`check_method_definition`) stays in the semantic checker, so the shared diagnostic is kept.
## Conformance
No count changes in any suite; the only snapshot delta is the TS18019 error relocating (now emitted during parsing) in `parser_typescript.snap`. Parser/semantic/linter tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## What
Moves the "A 'default' clause cannot appear more than once in a 'switch' statement" (TS1113) check out of the semantic checker (`check_switch_statement`) and into the parser's `parse_switch_statement`.
## Why
The check only reads the switch statement's own `cases` list, which is fully built at the parse site — it needs no scope, symbol, or strict-mode information, so it can move to the parser without introducing any new parser state. Reporting it during parsing surfaces the error in source order and removes a node-kind dispatch from the semantic builder.
## How
- The duplicate-`default` check is folded into the case-parsing pass (no second loop over `cases`). To do this without duplicating `parse_normal_list`'s open/loop/close logic, its callback bound is relaxed from `Fn` to `FnMut` (a safe generalization — all existing callers already satisfy it), so the case callback can track the first `default` span inline.
- Only the first duplicate per `switch` is reported, matching the previous semantic-checker behavior.
- A `unicorn/no-useless-switch-case` pass fixture that relied on a two-`default` switch being a *recoverable* (semantic-only) error was removed: the linter treats any parser error as fatal to the file, so a duplicate `default` now stops that file from being linted.
## Conformance
No positive/negative count changes in any suite. The only snapshot delta is a reordering in `parser_typescript.snap` (the error now fires during parsing, in source order, ahead of later errors in the same switch).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
## What
Move the `MetaProperty` early-error checks from the semantic checker (`oxc_semantic`) into the parser:
- **`new.target` outside a valid context** — e.g. at the top level, in a top-level arrow, or in a computed class-member key.
- **`import.meta` outside a module** — i.e. in script / CommonJS code.
Consumers that only run the parser (without `SemanticBuilder`) now get these diagnostics.
## How
- `new.target` validity is tracked with a new inherited `Context::NewTarget` flag (widening `Context` from `u8` to `u16`). The flag is *added* by three constructs — a regular (non-arrow) function's parameters + body, a class static block, and a class field initializer — and *inherited* through arrow functions and class bodies. This naturally models the spec rule, including cases like `function g(){ class D { [new.target](){} } }` (valid, inherits `g`'s context).
- `import.meta` reuses the existing `error_on_script` deferral, so in unambiguous source `visit_import_meta` marks the file a module and the deferred error is discarded.
- `check_meta_property` and its now-unused diagnostics are removed from `oxc_semantic`.
## Spec correctness
Verified all cases against V8 (Node v24). One behavior change vs. the old semantic checker: a computed class **field** key such as `class C { [new.target] = 1 }` at the top level is now correctly a **SyntaxError**, matching V8 — the old checker accepted it due to a coarse `PropertyDefinition` AST-ancestor match.
| Source | V8 | this PR |
| --- | --- | --- |
| `new.target;` | SyntaxError | error |
| `function f(){ new.target; }` | ok | ok |
| `() => new.target;` | SyntaxError | error |
| `class C { x = new.target; }` | ok | ok |
| `class C { static { new.target; } }` | ok | ok |
| `class C { [new.target] = 1; }` | SyntaxError | error |
| `class C { [new.target](){} }` | SyntaxError | error |
| `function g(){ class D { [new.target](){} } }` | ok | ok |
| `import.meta` in script | SyntaxError | error |
| `import.meta` in module | ok | ok |
Parser & semantic conformance remain 100% positive / 100% negative on test262; all unit tests pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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)
The Printer never consumes these; they belong with each language formatter.
- Evict `Quote` (each formatter owns `QuoteStyle`)
- `normalize_string` takes a quote byte
- Evict `Expand` / `BracketSpacing` / `TrailingCommas` into oxc_formatter and oxc_formatter_json
- JSON's `TrailingCommas` is now two-variant (`Always`/`Never`)
- collapse Prettier's `all`/`es5` at each translation layer
- Rename `util/` -> `spec/` (pure shared helpers)
- Split `SourceText`: mechanical access stays in core, JS/TS lexis moves to `SourceTextExt`
- Drop the `oxc_syntax` dependency from core
All behaviros, Prettier conformance are unchanged.
## Summary
In unambiguous (`.js`) sources, `@foo export default class C { x = await + 1 }` reported a spurious `A module cannot have multiple default exports`.
A leading decorator makes the statement start with `@`, not `export`, so the eager Module-goal switch from #22684 didn't fire. The `await` identifier in the field initializer then triggered the unambiguous-mode top-level-await reparse, which re-ran `parse_statement_list_item` and recorded the `export default` a second time.
## Fix
- Commit to the Module goal in `parse_export_declaration` — the single place both leading and decorated exports flow through — so the export body parses under `Await` on the first pass, matching a non-decorated leading `export` (`ExportDeclaration` is unambiguously a `ModuleItem`, ECMA-262 §16.2.3).
- Skip the reparse for module declarations (`!stmt.is_module_declaration()`): an `export` / `import` is recorded on the first pass and is goal-independent, so reparsing only double-records it.
This also simplifies `parse_directives_and_statements`: the mutable `track_await_reparse` flag and the inline eager-commit block are removed; committing is now driven by `!ctx.has_await()`.
`await` stays correctly reserved in the module, so the input is still a syntax error (`The keyword 'await' is reserved`) — just no longer a *spurious* duplicate-export one. `tasks/coverage/misc/fail/oxc-22684.js` covers this.
Conformance: zero snapshot drift outside the new `misc` fixture.
Follow-up to #22684.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
This adds the TypeScript parser diagnostic for definite assignment assertions in class member contexts where TypeScript rejects them.
`!` on class properties is still allowed for normal initialized-check suppression, but we now report TS1255 when it appears on ambient class members or abstract properties/accessor properties. This covers cases like `declare property!: T`, `declare class C { property!: T }`, and `abstract property!: T`.
Fixes a missing TypeScript parser diagnostic for optional setter parameters.
TypeScript reports `TS1051` for setters like:
```ts
class C {
set Foo(a?: number) {}
}
```
Oxc already had the diagnostic defined and used it for TypeScript accessor signatures, but concrete class/object setter validation only checked rest parameters and initializers. This adds the missing `param.optional` check in TypeScript mode and updates the parser TypeScript coverage snapshot.
Adds parser validation for TypeScript accessor declarations with type parameters.
TypeScript reports TS1094 for accessors like `get foo<T>()` and `set foo<T>(value)`. Oxc already parsed the type parameters, but did not emit the matching parser diagnostic, so the relevant TypeScript conformance cases were still listed as expected syntax errors.
Align `oxc_formatter` with `oxc_formatter_json`, both now expose `format(...) -> Result<Formatted<'a, _>, OxcDiagnostic>`.
- Add text-in `format` and `format_fragment`, also `format_program`, `parse_for_format` and the `FragmentContext` enum
- Parse options, JSX enabling, js-in-xxx context aware handlings are applied internally
- Make the `Formatter` struct and AST/IR primitives crate-internal
- Fold `get_parse_options` / `enable_jsx_source_type` and the `service` module into `parse_for_format`
A generator (`function*` / `*method`) is not allowed in an ambient context
(TS1221), and a generator overload signature with no body is not allowed
(TS1222). Mirrors tsc's `checkGrammarForGenerator`:
declare function* f(): any; // now TS1221
declare namespace N { function* g(); } // now TS1221
declare class C { *m(); } // now TS1221
function* f(): any; // overload → now TS1222
function* f() { yield 1; }
function* f() { yield 1; } // normal generator → still valid
The check lives in `parse_function` next to the TS1183 implementation-in-ambient
check and keys off `ctx.has_ambient()` / `body.is_none()`. Class methods are
covered too (`class.rs` does not check generators, so there is no duplicate
diagnostic).
Also fixes a latent bug: `parse_ts_declare_function` hard-coded `generator: false`
and never consumed the `*`, so `declare function* f()` silently dropped the
generator flag. It now eats the `*` and threads the flag through (which is what
lets TS1221 fire there).
parser_typescript +7 negative-pass (generatorInAmbientContext1-4, generatorOverloads1-3);
positive/AST stay 100%.
An index signature parameter type must be `string`, `number`, `symbol`, or a
template literal type. This adds the syntactic check oxc was missing for the
direct cases:
interface I { [x: boolean]: number; } // now TS1268
interface I { [x: "a"]: number; } // now TS1337
The check accepts the valid key keywords (`string`/`number`/`symbol`/`any`) and
rejects any other keyword type (via `TSType::is_keyword`) or a literal type; the
single-parameter validation is folded into the same `match`. Non-keyword nodes
(type references, unions, templates) need type resolution and remain a checker
concern, so this is a conservative, regression-safe subset of the full rule.
parser_typescript +1 negative-pass; positive/AST stay 100%.
A `declare` modifier is redundant and not allowed when its declaration is nested
directly inside an ambient `declare namespace` / `declare module` body (TS1038).
A top-level `declare` in a `.d.ts` file stays valid:
declare namespace N { declare function f(): void; } // now TS1038
declare module "m" { declare function f(): void; } // now TS1038
// foo.d.ts
declare function f(): void; // valid (top level)
namespace N { declare function f(): void; } // valid (non-ambient namespace)
Checked in `parse_ts_declaration_statement` against the enclosing context captured
before the declaration unions in its own ambient flag: `declare` modifier present,
`reserved_ctx.has_ambient()`, and `!reserved_ctx.has_top_level()`. The
`has_top_level()` guard is what exempts a `.d.ts` source file (whose parent is the
SourceFile, not a module block) while still covering internal namespaces and
external `declare module "x"` bodies. No valid construct has
`declare + ambient + !top_level`, so there is no positive regression.
Two linter fixtures used a redundant nested `declare` and are updated to valid TS
(`no_namespace`, `prefer_namespace_keyword`).
parser_typescript +9 negative-pass; positive/AST stay 100%.
A non-declaration statement (`if`/`for`/`while`/expression/`try`/block/etc.) is not
allowed directly inside an ambient block — a `.d.ts` top level or a `declare`
namespace/module body:
declare namespace M { while (true); } // now TS1036
// foo.d.ts
foo(); // now TS1036
declare namespace M { export const x = 1; } // declarations stay valid
namespace N { while (true); } // non-ambient namespace: valid
Implemented inline in `parse_directives_and_statements`. Ambient-ness is captured
once at block entry (`(is_top_level || in_ts_namespace_body) && has_ambient()`),
which excludes function bodies (they report TS1183 for the body instead) and keeps
non-ambient blocks unaffected. A statement is flagged when it is neither a
`Declaration` nor a `ModuleDeclaration` (so imports/exports keep their specific
namespace diagnostics). The error is reported once per block, matching tsc's
`checkGrammarStatementInAmbientContext`. The hot path is unchanged: `has_ambient()`
short-circuits for all non-ambient code.
Three linter fixtures used invalid ambient statements and are updated to valid TS:
`no_shadow` (`declare; foo5: boolean;` -> `declare var foo5: boolean;`),
`check_tag_names` (a `.d.ts` block -> `declare namespace`), and
`prefer_namespace_keyword` (an assignment in a module body -> an ambient binding).
parser_typescript +18 negative-pass; positive/AST stay 100%.
`export as namespace X` declares a UMD global export, which may only appear at the
top level of a module or a `.d.ts` file. Inside an internal `namespace`/`module`
body it is invalid:
namespace N { export as namespace N; } // now TS1316
module M { export as namespace M; } // now TS1316
declare namespace N { export as namespace N; } // now TS1316
export as namespace Lib; // top level → still valid
This extends the namespace-body validation (`check_namespace_body_statement`) with
a `TSNamespaceExportDeclaration` arm emitting TS1316 ("Global module exports may
only appear at top level."), matching tsc and babel. The statement is already
checked inline as each direct child of the namespace body is parsed, so there is
no extra pass. External module declarations (`declare module "x" {}`) are left
unrestricted, consistent with the existing namespace-body checks.
parser_babel +1 negative-pass; positive/AST stay 100%.
A function declaration with a body is an implementation, which is not allowed in
an ambient context. oxc already emitted TS1183 ("An implementation cannot be
declared in ambient contexts") when the function carried its own `declare`
modifier, but missed functions that inherit the ambient context from an enclosing
`declare module` / `declare namespace` or a `.d.ts` file:
declare module "m" { function f() {} } // now TS1183
declare namespace N { function f() {} } // now TS1183
// foo.d.ts
function foo(): any {} // now TS1183
Bodyless signatures stay valid (`declare module "m" { function f(): void; }`,
`export declare function f(): void;`).
The check now keys solely off `ctx.has_ambient()` instead of the function's own
`declare` modifier, scoped to function declarations. Class methods are excluded
because they are already validated in `check_method_definition`, which avoids a
duplicate diagnostic. This matches `typescript-go`'s
`checkGrammarStatementInAmbientContext` / function-like body check, and babel.
The fast path is unchanged: `has_ambient()` is a cheap bitflag test that
short-circuits for all non-ambient code.
parser_babel +3 and parser_typescript +1 negative-pass; positive/AST stay 100%.
`static`/`abstract` and `declare`/`override` are mutually exclusive in either
order (TS1243):
- `static abstract m()` / `abstract static m()`
- `declare override x` / `override declare x`
oxc already validated modifier order (TS1029) and duplicates (TS1030). This extends
the existing `ILLEGAL_PRECEDING_MODIFIERS` table in both directions for these pairs
and reports them as "cannot be used with" (TS1243) rather than "must precede" — the
fast path is unchanged (still a table lookup + AND); only the cold error path picks
the message.
Matches babel and TypeScript: parser_babel +1 negative-pass
(`class/modifiers-incompatible`), positive/AST stay 100% across all suites, and the
correct TS1243 diagnostic is now emitted on existing TypeScript cases.
Inside an internal `namespace N {}` / `module N {}`, statements that reference a
module are not permitted — this matches both babel and TypeScript:
- `import ... from "m"` / `import x = require("m")` → TS1147
- `export ... from "m"` / `export * from "m"` → TS1194
- `export default ...` → TS1319
- `export = ...` → TS1063
Bare `export { x }` (re-export of locals, no module source) and `import x = A.B`
(entity alias) stay valid, as do external modules (`declare module "x" {}`) where
these forms are allowed — the check only runs for internal namespaces.
Each statement is validated inline as the namespace body is parsed (no second pass).
Also removes TS1147 from the coverage denylist (`NOT_SUPPORTED_ERROR_CODES`), since
oxc now detects it.
Conformance: parser_babel +8 and parser_typescript +9 negative-passes vs main, with
positive/AST staying 100% across all parser suites (no regressions).
Split `oxc_formatter` into:
- `oxc_formatter(_js)`: JS/TS specific formatter, comment handling, sortImports, jsdoc, etc
- `oxc_formatter_core`: Language agnostic IR, printer, etc
The naming convention is different from Biome or Ruff (they do not have `_core`, and `_formatter` is equivalent to the core), but this is because in Oxc, items without `_js` are the default.
As a basic task, I have added `Context` as a generic to all relevant parts to ensure support for multiple languages.
There might still be other components that could be refactored into the core, but I would like to finish them as I implement support for other languages.
Also moved some shared utilities to the core in advance.
## Summary
`cargo coverage` (a.k.a. `just c`) crashes locally on macOS with `fatal runtime error: stack overflow` while running `semantic_typescript` on `typescript/tests/cases/compiler/binderBinaryExpressionStressJs.ts`. That fixture builds a ~1500-deep left-leaning `BinaryExpression` chain (`0 + 1 + 2 + ... + 1499`).
macOS's `std::thread` default stack is 2 MB; Linux is 8 MB, which is why CI doesn't see this. The file used to fit because `OxcDiagnostic` was 8 bytes (`Box<OxcDiagnosticInner>`); after #22406 inlined the ~200-byte inner, every recursive frame holding a diagnostic grew, pushing past 2 MB on macOS.
This sets the rayon pool's `stack_size` to 8 MB to match Linux's default. Scope is limited to the dev coverage task — the perf win from #22406 is preserved, and no runtime consumers (parser, semantic, oxlint, oxfmt) are affected.
Verified `just c` runs end-to-end on macOS after the change.
## Summary
Fixes semantic scope metadata for private method helper functions that
are lifted out of classes nested inside static field initializers.
The class-properties transform converts a private method into a helper
function declaration and emits that helper outside the class. For a
nested class inside a static field initializer, `current_scope_id()`
points at the nested class scope, but the helper is actually emitted
into the surrounding hoist scope. That made semantic rebuild disagree
with the transformed AST.
## Before
```js
class Outer {
static value = class Inner {
static #method() {}
};
}
```
The helper function was reparented as if it still lived under the nested
class scope:
```mermaid
flowchart TD
Program["enclosing hoist scope"] --> Outer["Outer class scope"]
Outer --> Field["static field initializer"]
Field --> Inner["Inner class scope"]
Inner --> Helper["lifted helper function scope"]
Helper -.-> Problem["incorrect parent + strict-mode source"]
```
In code, the old logic always used the current scope and current scope
flags:
```rust
let new_parent_id = ctx.current_scope_id();
ctx.scoping_mut().change_scope_parent_id(scope_id, Some(new_parent_id));
let make_sloppy_mode = !ctx.current_scope_flags().is_strict_mode();
```
That is correct for normal private methods, but not for private methods
inside a class expression that appears in a static field initializer.
## After
When the transform is running under a static `PropertyDefinitionValue`,
the helper is parented to the current hoist scope instead:
```rust
let new_parent_id = if Self::is_inside_static_property_initializer(ctx) {
ctx.current_hoist_scope_id()
} else {
ctx.current_scope_id()
};
ctx.scoping_mut().change_scope_parent_id(scope_id, Some(new_parent_id));
let make_sloppy_mode = !ctx.scoping().scope_flags(new_parent_id).is_strict_mode();
```
The predicate checks the field is actually static:
```rust
matches!(ancestor, Ancestor::PropertyDefinitionValue(property) if *property.r#static())
```
The resulting scope shape now matches where the helper is emitted:
```mermaid
flowchart TD
Program["enclosing hoist scope"] --> Helper["lifted helper function scope"]
Program --> Outer["Outer class scope"]
Outer --> Field["static field initializer"]
Field --> Inner["Inner class scope"]
```
The strict/sloppy adjustment also reads from the selected parent scope,
so the helper's flags match the output location rather than the original
nested class context.
## Validation
- `just fmt`
- `cargo test -p oxc_transformer`
- `just coverage` was run before review. It aborted later with a stack
overflow / signal 6 in `target/coverage/oxc_coverage`; before the abort,
the targeted `semantic_test262` failures were removed from the generated
snapshot output.
AI assistance: Codex was used to cherry-pick, review, test, and draft
this PR description.
Made the errors for `({...[]} = {});` and `const {...[]} = obj;` a bit more easier to understand:
```
× Invalid rest element target in destructuring assignment
╭─[misc/fail/rest-assignment-target.js:1:6]
1 │ ({...[]} = {});
· ──
╰────
help: Expected an identifier or member expression, like `...rest` or `...obj.prop`.
```
```
× Invalid rest element target in destructuring pattern
╭─[misc/fail/rest-binding-element.js:1:11]
1 │ const {...[]} = obj;
· ──
╰────
help: Expected an identifier, like `...rest`.
```
This improves the parser diagnostic for invalid `import.<property>` access.
Previously, invalid properties like `import.sync("baz")` were reported as if only `import.meta` were valid, which is misleading now that `import.source()` and `import.defer()` are also supported.
Before:
```text
× The only valid meta property for import is import.meta
╭─[index.ts]
37 │
38 │ import.foo();
──────────
39 │
╰────
```
After:
```text
× The only valid property accesses on import are `import.meta`, `import.source()`, and `import.defer()`
╭─[index.ts]
37 │
38 │ import.foo();
──────────
39 │
╰────
```
Also adds a coverage fixture for the `import.sync("baz")` case and updates the parser coverage snapshots.
Fixes private method transforms so scopes inherited from a transformed private method are converted back to sloppy mode when the surrounding scope is sloppy.
Previously we only cleared `StrictMode` on the private method function scope itself. That left child scopes, such as async/generator wrapper scopes, marked as strict even though the transformed output is no longer inside a class method. This caused semantic rebuild mismatches across Babel and Test262 coverage.
Nested classes are still left strict, since class bodies are always strict mode.