mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
codex/jsstr-core
920 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
32d00c55bc |
fix(codegen): preserve instantiation expression precedence (#26424)
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. |
||
|
|
ae6c386ed5 |
fix(codegen): preserve in restriction through yield arguments (#26413)
## 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`.
|
||
|
|
42ac916b21 |
fix(codegen): preserve private-in right operand precedence (#26411)
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.
|
||
|
|
60fa13878c |
release(crates): oxc v0.149.0 (#26397)
### 💥 BREAKING CHANGES - |
||
|
|
b156333a23 |
fix(codegen): order accessibility before abstract on accessors (#26392)
Codegen emits invalid TypeScript when an abstract accessor has an explicit accessibility modifier. Both normal and minified output put `abstract` before `public` or `protected`, causing TS1029 because accessibility must precede `abstract`.
For this valid input:
```ts
abstract class C {
public abstract accessor x: number;
protected abstract accessor y: string;
}
```
The previous output reordered the members to:
```ts
abstract public accessor x: number;
abstract protected accessor y: string;
```
`AccessorProperty::gen` now prints accessibility before `abstract`, matching the neighboring method and property emitters and preserving the input's valid modifier order. The same ordering applies in minified output:
```ts
abstract class C{public abstract accessor x:number;protected abstract accessor y:string}
```
The change is confined to accessor modifier ordering; subsequent `static`, `override`, and `accessor` emission remains unchanged.
|
||
|
|
2da73b7824 |
fix(codegen): print matching quoted import names as identifiers (#26386)
Codegen omitted the alias whenever an imported name matched its local binding, comparing the decoded names without accounting for whether the imported name was a string literal. That produces invalid syntax for quoted import names, which require an explicit `as` binding:
```js
// Input
import { "foo" as foo } from "m";
// Before
import { "foo" } from "m";
// After
import { foo } from "m";
// After, minified
import{foo}from"m";
```
When a string-literal import name matches the emitted local binding name, print the local binding directly and use identifier shorthand. The matching binding already supplies a valid identifier, so this does not need a separate identifier-validity check or an AST mutation. The conversion applies in both default and minified output.
The comparison uses the decoded string value, so escaped names and Unicode names can also use shorthand. Declaration-level and inline type imports follow the same rule:
```ts
// Input
import { "a\u0062" as ab } from "m";
import { "π" as π } from "m";
import type { "Foo" as Foo } from "types";
import { type "Bar" as Bar } from "types";
// After
import { ab } from "m";
import { π } from "m";
import type { Foo } from "types";
import { type Bar } from "types";
```
Quoted names that differ from their local binding retain their quotes and aliases, including `"foo-bar" as foo`, `"" as foo`, and `"default" as foo`. Ordinary identifier imports retain the existing shorthand behavior.
|
||
|
|
0a81d293f9 |
fix(codegen): preserve private-in left operand precedence (#26383)
Private-in expressions lose required parentheses when they appear on the left of a higher-precedence binary operator. This affects both default and minified code generation and can change the result or cause a runtime error.
For example:
```js
class C {
#x;
test(o) {
return (#x in o) + 1;
}
}
console.log(new C().test(new C()));
```
The input prints `2`. Previously, codegen emitted this return expression:
```js
return #x in o + 1;
```
Because `+` binds more tightly than `in`, this is parsed as `#x in (o + 1)`. The addition converts the object to a primitive, so the private-field check throws a `TypeError` instead of producing the boolean used by the original addition.
With this change, the required grouping is preserved:
```js
// Default output
return (#x in o) + 1;
// Minified output
return(#x in o)+1
```
The binary expression visitor already computes the precedence required for its left operand. However, its `PrivateInExpression` special case discarded that value and called `gen_expr` with `Precedence::Lowest` and an empty context. Passing `self.left_precedence` and `self.ctx` lets the existing private-in printer apply its parentheses rule using the same inputs as other left operands.
This preserves parentheses for arithmetic, exponentiation, and shift operators. Equal- and lower-precedence operators, such as `<`, `===`, and `&&`, continue to omit unnecessary parentheses.
|
||
|
|
2c9a947363 |
feat(parser)!: reduce MAX_LEN to 256 bytes below u32::MAX (#26352)
Technically a breaking change, but one that's extremely unlikely to affect any real-world applications. On 64-bit systems, the maximum source text length was exactly `u32::MAX`. Lower it by 256 bytes, for 2 reasons: 1. `oxc_lexer` will require source text to be followed by 64 bytes of padding. Reducing max source length ensures _padded_ length can always fit in a `u32`. 2. Counts of tokens, comments and errors are all bounded by source length plus a small constant. By reducing max source length, these can all be stored as `u32`s without any possibility of overflow. `oxc_lexer` only needs 64 bytes of padding. Have gone with 256 to leave headroom in case later redesigns require more than 64. This avoids another breaking change later. We can always switch to 64 later on if we want, and this later relaxation would not be a breaking change. All the changes to other crates in this PR are just updating comments which talk about max source length. |
||
|
|
03cdfdbf17 |
test(codegen): group unapplied annotation comment cases (#26295)
## Summary - group unapplied `PURE` and `NO_SIDE_EFFECTS` codegen coverage - keep the mixed-annotation preservation regression with related cases |
||
|
|
bfb4c57a5d |
feat(parser): distinguish unapplied no-side-effects comments (#26120)
## Summary Fixes #26088 `NO_SIDE_EFFECTS` can either be applied to a function: ```rs /* #__NO_SIDE_EFFECTS__ */ function foo() {} ``` Or be meaningless for its position, since it only works on function declaration: ```rs /* #__NO_SIDE_EFFECTS__ */ value; ``` But oxc could not distinguish these two cases. This PR adds `CommentContent::NoSideEffectsNotApplied`, mirroring the existing `PureNotApplied`. --------- Co-authored-by: Cameron <cameron.clark@hey.com> |
||
|
|
894c8f9cd8 |
release(crates): oxc v0.148.0 (#26198)
### 💥 BREAKING CHANGES - |
||
|
|
4e258430cd |
release(crates): oxc v0.147.0 (#26046)
### 🚀 Features - |
||
|
|
8db105342f |
fix(codegen): require sourcemap for example (#26047)
## Summary - require the sourcemap feature when building the codegen example - document the example's sourcemap option - allow oxc_codegen to test cleanly without default features |
||
|
|
fe444cc10a | perf(codegen): avoid allocations for single comment anchors (#25598) | ||
|
|
8a9bdbd6d8 |
fix(estree): include decorators in FormalParameterRest spans (#26021)
Serialize `FormalParameterRest` with its outer span so its range includes parameter decorators. Regenerate the raw-transfer deserializers and remove `sourceMapValidationDecorators.ts` from the ESTree mismatch baseline. Fixes #26011. |
||
|
|
6c5ad1b723 |
fix(codegen): only add mapping names for PrivateIdentifiers which have changed (#25958)
A `PrivateIdentifier`'s span includes the leading `#`, but the `name` the AST holds does not. The source map name was read from the whole span and then compared to `name` from AST, so the check which omits the mapping's `name` when it wasn't renamed compared `#foo` against `foo`. It never matched, so a named mapping was created for _every_ private identifier, renamed or not. This PR fixes that in both Rust `oxc_codegen` and JS `oxc-codegen`, by comparing correctly including the `#`. This also makes mapping names correct when a normal `Identifier` has been replaced with a `PrivateIdentifier` (`this.foo` -> `this.#foo)`, or vice-versa (`this.#foo` -> `this.foo`). New tests cover both directions. |
||
|
|
1bb5c236f7 | refactor(codegen): simplify add comment logic (#25952) | ||
|
|
d5d575c1de |
chore(deps): update rust to v1.98.0 (#25945)
This PR contains the following updates: | Package | Type | Update | Change | |---|---|---|---| | [rust](https://rust-lang.org/) ([source](https://redirect.github.com/rust-lang/rust), [changelog](https://redirect.github.com/rust-lang/rust/blob/main/RELEASES.md)) | toolchain | minor | `1.97.1` → `1.98.0` | --- ### Configuration 📅 **Schedule**: (in timezone Asia/Shanghai) - Branch creation - "before 10am on monday" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Enabled. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR was generated by [Mend Renovate](https://mend.io/renovate/). View the [repository job log](https://developer.mend.io/github/oxc-project/oxc). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4zNS40IiwidXBkYXRlZEluVmVyIjoiNDQuMzUuNCIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOltdfQ==--> --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Cameron <cameron.clark@hey.com> |
||
|
|
a185ac3ff3 |
fix(codegen): print # before private identifier in TS signature key (#25938)
`TSPropertySignature` and `TSMethodSignature` were printing a `PrivateIdentifier` key as `key.name`, dropping the `#`, so `interface I { #x: number }` was printed as `interface I { x: number }`.
This PR switches `oxc_codegen` to delegating to the `PrivateIdentifier` printer, as every other property key site already does. It also applies the same thing to JS printer `oxc-codegen`.
This is not legal syntax anyway - TS rejects it with TS18016, and Oxc parser with "Private identifier '#x' is not allowed in property names" - so this is only reachable from a recovered parse or a hand-built AST. But, since we have this code, it might as well do what you'd expect it to.
|
||
|
|
0389c4010e |
release(crates): oxc v0.146.0 (#25912)
### 🚀 Features - |
||
|
|
4a4a1fb2e7 |
release(crates): oxc v0.145.0 (#25865)
### 💥 BREAKING CHANGES - |
||
|
|
8ab883afaa |
fix(codegen): preserve property key annotations (#25766)
`@__KEY__` and `#__KEY__` are currently treated as normal comments. Codegen can drop them before a later property-mangling pass. The property may then be renamed while the annotated string keeps its old name.
This classifies the marker as a property-key annotation. Codegen keeps it next to string and no-substitution template literals when annotation comments are enabled, and the property mangler reads that classification instead of scanning source text. Final output still follows the normal comment options, so whitespace-minified or annotation-disabled output removes the marker.
## Example
Input:
```js
const object = { _field: 1 };
const key = /* #__KEY__ */ "_field";
object[key];
```
Before, an intermediate codegen pass could remove the marker:
```js
const object = { _field: 1 };
const key = "_field";
object[key];
```
After, the marker survives for the later property-mangling pass.
Verified with the affected crate suites, `just minsize`, workspace lint, docs, AST generation, and independent review. `just ready` is only blocked locally by existing Node-version and terminal-color snapshot differences.
Implemented with AI assistance (OpenAI Codex). I reviewed and understand the changes and remain responsible for this contribution under the repository AI usage policy.
|
||
|
|
c1369a71ff |
fix(codegen): resolve private names in class heritage (#25588)
### Summary
Fix private-member mangling for class heritage expressions.
The code generator previously activated a class's private-member mapping before emitting that class's decorators and `extends` expression. A private identifier in those expressions is evaluated in the enclosing lexical class scope, not the class currently being declared.
This change:
- reserves each class's mapping ID in AST traversal order;
- activates that mapping only immediately before emitting its class body;
- adds a mangler/codegen regression test covering a nested class with identically named private members.
### Reproduction
```js
class HasOuterBrand {}
class MissingOuterBrand {}
class Outer {
#x;
create(o) {
return class Inner extends (
#x in o ? HasOuterBrand : MissingOuterBrand
) {
#x;
};
}
}
```
`#x` in `Inner`'s heritage expression refers to `Outer.#x`. `Inner.#x` is not in scope until the `Inner` class body.
### Scope model
```text
Outer class body: active private mapping
Outer.#x -> #e
create(o) {
class Inner extends (#x in o ? ... : ...)
│
└─ resolves in Outer scope: #e
Inner class body: active private mapping
Inner.#x -> #t
}
```
### Before
Codegen entered `Inner` before emitting the entire class header:
```rust
p.enter_class();
p.print_decorators(&self.decorators, ctx);
...
heritage.expression.print_expr(...);
...
self.body.print(p, ctx);
```
That made private-identifier lookup prefer `Inner`'s mapping while printing `extends`, producing the wrong brand:
```js
return class t extends (#t in e ? HasOuterBrand : MissingOuterBrand) {
#t;
};
```
The `#t` in the heritage expression is invalid because `Inner.#t` is not yet in scope.
### After
The class ID is reserved first, preserving mapping IDs assigned in traversal order, but the class mapping is pushed only for the body:
```rust
let class_id = p.reserve_class();
p.print_decorators(&self.decorators, ctx);
...
heritage.expression.print_expr(...);
p.enter_class(class_id);
self.body.print(p, ctx);
p.exit_class();
```
```text
Class-ID allocation Active private-name scope
──────────────────── ─────────────────────────────
reserve Outer ID enclosing class / none
reserve nested header IDs enclosing class / none
enter Outer ID Outer body
enter Inner ID Inner body
```
Separating reservation from activation matters: moving allocation until after heritage could shift IDs for nested classes encountered in a heritage expression and break the mangler's precomputed mapping association.
### Regression coverage
The existing minifier mangler integration harness now snapshots this case with private-member mappings enabled. It confirms that the generated heritage expression uses Outer’s mapping (`#e`) while Inner’s field uses its distinct mapping (`#t`):
```js
class Outer {
#e;
create(e) {
return class t extends (#e in e ? HasOuterBrand : MissingOuterBrand) {
#t;
};
}
}
```
|
||
|
|
c002f29226 |
fix(codegen): escape sources for empty import specifiers (#25586)
## Summary
- Route empty named-import sources through the shared string-literal printer.
- Cover quote and newline escaping, including minified quote selection.
Given:
```ts
import {} from 'h"i'
```
Before it would be printed as:
```ts
import {} from "h"i" // missing escape of `"` or should be using `'` quotes
```
After:
```ts
import {} from 'h"i'
```
|
||
|
|
b4e6a9e717 |
fix(codegen): output newline after export default interface (#25487)
|
||
|
|
1645d93cfb |
fix(react_compiler): preserve source spans (#25462)
React Compiler rebuilt source-derived AST nodes with empty spans, so compiled function bodies had no useful source mappings. Preserve retained HIR and reactive spans during codegen while keeping synthetic memoization scaffolding unmapped. AI-assisted: implementation used Codex; the author remains responsible for review. |
||
|
|
b6f1458dec |
release(crates): oxc v0.144.0 (#25474)
### 💥 BREAKING CHANGES - |
||
|
|
030857b509 |
refactor(diagnostics): update oxc-miette to v4 (#25395)
Remove the miette `Report` wrapper and update oxc-miette to v4's narrowed diagnostic and source protocol. AI-assisted by OpenAI Codex. |
||
|
|
bbd6ed38df |
fix(codegen): wrap string literal in parens after directives (#25435)
When printing statements (`Codegen::print_directives_and_statements`), if first statement is an `ExpressionStatement` containing just a `StringLiteral`, it has to be parenthesized to avoid it becoming a directive.
Fix 4 different bugs in this logic.
## 1\. Only protected when there were no directives at all
The check was gated on `directives.is_empty()`, but the statement at risk is the first non-directive one, whatever its index. The second statement below printed bare, and re-parsing turned it into a directive - switching the program from sloppy to strict mode.
```js
// Input
"use server";
("use strict");
// Output, before
"use server";
"use strict"; // Now a directive - program is in strict mode
// Output, after this PR
"use server";
("use strict");
```
## 2\. No indent on the protected path
The path printed the statement itself instead of going through `ExpressionStatement`'s printer, and never printed the indent.
```js
// Output, before
function f() {
("x");
}
// Output, after this PR
function f() {
("x");
}
```
## 3\. Leading comments dropped on the protected path
Same cause - the statement's own printer is what prints its comments.
```js
// Input
// keep me
("use strict");
// Output, before
("use strict");
// Output, after this PR
// keep me
("use strict");
```
## 4\. Unprotected in minify mode
Protection was skipped entirely when minifying. A string usually minifies to a template literal, which cannot be a directive - but only where that is the shortest form. One backtick or `${` makes quotes shorter, and then it's printed as a directive.
```js
// Input
("`");
// Output, before
"`"; // Now a directive
// Output, after this PR
`\``;
```
Minified output is now always a template literal:
```js
("`"); // -> `\``;
("${}"); // -> `\${}`;
```
A template is never a directive, costs no more than `("...")`, and survives a round trip.
In the common case (no backticks or `${` in the string), printing as a template literal (`` `use strict`; ``) is more compact than a parenthesized quoted string (` ("use strict"); `).
Note: this visibly changes minified output for this construct - a template literal where there was a parenthesized string. Behavior is unchanged.
|
||
|
|
d07a293d12 |
fix(codegen): preserve cast parentheses in conditional tests (#25115)
## Summary - preserve parentheses around as and satisfies expressions used as conditional tests - prevent a following regular expression consequent from being parsed as part of the type expression - add regression coverage for both TypeScript expression forms Part of #25111. Closes #25114. |
||
|
|
8b7d4419ef |
fix(codegen): preserve satisfies parentheses before bitwise operators (#25113)
## Summary - preserve parentheses around a satisfies expression used as the left operand of bitwise OR or AND - prevent the operator from being consumed as part of the satisfies type - add regression coverage for both operators Part of #25111. Closes #25112. |
||
|
|
a33788e13e |
feat(ast)!: group class heritage into ClassHeritage (#25360)
## Summary
- Groups `Class::super_class` and `Class::super_type_arguments` into `Class::heritage`.
- Introduces `ClassHeritage` containing the superclass expression and its optional TypeScript type arguments.
- Makes superclass type arguments without a superclass expression unrepresentable.
## Breaking Change
The Rust AST changes from:
```rust
pub struct Class<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub r#type: ClassType,
pub decorators: Vec<'a, Decorator<'a>>,
pub id: Option<BindingIdentifier<'a>>,
#[ts]
pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
pub super_class: Option<Expression<'a>>,
#[ts]
pub super_type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
#[ts]
pub implements: Vec<'a, TSClassImplements<'a>>,
pub body: Box<'a, ClassBody<'a>>,
#[ts]
pub r#abstract: bool,
#[ts]
pub declare: bool,
pub scope_id: Cell<Option<ScopeId>>,
}
```
to:
```rust
pub struct Class<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub r#type: ClassType,
pub decorators: Vec<'a, Decorator<'a>>,
pub id: Option<BindingIdentifier<'a>>,
#[ts]
pub type_parameters: Option<Box<'a, TSTypeParameterDeclaration<'a>>>,
pub heritage: Option<ClassHeritage<'a>>,
#[ts]
pub implements: Vec<'a, TSClassImplements<'a>>,
pub body: Box<'a, ClassBody<'a>>,
#[ts]
pub r#abstract: bool,
#[ts]
pub declare: bool,
pub scope_id: Cell<Option<ScopeId>>,
}
pub struct ClassHeritage<'a> {
pub expression: Expression<'a>,
#[ts]
pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}
```
## Why?
ECMAScript defines the complete `extends` clause as `ClassHeritage`:
```text
ClassHeritage : extends LeftHandSideExpression
```
TypeScript additionally permits type arguments on the heritage expression:
```ts
class Derived extends Base<T> {}
```
Previously, the AST stored the expression and type arguments independently:
```text
Class {
super_class: Some(Base),
super_type_arguments: Some(<T>),
...
}
```
This allowed an invalid state to be constructed:
```text
Class {
super_class: None,
super_type_arguments: Some(<T>),
...
}
```
Superclass type arguments cannot exist without an `extends` expression.
The parser already treated these fields as a pair. It parsed a class heritage entry and then split its expression and type arguments into two independent fields. Builders and transforms therefore had to preserve their relationship manually.
Grouping them into `ClassHeritage` makes the syntactic dependency explicit and prevents consumers from observing or constructing contradictory class heritage.
## Class heritage forms
A class without an `extends` clause has no heritage:
```js
class Base {}
```
```text
Class {
heritage: None,
...
}
```
A class extending an expression has heritage without type arguments:
```js
class Derived extends Base {}
```
```text
Class {
heritage: Some(ClassHeritage {
expression: Base,
type_arguments: None,
}),
...
}
```
TypeScript type arguments belong to the same heritage structure:
```ts
class Derived extends Base<T> {}
```
```text
Class {
heritage: Some(ClassHeritage {
expression: Base,
type_arguments: Some(<T>),
}),
...
}
```
The heritage expression may be any valid class heritage expression:
```js
class Derived extends mixin(Base) {}
class NullDerived extends null {}
```
## How to migrate
Code that previously accessed the superclass expression directly:
```rust
if let Some(super_class) = &class.super_class {
// ...
}
```
can use the compatibility accessor:
```rust
if let Some(super_class) = class.super_class() {
// ...
}
```
Code that needs both parts should access the grouped heritage:
```rust
if let Some(heritage) = &class.heritage {
let expression = &heritage.expression;
let type_arguments = heritage.type_arguments.as_deref();
}
```
Mutating consumers should update the nested fields:
```rust
if let Some(heritage) = &mut class.heritage {
heritage.type_arguments = None;
}
```
Common migrations:
| Previous representation | New representation |
| --- | --- |
| `class.super_class` | `class.heritage.as_ref().map(\|h\| &h.expression)` or `class.super_class()` |
| `class.super_type_arguments` | `class.heritage.as_ref()?.type_arguments` or `class.super_type_arguments()` |
| `class.super_class.is_some()` | `class.heritage.is_some()` |
| Mutate `class.super_class` | Mutate `class.heritage.expression` |
| Clear `class.super_type_arguments` | Clear `class.heritage.type_arguments` |
| Pass superclass and type arguments separately to a `Class` builder | Construct and pass `ClassHeritage` |
| Clone both fields independently | Clone `class.heritage` |
```ts
superClass: Expression | null;
superTypeArguments?: TSTypeParameterInstantiation | null;
```
No `heritage` property or `ClassHeritage` node is added to the ESTree output.
fixes https://github.com/oxc-project/backlog/issues/216
|
||
|
|
5c5cdcd522 |
feat(ast)!: narrow TSInterfaceHeritage::expression to TSTypeName (#24360)
## 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
|
||
|
|
44fd320324 |
feat(ast)!: split TS external modules & Namespace Declarations (#25284)
## Summary
- Splits `TSModuleDeclaration` into two dedicated declaration nodes:
- `TSExternalModuleDeclaration` for string-literal modules and module augmentations.
- `TSNamespaceDeclaration` for identifier-based `module` and `namespace` declarations.
- Removes `TSModuleDeclaration`, `TSModuleDeclarationName`, `TSModuleDeclarationBody`, and `TSModuleDeclarationKind`.
- Makes invalid combinations of names, bodies, and declaration kinds unrepresentable.
- Ensures only namespace declarations introduce a normal binding.
- Updates all AST consumers, generators, traversal, formatting, code generation, semantic analysis, lint rules, transforms, and serialization.
- Keeps normal and eager-raw ESTree output compatible: both declarations remain `TSModuleDeclaration` nodes.
- Allows experimental lazy raw deserialization to expose the two new node types directly.
## Breaking Change
The Rust AST changes from:
```rust
pub enum Declaration<'a> {
VariableDeclaration(Box<'a, VariableDeclaration<'a>>) = 32,
FunctionDeclaration(Box<'a, Function<'a>>) = 33,
ClassDeclaration(Box<'a, Class<'a>>) = 34,
TSTypeAliasDeclaration(Box<'a, TSTypeAliasDeclaration<'a>>) = 35,
TSInterfaceDeclaration(Box<'a, TSInterfaceDeclaration<'a>>) = 36,
TSEnumDeclaration(Box<'a, TSEnumDeclaration<'a>>) = 37,
TSModuleDeclaration(Box<'a, TSModuleDeclaration<'a>>) = 38,
TSGlobalDeclaration(Box<'a, TSGlobalDeclaration<'a>>) = 39,
TSImportEqualsDeclaration(Box<'a, TSImportEqualsDeclaration<'a>>) = 40,
}
pub struct TSModuleDeclaration<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub id: TSModuleDeclarationName<'a>,
pub body: Option<TSModuleDeclarationBody<'a>>,
pub kind: TSModuleDeclarationKind,
pub declare: bool,
pub scope_id: Cell<Option<ScopeId>>,
}
```
to:
```rust
pub enum Declaration<'a> {
VariableDeclaration(Box<'a, VariableDeclaration<'a>>) = 32,
FunctionDeclaration(Box<'a, Function<'a>>) = 33,
ClassDeclaration(Box<'a, Class<'a>>) = 34,
TSTypeAliasDeclaration(Box<'a, TSTypeAliasDeclaration<'a>>) = 35,
TSInterfaceDeclaration(Box<'a, TSInterfaceDeclaration<'a>>) = 36,
TSEnumDeclaration(Box<'a, TSEnumDeclaration<'a>>) = 37,
TSExternalModuleDeclaration(Box<'a, TSExternalModuleDeclaration<'a>>) = 38,
TSNamespaceDeclaration(Box<'a, TSNamespaceDeclaration<'a>>) = 39,
TSGlobalDeclaration(Box<'a, TSGlobalDeclaration<'a>>) = 40,
TSImportEqualsDeclaration(Box<'a, TSImportEqualsDeclaration<'a>>) = 41,
}
pub struct TSExternalModuleDeclaration<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub id: StringLiteral<'a>,
pub body: Option<Box<'a, TSModuleBlock<'a>>>,
pub declare: bool,
pub scope_id: Cell<Option<ScopeId>>,
}
pub struct TSNamespaceDeclaration<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub id: BindingIdentifier<'a>,
pub body: TSNamespaceDeclarationBody<'a>,
pub kind: TSNamespaceDeclarationKind,
pub declare: bool,
pub scope_id: Cell<Option<ScopeId>>,
}
pub enum TSNamespaceDeclarationBody<'a> {
TSNamespaceDeclaration(Box<'a, TSNamespaceDeclaration<'a>>) = 0,
TSModuleBlock(Box<'a, TSModuleBlock<'a>>) = 1,
}
pub enum TSNamespaceDeclarationKind {
Module = 0,
Namespace = 1,
}
```
The corresponding inherited `Statement` variants, `AstKind` variants, builders, and visitor methods have also changed.
## Why?
`TSModuleDeclaration` previously represented two declaration families with different syntax and semantics:
```ts
declare module "foo" {}
declare module "*.css";
module Foo {}
namespace Foo {}
namespace Foo.Bar {}
```
The shared representation allowed invalid states such as:
- A namespace with a string-literal name.
- An external module with an identifier name.
- An identifier namespace without a body.
- A dotted external module.
- An external module containing another module declaration as its body.
- A declaration whose `kind` disagreed with its name.
Consumers also had to inspect `id` before determining whether the node introduced a binding or represented an external module.
The two declaration families have stronger independent invariants:
### External modules
- Always have a string-literal name.
- Never introduce a normal binding identifier.
- May omit their body.
- Cannot use dotted names.
- Use module-body import and export rules.
### Namespace declarations
- Always have a binding identifier.
- Always have a body.
- May contain nested namespace declarations for dotted names.
- Explicitly preserve the `module` or `namespace` spelling.
- Introduce a namespace/module symbol.
- Use namespace-body restrictions.
Dedicated nodes encode these differences directly.
## Declaration forms
External modules use `TSExternalModuleDeclaration`:
```ts
declare module "foo" {}
declare module "*.css";
module "foo" {}
```
Identifier-based declarations use `TSNamespaceDeclaration`:
```ts
module Foo {}
namespace Foo {}
```
Dotted namespaces are represented as nested namespace declarations:
```ts
namespace Foo.Bar {}
```
```rust
TSNamespaceDeclaration {
id: Foo,
body: TSNamespaceDeclarationBody::TSNamespaceDeclaration(
TSNamespaceDeclaration {
id: Bar,
body: TSNamespaceDeclarationBody::TSModuleBlock(...),
// ...
}
),
// ...
}
```
Only `TSExternalModuleDeclaration::body` is optional.
## ESTree compatibility
Normal serialization and eager raw deserialization continue to emit the existing ESTree shape:
```ts
interface TSModuleDeclaration {
type: "TSModuleDeclaration";
id: BindingIdentifier | StringLiteral | TSQualifiedName;
body: TSModuleBlock | null;
kind: "module" | "namespace";
declare: boolean;
global: false;
}
```
Dotted namespaces continue to serialize as a single `TSModuleDeclaration` with a `TSQualifiedName`.
Experimental lazy raw deserialization is not compatibility-preserving. It now exposes `TSExternalModuleDeclaration` and `TSNamespaceDeclaration` directly, with separate constructors, layouts, and visitor type IDs.
## How to migrate
Code that previously matched all forms through one variant:
```rust
if let Declaration::TSModuleDeclaration(declaration) = declaration {
// ...
}
```
should now match the required declaration family:
```rust
match declaration {
Declaration::TSExternalModuleDeclaration(declaration) => {
// String-literal external module
}
Declaration::TSNamespaceDeclaration(declaration) => {
// Identifier-based module or namespace
}
_ => {}
}
```
The same applies to `Statement` and `AstKind`:
```rust
Statement::TSExternalModuleDeclaration(declaration)
Statement::TSNamespaceDeclaration(declaration)
AstKind::TSExternalModuleDeclaration(declaration)
AstKind::TSNamespaceDeclaration(declaration)
```
Common field migrations:
| Previous representation | New representation |
| --- | --- |
| `TSModuleDeclarationName::StringLiteral(id)` | `TSExternalModuleDeclaration::id` |
| `TSModuleDeclarationName::Identifier(id)` | `TSNamespaceDeclaration::id` |
| `body: None` | Only valid on `TSExternalModuleDeclaration` |
| `TSModuleDeclarationBody::TSModuleBlock` | External `body` or `TSNamespaceDeclarationBody::TSModuleBlock` |
| `TSModuleDeclarationBody::TSModuleDeclaration` | `TSNamespaceDeclarationBody::TSNamespaceDeclaration` |
| `TSModuleDeclarationKind::Module` | External module, or `TSNamespaceDeclarationKind::Module` |
| `TSModuleDeclarationKind::Namespace` | `TSNamespaceDeclarationKind::Namespace` |
| Inspect `id` to determine binding behavior | Match `TSNamespaceDeclaration` |
| `declaration.id()` for string modules | Now returns `None` |
| `visit_ts_module_declaration` | `visit_ts_external_module_declaration` or `visit_ts_namespace_declaration` |
When constructing nodes, use the generated `TSExternalModuleDeclaration` or `TSNamespaceDeclaration` builders rather than recreating the previous shared representation.
fixes https://github.com/oxc-project/backlog/issues/218
|
||
|
|
4d723caaff |
fix(parser): report TS1035 for quoted module names (#25305)
Quoted module names are only valid in ambient contexts in TypeScript, but we were accepting them in regular TypeScript files. This reports TS1035 when a quoted module name appears outside a declaration file or ambient declaration. The conformance snapshots now cover both TypeScript cases, and the old invalid codegen fixture has been replaced with a valid internal module declaration. |
||
|
|
45a17c25d1 |
release(crates): oxc v0.143.0 (#25250)
### 💥 BREAKING CHANGES - |
||
|
|
067da8c4e7 |
feat(ast)!: store single parameter in TSIndexSignature::parameter (#25154)
|
||
|
|
1bdedd11ca |
feat(ast)!: introduce ExportDeclaration, ExportFromDeclaration (#25095)
## Summary
Split the overloaded Rust `ExportNamedDeclaration` into syntax-specific AST nodes:
- `ExportDeclaration` for exported declarations
- `ExportNamedDeclaration` for local exports
- `ExportFromDeclaration` for re-exports
## Breaking Change
The Rust AST changes from:
```rust
pub struct ExportNamedDeclaration<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub declaration: Option<Declaration<'a>>,
pub specifiers: Vec<'a, ExportSpecifier<'a>>,
pub source: Option<StringLiteral<'a>>,
pub export_kind: ImportOrExportKind,
pub with_clause: Option<Box<'a, WithClause<'a>>>,
}
pub enum ModuleDeclaration<'a> {
// ...
ExportNamedDeclaration(Box<'a, ExportNamedDeclaration<'a>>),
// ...
}
```
to:
```rust
pub struct ExportDeclaration<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub declaration: Declaration<'a>,
}
pub struct ExportNamedDeclaration<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub specifiers: Vec<'a, ExportSpecifier<'a>>,
pub export_kind: ImportOrExportKind,
}
pub struct ExportFromDeclaration<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub specifiers: Vec<'a, ExportSpecifier<'a>>,
pub source: StringLiteral<'a>,
pub export_kind: ImportOrExportKind,
pub with_clause: Option<Box<'a, WithClause<'a>>>,
}
pub enum ModuleDeclaration<'a> {
// ...
ExportDeclaration(Box<'a, ExportDeclaration<'a>>),
ExportNamedDeclaration(Box<'a, ExportNamedDeclaration<'a>>),
ExportFromDeclaration(Box<'a, ExportFromDeclaration<'a>>),
// ...
}
```
## Why?
The previous node represented three distinct syntax forms through optional and mutually exclusive fields:
```text
export const value = 1; -> ExportDeclaration
export { value }; -> ExportNamedDeclaration
export { value } from "mod"; -> ExportFromDeclaration
```
This allowed invalid combinations such as declarations with specifiers or sources, and import attributes without a source. The new nodes make these states unrepresentable and remove repeated `declaration`/`source` branching from consumers.
## How to migrate
Match the syntax-specific `ModuleDeclaration`, `Statement`, or `AstKind` variant instead of inspecting optional fields. Generated builders, visitors, traversal ancestors, and formatter wrappers now expose corresponding APIs for all three nodes.
Common field migrations:
| Previous representation | New representation |
| --- | --- |
| `declaration: Some(declaration)` | `ExportDeclaration { declaration }` |
| `declaration.as_ref()` | `&export.declaration` |
| `source.is_none()` for local exports | Match `ExportNamedDeclaration` |
| `source: Some(source)` | `ExportFromDeclaration { source }` |
| `source.as_ref().unwrap()` | `&export.source` |
| `with_clause` | Only available on `ExportFromDeclaration` |
| Stored `export_kind` on exported declarations | `ExportDeclaration::export_kind()` |
| `ExportNamedDeclaration::boxed_plain_declaration(...)` | `ExportDeclaration::boxed(...)` |
| `ExportNamedDeclaration::boxed(..., None, specifiers, None, ...)` | `ExportNamedDeclaration::boxed(..., specifiers, ...)` |
| `ExportNamedDeclaration::boxed(..., None, specifiers, Some(source), ...)` | `ExportFromDeclaration::boxed(..., specifiers, source, ...)` |
closes https://github.com/oxc-project/backlog/issues/219
|
||
|
|
c917f204a7 |
feat(ast)!: introduce ArrowFunctionBody enum (#24987)
## Summary
- Introduces `ArrowFunctionBody` to represent concise expression and block bodies explicitly.
- Removes the `ArrowFunctionExpression::expression` boolean.
- Makes invalid arrow body combinations unrepresentable.
## Breaking Change
The Rust AST changes from:
```rust
pub struct ArrowFunctionExpression<'a> {
pub expression: bool,
pub body: Box<'a, FunctionBody<'a>>,
}
```
to:
```rust
pub enum ArrowFunctionBody<'a> {
FunctionBody(Box<'a, FunctionBody<'a>>) = 64,
INHERIT(Expression<'a>),
}
pub struct ArrowFunctionExpression<'a> {
pub body: ArrowFunctionBody<'a>,
}
```
Concise arrow bodies are now stored directly as expressions instead of synthetic `FunctionBody` and `ExpressionStatement` nodes. Block bodies continue to use `FunctionBody`.
## Why?
The previous representation required consumers to maintain this implicit invariant:
```text
expression == true
→ no directives
→ exactly one statement
→ that statement is an ExpressionStatement
```
Invalid combinations could cause panics, skipped work, or fallback behavior. The enum encodes the grammar directly and makes those invalid states unrepresentable.
Concise expressions also now have `ArrowFunctionExpression` as their direct traversal ancestor, without synthetic `FunctionBody` or `ExpressionStatement` nodes.
## How to migrate
Use the arrow body accessors or match on `ArrowFunctionBody` rather than inspecting `expression` and synthetic statements.
Common field migrations:
| Previous representation | New representation |
| --- | --- |
| `arrow.expression` | `arrow.is_expression()` |
| `arrow.get_expression()` | `arrow.get_expression()` |
| `&arrow.body.statements` | `arrow.get_function_body().map(\|body\| &body.statements)` |
| `&mut arrow.body.statements` | `arrow.get_function_body_mut().map(\|body\| &mut body.statements)` |
| Synthetic expression statement in `arrow.body` | Direct `ArrowFunctionBody` expression variant |
| `arrow.body = function_body` | `arrow.body = ArrowFunctionBody::FunctionBody(function_body)` |
| Expression-to-block conversion | Create a `FunctionBody` containing a return statement |
| Block-to-expression conversion | Assign the expression directly to `arrow.body` |
| Builder `(expression, body)` arguments | Pass a single `ArrowFunctionBody` |
closes https://github.com/oxc-project/backlog/issues/211
|
||
|
|
fc702c1fa9 |
release(crates): oxc v0.142.0 (#24977)
### 💥 BREAKING CHANGES - |
||
|
|
dba374912a |
refactor(ecmascript): share number literal serialization (#24876)
Codegen is not the only consumer that needs the exact emitted form of a number, but the serializer currently lives in the codegen crate. Other consumers would otherwise need to duplicate its formatting policy or depend on codegen. Move the callback helper extracted in #24888 from `oxc_codegen` to `oxc_ecmascript`, then reuse it from codegen. The serialization algorithm is unchanged; this PR only makes the helper public and relocates its dependencies. This PR depends on #24888. #24889 handles allocation-free candidate construction, and #24875 adds the minifier consumer. Verified independently with `cargo test -p oxc_ecmascript -p oxc_codegen`, Clippy, and formatting. AI assistance: Codex was used to implement and verify this change. |
||
|
|
2ee6e7a759 |
refactor(codegen): extract number literal serialization (#24888)
Number literal serialization is coupled to `Codegen`, even though the formatter produces one string before codegen writes it. That coupling makes the formatting policy difficult to reuse without mixing a move with an implementation rewrite. Extract the existing algorithm into a codegen-local callback helper. Generated output and allocation behavior are unchanged. This is the first PR in the stack. #24876 moves the helper unchanged into `oxc_ecmascript`. Verified with `cargo test -p oxc_codegen`, Clippy, formatting, and the combined stack with `just ready`. AI assistance: Codex was used to implement and verify this change. |
||
|
|
0126aba1d9 |
fix(codegen): preserve orphaned file coverage comments (#24815)
File-level V8 and Istanbul coverage directives attach to the first AST statement. When TypeScript import removal or another upstream pass deletes that statement, codegen never visits the comment attachment position and silently drops the directive. Coverage tooling then instruments a file that was explicitly excluded.
The lexer now classifies `v8 ignore file` and `istanbul ignore file` separately, including line-comment forms and explanatory suffixes. Codegen lets those directives share the existing legal-comment orphan flush, preserving source order without scanning source text during printing. Position-sensitive directives such as `ignore next`, `start`, and `stop` remain unchanged.
The recovery path is named and documented around orphaned attachments. Hashbang ordering remains intact, and disabling annotation comments still suppresses these directives. The transformer regression stays in the Rust integration harness because transform conformance deliberately clears comments before codegen.
## Example
Input:
```ts
/* v8 ignore file */
import type { Foo } from "./types";
export default { typed: "stuff" } satisfies Foo;
```
Before:
```js
export default { typed: "stuff" };
```
After:
```js
/* v8 ignore file */
export default { typed: "stuff" };
```
Covered by direct codegen regressions for removed anchors, hashbang ordering, disabled annotation comments, and position-sensitive negative cases, plus a TypeScript transformer integration test. Verified with `env -u NO_COLOR just ready`, including all-feature tests, linting, documentation, generated-code checks, formatting, and a clean-tree check.
closes #23667
This PR was assisted by OpenAI Codex.
|
||
|
|
baf022d74d |
refactor(ast)!: remove old AstBuilder methods (#24563)
Closes the migration tracked in #23043. The new builder API (methods on the AST types themselves) has been in place for a while, all Oxc crates are migrated, and every crate which uses `AstBuilder` already enables `disable_old_builder` in its tests. The module docs said "after a few weeks, we will remove `AstBuilder`'s own methods entirely" — this does that. ## What's removed - `crates/oxc_ast/src/generated/ast_builder.rs` and `builder/methods.rs` — `AstBuilder`'s own node and primitive constructors. - `AstBuilderGenerator`, which produced the former. - The `disable_old_builder` Cargo feature, plus the dev-dependency hacks it required across 33 manifests. Eight crates depended on `oxc_ast` *purely* to turn the feature on; those deps and their `cargo-shear` ignore entries are gone too. ## One thing worth reviewing `AstBuilderGenerator` owned the `#[builder(default)]` / `#[builder(skip)]` attr registration that `BuilderMethodsGenerator` only *reads* — its doc comment said as much. Deleting it naively would have silently dropped `skip`/`default` handling for the 12 AST types using those attrs, so `attrs()` and `parse_attr()` move to `BuilderMethodsGenerator`. The evidence this was faithful: after regenerating, `builder_methods.rs` and every other generated file are **byte-identical**. The only generated change in the diff is the deletion. ## Breaking changes - `AstBuilder`'s own methods are gone. Use the AST type methods: `Statement::new_expression_statement(span, expr, &builder)` instead of `builder.statement_expression(span, expr)`. - `AstBuilder` is no longer `Copy`/`Clone`, and its `allocator` field is private — use the `allocator` method from `GetAllocator`. - `AstBuilder` and `NONE` are no longer re-exported from the crate root; import them from `oxc_ast::builder`. (The `disable_old_builder` feature already gated these off, so nothing in-tree used the root path.) - The `disable_old_builder` feature no longer exists — remove it from any manifest that sets it. A migration note covering all of the above is in `crates/oxc_ast/src/builder/mod.rs`. --- Implemented with AI assistance (Claude Code); design, review, and verification driven by the author per the repo AI-usage policy. |
||
|
|
14533a3dc1 |
release(crates): oxc v0.141.0 (#24714)
### 💥 BREAKING CHANGES - |
||
|
|
6cc4a34307 |
fix(codegen): wrap negative BigInt literal base of ** in parentheses (#23621)
### Summary
Fixes invalid codegen output where a negative `BigInt` literal used as
the base of an exponentiation (`**`) expression was emitted without the
required parentheses, producing code that the parser rejects.
### Problem
Minifying `x = (0n + -1n) ** 2n;` folds `0n + -1n` into a single
negative `BigIntLiteral` and then prints `x = -1n ** 2n;`, which is a
`SyntaxError` ("Unexpected exponentiation expression") — the same
grammar restriction that makes `-1 ** 2` illegal. The equivalent number
case already prints correctly as `x = (-1) ** 2;`.
### Root cause
In `crates/oxc_codegen/src/binary_expr_visitor.rs`, the `**` base
special-case raises the left operand precedence to `Precedence::Call`
(forcing parentheses) for `UnaryExpression`, `AwaitExpression`,
`TSTypeAssertion` and `NumericLiteral`, but omitted `BigIntLiteral`,
which also prints with a leading `-` for negative values. So a negative
BigInt base kept `left_precedence = Exponentiation` and was emitted
without parentheses.
### Fix
Add `BigIntLiteral` to that match so a negative BigInt base is
parenthesized like a negative number base. A positive BigInt base (e.g.
`2n ** 3n`) is unaffected and not over-parenthesized.
### Testing
- Added regression test `test_exponentiation_negative_bigint_base` in
`crates/oxc_minifier/tests/peephole/obscure_edge_cases.rs` (a negative
BigInt literal only arises from constant folding, not from parsing).
- `cargo test -p oxc_codegen` passes; the only failing snapshot,
`sourcemap::stacktrace_is_correct`, is a pre-existing Node.js version
mismatch unrelated to this change.
|
||
|
|
b28051fb12 |
refactor(all): pass [v] for single-value ArenaVec builder args (#24623)
Pure refactor. Just shorten code. #24621 made AST builder methods take arrays as params. Replace `ArenaVec::from_value_in(v, alloc)` with `[v]` where the `ArenaVec` is passed to an AST builder method. ```rs let block = Statement::new_block_statement( SPAN, ArenaVec::from_value_in(return_stmt, ctx), ctx, ); ``` \-> ```rs let block = Statement::new_block_statement(SPAN, [return_stmt], ctx); ``` |
||
|
|
c1a074cdc8 |
refactor(all): pass [] for empty ArenaVec args to AST builder methods (#24622)
Pure refactor. Just shorten code. #24621 made AST builder methods take arrays as params. Replace `ArenaVec::new_in(...)` arguments written inline at AST builder method call sites with the empty array literal `[]`. ```rs let block = Statement::new_block_statement(SPAN, ArenaVec::new_in(ctx), ctx); ``` \-> ```rs let block = Statement::new_block_statement(SPAN, [], ctx); ``` |
||
|
|
70994ae544 |
fix(codegen): preserve comments before expression operands (#24510)
Coverage directives attached to expression operands were silently dropped by codegen in logical right-hand sides, conditional consequents, object property values, and template literal interpolations. The AST expressions survive, but those emitters never consume their leading comment groups. Print the comment group at each surviving expression boundary, probing parenthesized layers (`a || /* c */ (x)` anchors the comment at the `(`, `a || (/* c */ x)` at `x`). Parenthesized function and arrow IIFEs keep comments inside their generated wrapper. Logical right-hand sides and conditional consequents only print groups containing annotations. This is codegen policy for mutated ASTs: comments are anchored by source position, and mutating consumers (minifier statement merging, rolldown finalization) move statements into exactly these positions — a dissolved statement's leading normal-comment group can coincide with the moved operand's span start, and printing it there misplaces statement-level trivia and breaks minify-twice idempotency (test262 `language/asi/S7.9_A5.8_T1.js` and friends). Annotations are the one comment kind with expression-level meaning, so they pass through. Printing comments at expression boundaries also exposed three latent spacing bugs in the shared comment printer, surfaced by monitor-oxc over real node_modules: a full indent was injected before mid-line groups (doubling indentation on every pass, invisible at indent 0), a group ending in a newline left the next operand at column 0, and minify-with-comments mode emitted a glue space or not depending on which printer consumed the group. All three are fixed with regression tests. ## Example Input: ```js const value = a ?? /* istanbul ignore next */ []; ``` Before: ```js const value = a ?? []; ``` After: ```js const value = a ?? /* istanbul ignore next */ []; ``` Verified with `cargo test -p oxc_codegen` (134 tests), full `cargo coverage` (minifier at baseline, zero snapshot drift), `--twice` idempotency over webpack/typescript/eslint-utils/storybook/sigstore files in plain, dce, and minify-with-comments modes, and monitor-oxc green on codegen/dce/whitespace/transformer (remaining failures are main's pre-existing baseline). closes #21301 closes #22049 closes #24324 This PR was assisted by OpenAI Codex and Claude. |
||
|
|
54cc121250 |
feat(ast)!: split MetaProperty into ImportMeta and NewTarget (#24557)
## Summary
- replace the generic Rust MetaProperty AST node with dedicated ImportMeta and NewTarget nodes
- preserve the ESTree MetaProperty shape and generated public TypeScript types
- update parser, formatter, codegen, lint, transform, minifier, traversal, and React compiler consumers
## Summary
Remove `MetaProperty` AST node, in favour of two separate nodes (`ImportMeta` and `NewTarget`)
## Breaking Change
The Rust AST field changes from:
```rust
pub struct MetaProperty<'a> {
pub node_id: Cell<NodeId>,
pub span: Span,
pub meta: IdentifierName<'a>,
pub property: IdentifierName<'a>,
}
```
to:
```rust
/// /// `import.meta` in `console.log(import.meta);`.
pub struct ImportMeta {
pub node_id: Cell<NodeId>,
pub span: Span,
}
/// `new.target` in `function F() { return new.target; }`.
pub struct NewTarget {
pub node_id: Cell<NodeId>,
pub span: Span,
}
```
## Why?
The current AST could represent shapes such as:
```
foo.bar
```
By constructing the node manually. However this doesn't make sense, and is invalid.
## How to migrate?
1. Replace any `Expression::MetaProperty` matches/checks with the dedicated `Expression::ImportMeta` and `Expression::NewTarget` varients
2. Replace any `new_meta_property` construction with the new `new_import_meta` or `new_new_target` APIs.
3. Inspecting the `meta`/`property` identifier names is no longer required.
|