Commit Graph

220 Commits

Author SHA1 Message Date
Boshen 00e1b762e0 refactor(diagnostics): vendor oxc-miette (#25499)
We no longer need to maintain the `oxc-miette` repo as most of the code is now removed, the remaining code is minimal and can be vendored.
2026-08-11 08:04:02 +00:00
Boshen 2fe0a0dfd4 fix(react_compiler): preserve caught property loads (#25498)
Preserve catch-observable property and computed loads during dead-code elimination, and record their root operands as reactive-scope dependencies so throwing reads remain inside `try`.

This prevents memoization from suppressing catch behavior or hoisting potentially throwing property access into cache guards.

Validated with the React Compiler crate suite, NAPI transform suite, and original-vs-compiled runtime cases.

AI assistance was used to investigate and implement this change.
2026-08-11 07:15:55 +00:00
Boshen c3a9731624 fix(react_compiler): preserve underscore-prefixed JSX components (#25496)
Preserve JSX component classification through React Compiler HIR and codegen so underscore-prefixed components are emitted as identifiers instead of intrinsic string tags.

Update the existing context-variable JSX fixture to cover `_Component`.

Validated with the React Compiler crate suite, NAPI suite, and all six ecosystem reproductions.

AI assistance was used.
2026-08-11 06:53:51 +00:00
Boshen 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.
2026-08-11 02:10:30 +00:00
Boshen 93c83d3d9b fix(react_compiler): model tagged templates as calls (#25485)
Model tagged-template expressions as function applications during mutation and alias inference. This keeps mutations of returned objects within the memoization scope and preserves the implicit template argument position.

Checked with `cargo test -p oxc_react_compiler` and the transform-react NAPI suite.

AI-assisted.
2026-08-10 15:43:19 +00:00
Boshen 8d7f9cff3f fix(react_compiler): honor eslint suppressions (#25394)
Honor React ESLint suppressions independently of internal compiler validation settings, matching `babel-plugin-react-compiler@1.0.0`.

Closes #25392.

AI-assisted.
2026-08-10 10:42:23 +00:00
Boshen 2e35238d65 fix(react-compiler): compile forwardRef callbacks with non-ref param names (#25468)
The discovery walker let a declarator name flow through `forwardRef`/`memo` calls, so `const Foo = forwardRef((props, viewTracker) => ...)` was treated as a named component and rejected by the component param check (a second param must look like a ref). Upstream only names a function from its direct parent (declarator/assignment/property); a function in call arguments is anonymous and is detected through the forwardRef/memo callback branch, which never checks params.

Fixes the two Simorgh files from the ecosystem comparison (`TopStoriesItem`, `LatestMediaItem`): both now compile with output matching upstream, including identical memo cache sizes. Adds a fixture for the pattern; no existing fixture snapshots change.

Created with assistance from Claude.
2026-08-10 08:54:59 +00:00
Boshen 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.
2026-08-10 07:48:02 +00:00
Boshen fa7fdd2123 fix(react-compiler): compile parenthesized arrow components (#25463)
Compile parenthesized arrow components by preserving declarator name inference through nested parentheses.

Adds a React Compiler fixture for the ecosystem reproduction.

Created with assistance from Codex.
2026-08-10 07:30:16 +00:00
Boshen 28c2e09838 feat(react_compiler): recognize oxlint suppressions (#25456)
Recognize `oxlint-disable*` and `oxlint-enable` comments wherever React Compiler accepts their ESLint-prefixed equivalents.

AI-assisted.
2026-08-10 05:19:14 +00:00
Boshen e3683f29d8 fix(react_compiler): materialize compound assignment reads (#25450)
Materializes identifier reads before compound-assignment binary operations so constant propagation matches the upstream React Compiler. AI-assisted.
2026-08-09 16:25:11 +00:00
Boshen 92426aefeb fix(react_compiler): align nested ref validation (#25427)
Align ref validation with Babel React Compiler 1.0 by limiting call-site alias-effect checks to values actually passed to the call. This keeps refs captured by returned event handlers valid while preserving diagnostics for refs passed to invoked functions.

Validated with `cargo test -p oxc_react_compiler`, the rebuilt `oxc-transform-react` binding against Outline’s `ContentEditable.tsx`, and `just ready`.

AI-assisted; the contributor remains responsible for review and submission.
2026-08-09 13:15:18 +00:00
Boshen 937825c6a6 fix(react_compiler): disable exhaustive memo validation by default (#25417)
Disable exhaustive memo dependency validation by default and align
suppression handling.

AI-assisted.

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-09 11:18:12 +08:00
Boshen ad4479ea28 test(react_compiler): cover manual memo semantics (#25415)
Cover manual memoization semantics.

AI-assisted.

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-08-09 11:18:12 +08:00
Boshen 7d8a9b5f2f fix(react_compiler): preserve hook spans for React Refresh (#25393)
Fix React Refresh when used with the React Compiler N-API transform by
preserving original hook argument and binding-pattern spans through
compiler codegen.

This prevents refresh signature generation from panicking and keeps
signatures based on the original source.

Validated with `just ready` and the React transform N-API suite.

Generated with AI assistance; reviewed and validated by the contributor.
2026-08-08 08:46:36 +00:00
Boshen 4a6542bfda chore(react_compiler): configure package publishing (#25389)
Prevents the internal `oxc_react_compiler` and
`oxc_transform_react_napi` Rust crates from being published while
including the `oxc-transform-react` npm package in workspace releases.

Validated with `taplo lint`, Cargo metadata, and `just ready`.

AI assistance was used for this change.
2026-08-08 08:05:28 +00:00
camc314 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
2026-08-07 14:18:59 +00:00
camc314 6be314f226 feat(ast)!: remove duplicated VariableDeclarator::kind (#25319)
## Summary

- Removes `VariableDeclarator::kind`.
- Makes `VariableDeclaration::kind` the single source of truth for every declarator in its declaration list.
- Makes contradictory parent and child declaration kinds unrepresentable.

## Breaking Change

The Rust AST changes from:

```rust
pub struct VariableDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub kind: VariableDeclarationKind,
    pub declarations: Vec<'a, VariableDeclarator<'a>>,
    #[ts]
    pub declare: bool,
}

pub struct VariableDeclarator<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    #[estree(skip)]
    pub kind: VariableDeclarationKind,
    #[estree(via = VariableDeclaratorId)]
    pub id: BindingPattern<'a>,
    #[ts]
    #[estree(skip)]
    pub type_annotation: Option<Box<'a, TSTypeAnnotation<'a>>>,
    pub init: Option<Expression<'a>>,
    #[ts]
    pub definite: bool,
}
```

to:

```rust
pub struct VariableDeclaration<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    pub kind: VariableDeclarationKind,
    pub declarations: Vec<'a, VariableDeclarator<'a>>,
    #[ts]
    pub declare: bool,
}

pub struct VariableDeclarator<'a> {
    pub node_id: Cell<NodeId>,
    pub span: Span,
    #[estree(via = VariableDeclaratorId)]
    pub id: BindingPattern<'a>,
    #[ts]
    #[estree(skip)]
    pub type_annotation: Option<Box<'a, TSTypeAnnotation<'a>>>,
    pub init: Option<Expression<'a>>,
    #[ts]
    pub definite: bool,
}
```

## Why?

A variable declaration has one declaration kind shared by every declarator in its declaration list:

```js
const first = 1, second = 2;
```

Previously, the AST stored that kind on both the parent declaration and every child declarator:

```text
VariableDeclaration {
    kind: Const,
    declarations: [
        VariableDeclarator { kind: Const, ... },
        VariableDeclarator { kind: Const, ... },
    ],
}
```

This allowed contradictory ASTs to be constructed:

```text
VariableDeclaration {
    kind: Const,
    declarations: [
        VariableDeclarator { kind: Var, ... },
    ],
}
```

Different consumers read different copies:

- Code generation used `VariableDeclaration::kind`.
- Semantic binding used `VariableDeclarator::kind`.
- Lint rules, transforms, and minifier passes used a mixture of both.

As a result, one AST could be emitted as `const` while its binding was treated as function-scoped `var`.

Transforms also had to keep the copies synchronized manually. For example, const-to-let normalization updated the parent and then looped through every declarator to update each child. Explicit resource management performed similar paired mutations for `using` declarations.

Keeping the kind only on `VariableDeclaration` removes this synchronization requirement and prevents consumers from observing conflicting declaration semantics.

## Declaration forms

The declaration kind now belongs exclusively to the node that owns the declaration list:

```js
let value;
```

```text
VariableDeclaration {
    kind: Let,
    declarations: [
        VariableDeclarator { id: value, ... },
    ],
}
```

Multiple declarators share the same parent kind:

```js
const first = 1, second = 2;
```

```text
VariableDeclaration {
    kind: Const,
    declarations: [
        VariableDeclarator { id: first, ... },
        VariableDeclarator { id: second, ... },
    ],
}
```

## How to migrate

Code that previously read the kind from a declarator:

```rust
if declarator.kind.is_const() {
    // ...
}
```

should now read it from the containing declaration:

```rust
if declaration.kind.is_const() {
    // ...
}
```

Consumers operating through an AST node store should retrieve the parent `VariableDeclaration`:

```rust
let AstKind::VariableDeclaration(declaration) =
    nodes.parent_kind(declarator.node_id())
else {
    unreachable!();
};

let kind = declaration.kind;
```

Common migrations:

| Previous representation | New representation |
| --- | --- |
| `declarator.kind` | Containing `declaration.kind` |
| Match `VariableDeclarator::kind` during semantic binding | Read the parent `VariableDeclaration` from ancestry |
| Pass `kind` to `VariableDeclarator::new` | Remove the `kind` argument |
| Update parent and every child kind | Update only `VariableDeclaration::kind` |
| Recover kind after detaching a declarator | Capture or pass the parent kind before detaching it |
| Inspect a symbol’s declarator kind in lint rules | Resolve its parent declaration through the node store |

When constructing declarators, use the generated kindless `VariableDeclarator` builders and place them inside a `VariableDeclaration` carrying the required kind.

fixes https://github.com/oxc-project/backlog/issues/237
2026-08-05 16:31:46 +00:00
camc314 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
2026-08-05 14:37:35 +00:00
Boshen 495b02483d fix(react_compiler): preserve parenthesized non-node returns (#25289)
Preserve Babel's non-node classification for parenthesized object returns so ordinary PascalCase factories are not compiled as React components.

Validated against the React Compiler suite and the Kibana reproduction.

AI-assisted.
2026-08-05 01:38:14 +00:00
Boshen db407f61cc fix(react_compiler): discover functions in parameter defaults (#25287)
Fix React Compiler discovery of `memo` and `forwardRef` callbacks in runtime parameter defaults when the enclosing function is skipped.

Validated against the React Compiler suite and the Kibana reproduction.

AI-assisted.
2026-08-04 14:31:31 +00:00
oxc-guard 45a17c25d1 release(crates): oxc v0.143.0 (#25250)
### 💥 BREAKING CHANGES

- 4120da0 semantic: [**BREAKING**] Return borrowed JSDoc so the parse cache works (#25186) (Connor Shea)
- f0a41c8 allocator: [**BREAKING**] Remove `ArenaBox::dangling` method (#25236) (overlookmotel)
- 067da8c ast: [**BREAKING**] Store single parameter in `TSIndexSignature::parameter` (#25154) (camc314)
- a0c7788 ast: [**BREAKING**] Change `TSIndexSignatureName::name` to `Ident` (#25150) (camc314)
- 1bdedd1 ast: [**BREAKING**] Introduce `ExportDeclaration`, `ExportFromDeclaration` (#25095) (camc314)
- c917f20 ast: [**BREAKING**] Introduce `ArrowFunctionBody` enum (#24987) (camc314)
- 7e1199c ast: [**BREAKING**] Remove conversion to `Box` from AST builder methods (#25038) (overlookmotel)

### 🚀 Features

- 3c8312e allocator: Make `Box` `Send` + `Sync` when the type it contains is (#25242) (overlookmotel)
- cb5bc08 ast: Derive `GetSpan` for `Comment` (#25147) (leaysgur)
- 8541e18 ast_tools: Generate minifier scope collector (#25088) (camc314)
- 5acdae2 ecmascript: Skip transparent TS expressions (#25072) (camc314)
- 85d0f4d napi: Add React Compiler transform binding (#24934) (Boshen)
- 70d6ece minifier: Fold safe integer exponentiation (#25009) (Dunqing)
- 73fc747 minifier: Enable termination statement optimization for do while (#24921) (Armano)
- 5eadfdb minifier: Fold safe integer division and remainder (#24981) (Dunqing)

### 🐛 Bug Fixes

- 6c9ee14 minifier: Preserve if-chain idempotency and correct side effects when alternate should not be moved (#25041) (Armano)
- 8a47ff3 ecmascript: Do not treat lone-surrogate strings as constants (#25084) (Dunqing)
- 468b970 isolated-declarations: Strip re-export attributes (#25090) (camc314)
- a70035d parser: Report export imports in namespaces (#25086) (camc314)
- 5d8ccf5 react_compiler: Preserve nonfatal diagnostics (#25066) (Boshen)
- d349e4a react_compiler: Match eslint suppression gating (#25068) (Boshen)
- 0dc16ba react_compiler: Track catch block declarations (#25067) (Boshen)
- e6734a5 minifier: Fold typeof comparisons with known object strings (#25017) (Dunqing)
- 3a9d5a5 minifier: Keep `typeof foo == ['object']` as-is (#24941) (sapphi-red)
- 10ec276 ast: Custom AST builder methods take `IntoIn<ArenaBox>` where child method does (#25033) (overlookmotel)
- fbe8d31 minifier: Respect targets when folding RegExp source (#24790) (Dunqing)

### ⚡ Performance

- fb402b0 minifier: Stop re-deriving printed numeric spellings (#25103) (Dunqing)
- 7251e52 react_compiler: Avoid large types on stack (#25037) (overlookmotel)
- e07718f isolated_declarations: Avoid large types on stack (#25036) (overlookmotel)
- 9fb1e3e transformer: Avoid large types on stack (#25035) (overlookmotel)
- 8b80f8b parser: Avoid large types on stack (#25034) (overlookmotel)

### 📚 Documentation

- 2ea0aa9 ast: Correct grammar in AST builder methods doc comments (#25173) (overlookmotel)
2026-08-03 11:07:37 +00:00
camc314 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
2026-07-31 10:25:47 +00:00
camc314 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
2026-07-29 13:32:32 +00:00
Boshen 5d8ccf510b fix(react_compiler): preserve nonfatal diagnostics (#25066)
Preserves nonfatal React Compiler diagnostics and covers warning bailouts through the N-API binding.

AI-assisted.
2026-07-29 13:09:20 +00:00
Boshen d349e4a449 fix(react_compiler): match eslint suppression gating (#25068)
Matches React Compiler ESLint suppression gating for internal validation options.

AI-assisted.
2026-07-29 12:41:00 +00:00
Boshen 0dc16ba3a0 fix(react_compiler): track catch block declarations (#25067)
Tracks declarations introduced by catch bindings in React Compiler HIR.

AI-assisted.
2026-07-29 12:29:08 +00:00
overlookmotel 7e1199cbbb feat(ast)!: remove conversion to Box from AST builder methods (#25038)
AST builder methods accepted an `IntoIn<ArenaBox<T>>` for all params where an `ArenaBox<T>` is required. You could pass a plain `T`, and the builder method would automatically allocate space for it, and copy it into the arena.

This was originally intended as a convenience, but in fact it's a footgun - it encourages allocating late, holding types on the stack during other work, which results in register pressure, and churn of copying to/from the stack.

Remove the automatic conversion. All places where we were using auto-allocation are already fixed in previous PRs #25034, #25035, #25036, #25037.

Additional benefits of this change:

1. AST builder methods become less polymorphic which should help with compile times.
2. We can get rid of the `NONE` type, which was a hacky workaround, necessary for passing as params where an `IntoIn<Option<ArenaBox<T>>>` is required. Now these all become plain `None`.

Most of the diff in this PR is just replacing `NONE` with `None` all over the place.

Note: AST builder methods still define params which are `ArenaVec`s as `IntoIn<ArenaVec<T>>`, as this does add genuine value - it allows passing `[]` instead of the much more verbose `ArenaVec::new_in(self)`.
2026-07-28 22:40:04 +00:00
overlookmotel 7251e52a69 perf(react_compiler): avoid large types on stack (#25037)
Continuation of #25034.

In React compiler, move some allocations to happen earlier.

When we need an `ArenaBox<T>` for later method calls, it's preferable to allocate it into arena as swiftly as possible, so only hold an 8-byte `ArenaBox` on stack, instead of the whole `T`, which is much larger. This reduces stack and register pressure.
2026-07-28 22:40:03 +00:00
oxc-guard[bot] fc702c1fa9 release(crates): oxc v0.142.0 (#24977)
### 💥 BREAKING CHANGES

- 1ac5ac7 ast: [**BREAKING**] Tighten bound on `AstBuild` trait (#24925)
(overlookmotel)

### 🚀 Features

- c7c5696 minifier: Fold parenthesized boolean ternary tests (#24968)
(Dunqing)
- 82c5806 minifier: Fold sequence branches in boolean ternaries (#24943)
(Dunqing)
- cc623d9 ast_visit: Add VisitJsMut visitor (#24828) (Boshen)
- b31713a minifier: Add new IsTerminated trait and use it in exit-point
optimization (#24950) (Armano)
- e9e0f24 minifier: Shorten integer zero comparisons (#24933) (Dunqing)
- 34f5559 minifier: Convert switch with one case to if statement
(#24611) (Armano)
- 9107e74 str: Add `static_str!` macro (#24887) (overlookmotel)
- 4d6f623 parser: Emit error for type args in JS files (#24896)
(camc314)
- ec08afc ast: Re-export `Ident` from `oxc_ast` crate (#24861)
(overlookmotel)
- 73cfdfa oxc: Re-export `oxc_str` types from `oxc` crate (#24860)
(overlookmotel)
- 11f5d1f ast_visit: Add `Utf8ToUtf16::convert_program_and_comments`
(#24859) (overlookmotel)

### 🐛 Bug Fixes

- cb5acfb minifier: Optimize termination statements in labeled
statements (#24813) (Armano)
- 4618300 minifier: Avoid longer numeric constant folds (#24875)
(Dunqing)
- f7971f1 minifier: Keep side effects in typeof comparisons (#24940)
(sapphi-red)
- 4597d6f minifier: Keep side effects when compressing `== true` to `==
1` (#24939) (sapphi-red)
- c3a48df minifier: Keep side effects values in template literals
(#24938) (sapphi-red)
- e19f183 minifier: Keep side effects when folding `Number` call
(#24937) (sapphi-red)
- 9d7745a react-compiler: Preserve warning bailouts (#24935) (Boshen)
- 0126aba codegen: Preserve orphaned file coverage comments (#24815)
(Dunqing)
- 9fbbaf8 parser: Clarify return in class static blocks (#24899)
(camc314)
- e7d5c78 str: Fix hygiene of `format_ident!` and `format_str!` macros
(#24886) (overlookmotel)
- 07e9acc ecmascript: Keep side effects in parent of `toString` calls
(#24846) (sapphi-red)
- 16a40ad ecmascript: Detect side effects in unused arguments to
`charCodeAt` (#24845) (sapphi-red)
- 5b4ae54 parser: Use specific for-await diagnostic (#24856) (camc314)
- ffcc33a parser: Report for-await in non-async functions (#24855)
(camc314)
- df230d0 minifier: Avoid reordering this in nested class keys (#24785)
(Dunqing)
- d24b76b minifier: Isolate derived constructor this state (#24784)
(Dunqing)
- 582805d parser: Parse unary conditional after satisfies (#24807)
(Boshen)
- 23b904f minifier: Preserve default parameter side effects (#24791)
(Dunqing)
- 3048594 parser: Use correct diagnostic for `await using` in bare
switch case (#24798) (camc314)
- e80574f estree: Handle empty spans serializing `ImportMeta` and
`NewTarget` (#24775) (overlookmotel)
- 4b12840 minifier: Do not fold a `-0` ternary branch to `+a` (loses the
sign) (#23637) (Jerry Zhao)
- 133bb35 ecmascript: Detect side effects of `foo().#bar` correctly when
PropertyReadSideEffects::None (#24769) (sapphi-red)

### ⚡ Performance

- a086d91 ecmascript: Avoid number literal allocations (#24889)
(Dunqing)
- 98f2ecb react_compiler: Compare value set pointers before probing in
state merge (#24805) (Marius Schulz)
- b287f29 estree: Avoid ident hash calculations when serializing
(#24774) (overlookmotel)

### 📚 Documentation

- 4d2e2bf ast: Fix incorrect return value in `common_js_require` docs
(#24945) (connorshea)
- b539e74 estree: Document custom serializers for `ImportMeta` and
`NewTarget` (#24777) (overlookmotel)

Co-authored-by: Boshen <1430279+Boshen@users.noreply.github.com>
2026-07-27 23:07:40 +08:00
Boshen 9d7745af95 fix(react-compiler): preserve warning bailouts (#24935)
Preserve warning-level React Compiler diagnostics instead of wrapping pipeline bailouts in a synthetic fatal error. This keeps expected incompatible-library and Todo bailouts non-fatal so sibling functions can still compile.

Adds regression coverage and refreshes the affected snapshots. Validated with `just ready`.

AI-assisted: Codex was used to investigate, implement, and validate this change.
2026-07-26 06:26:02 +00:00
Jacob Asper 33a97da760 chore(oxc_react_compiler): exclude testonly files from published crate (#24932)
Adds explicit includes for `oxc_react_compiler` to omit 2.2MB of files
(49% of total) under `/tests` and `/fixtures` from the published
package. I included the examples directory, but that can also be
excluded if desired

[The full list of removed files are included in this text
file](https://github.com/user-attachments/files/30380492/oxc_react_removed.txt).
Today I learned there is a size limit in GitHub PR descriptions

For the record, I checked all other published crates in the workspace,
and none of them included any extraneous files 😃
2026-07-26 10:13:54 +08:00
overlookmotel e130d85a37 refactor(react_compiler): shorten AST builder method calls (#24844)
Shorten AST builder calls in React Compiler, by collapsing multiple calls into one.

I came across these while testing the codemod.
2026-07-23 16:01:15 +00:00
Marius Schulz 98f2ecbb10 perf(react_compiler): compare value set pointers before probing in state merge (#24805)
## Context

Value sets in the aliasing inference state are `Rc<FxHashSet<ValueId>>`,
shared between states rather than copied (#24117). When two states meet
at a join, the merge still walks every variable and probes one set
against the other looking for a value the current state doesn't have.
Most of the time both sides point at the same `Rc`, so that probe is
comparing a set to itself.

## This PR

Check `Rc::ptr_eq` before probing. If the pointers match there is
nothing to add, so we skip the scan. Both merge paths did this check in
slightly different ways, so I pulled it into one helper. The sets are
never mutated in place, which is what makes the pointer check safe.

### Performance

| workload | main | this PR | delta |
| ----------------------------------------------------- | -------: |
-------: | ------: |
| 3.6k-line control-flow heavy component, instructions | 23.762 B |
22.034 B | -7.3% |
| same file, wall time |   4.55 s |   4.13 s |     -9% |
| 16.5k-file real corpus, instructions (1 thread) | 50.135 B | 49.697 B
| -0.87% |

This PR was assisted by Claude Code.
2026-07-22 18:46:45 +08:00
Boshen 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.
2026-07-21 15:22:46 +00:00
oxc-guard[bot] 14533a3dc1 release(crates): oxc v0.141.0 (#24714)
### 💥 BREAKING CHANGES

- 54cc121 ast: [**BREAKING**] Split `MetaProperty` into `ImportMeta` and
`NewTarget` (#24557) (camc314)

### 🚀 Features

- 4c71560 parser: More friendly error for spread element in dynamic
imports (#24705) (sapphi-red)
- 7b045cd minfier: Drop last break from last switch case (#24673)
(Armano)
- 7d3c178 minifier: Remove unreachable recursive functions (#24125)
(Dunqing)
- 94f99b3 ast: Allow `NONE` to be passed to AST builder methods where
`Option<ArenaVec>` is expected (#24629) (overlookmotel)
- 77230c5 ast: Accept arrays for `ArenaVec` params of AST builder
methods (#24621) (overlookmotel)
- f08b152 allocator: Implement `FromIn` for array to `Vec` conversion
(#24620) (overlookmotel)
- 2338c13 track-memory-allocations: Track heap deallocs, alloc bytes,
and peak growth (#24619) (Boshen)
- 7aa4739 syntax,transformer: Move JSX entity decoder to `oxc_syntax`
(#24617) (camc314)
- 2b097c4 str: Export `Str` as `ArenaStr` (#24604) (overlookmotel)
- 3acf4c1 minifier: Expand switch optimiation to remove empty cases
(#24520) (Armano)
- 129b759 parser: Improve diagnostics for unparenthesized LHS on
exponential expr (#24569) (camc314)
- 4d0c601 minifier: Fold arithmetic over undefined and null operands
(#24485) (Dunqing)
- 91541dd minifier: Drop empty switch statements  (#24527) (Armano)
- d05224d ast_visit: Generate VisitJs visitor that skips TypeScript
type-space (#24499) (Boshen)
- 3d22307 parser: Add `ParseOptions::enable_ident_hashes` (#24491)
(Boshen)

### 🐛 Bug Fixes

- 64c2241 minifier: Align class heritage removal with assumptions
(#24533) (Dunqing)
- 48b59f4 parser: Span ambient generator diagnostics (#24711) (camc314)
- e750a82 ecmascript: Fix false negative for may_have_side_effects on
dynamic property access (#24709) (sapphi-red)
- f145d73 minifier: Guard reordered identifier reads (#24698) (Dunqing)
- a2ef382 isolated-declarations: Reject `window.Symbol` as global symbol
reference (#24689) (camc314)
- b1bcf72 minifier: Invalidate facts for redeclared bindings (#24658)
(Dunqing)
- 921b834 minifier: Don't treat a conditionally-assigned var as
write-once (#24650) (Dunqing)
- 061af1f minifier: Avoid stale pure function summaries (#24636)
(Dunqing)
- 40c2f43 allocator: `Vec::from_array_in` do not allocate zero-length
array (#24628) (overlookmotel)
- 70994ae codegen: Preserve comments before expression operands (#24510)
(Dunqing)
- 7b4baff parser: Reject new import member access (#23459) (camc314)
- 128b385 minifier: Clippy warning with no-debug-assertions (#24547)
(camc314)
- 8421feb parser: Use first `as` span for imported name (#24537)
(leaysgur)
- c517aa0 parser: Reject invalid accessor assertions (#24504) (camc314)

### ⚡ Performance

- 884d9eb parser: Pre-size cover-grammar assignment target buffers
(#24683) (Boshen)
- d3f07a0 diagnostics: Box OxcDiagnosticInner to reduce binary size
(#24665) (Boshen)
- bcc9de0 parser: Defer diagnostic creation until parse exit (#24663)
(Boshen)
- c35d8ab allocator: Mark `ReplaceWith` panic path cold (#24515)
(camc314)
- ba65790 semantic, allocator: Branchless `clone_in` for semantic IDs
(#24564) (overlookmotel)
- 747feec parser: Build AST nodes with the AST builder instead of
cloning (#24540) (Boshen)
- ba35a0d react_compiler: Use IndexVec for dense id-keyed maps (#24549)
(Boshen)
- b685062 react_compiler: Keep small hot-path collections inline
(#24514) (Marius Schulz)
- a149e95 transformer: Outline rare expression exits (#24512) (camc314)
- 7808a6e react_compiler: Make aliasing effects cheap to intern and
clone (#24506) (Marius Schulz)
- 3a36f2a react_compiler: Store AbstractValue reasons as a u16 bitmask
(#24480) (Boshen)
- 1c96753 react_compiler: Use FxHashMap for the lookup-only aliasing
node map (#24490) (Boshen)

Co-authored-by: Cameron <cameron.clark@hey.com>
2026-07-21 16:09:41 +01:00
Boshen 9f3e30088d fix(react_compiler): preserve aliasing diagnostic locations (#24757)
Preserve source-specific labels for aliasing diagnostics while retaining
their shared analysis identity. Previously, interning by place, message,
and help reused the first source location for later effects.

Validated with `just ready` and React ecosystem comparisons.

AI-assisted: Codex was used to implement and validate this change.
2026-07-21 20:21:01 +08:00
Boshen 4b81f4d963 fix(react_compiler): honor default suppressions (#24747)
Honor Babel React Compiler 1.0.0's default ESLint and Flow suppression bailouts even when validation flags are enabled.

Validation:
- `cargo test -p oxc_react_compiler`
- `cargo clippy -p oxc_react_compiler --all-targets -- -D warnings`
- Direct fixture comparison with `babel-plugin-react-compiler@1.0.0`

Caveat: Babel 1.0.0 treats block disables as open-ended; this change preserves that behavior.

AI-assisted.
2026-07-21 07:02:25 +00:00
Boshen 3fe5c87bbd perf(react_compiler): transform reactive blocks in place (#24703)
The reactive-tree transform passes rebuilt each mutated block by
deep-cloning statements into a fresh vector. With the tree now
arena-backed, those clones (and the abandoned scratch vectors) were
never freed until the per-file arena dropped — a churn tail on top of
the tree's relocation into the arena. This removes it.

## What changed

- `traverse_block` — the shared transform engine used by all 7
reactive-scopes passes — now mutates the block's own arena buffer
directly: `Keep` transforms in place, `Replace` overwrites the slot,
`Remove` is `Vec::remove`, `ReplaceMany` is `Vec::splice`. This drops
(a) the deep `clone_in` of the block prefix on the first structural
change, (b) the scratch `next_block` vector, and (c) the per-statement
placeholder swap. The index bookkeeping (advance on Keep/Replace, hold
on Remove, skip inserted on ReplaceMany) reproduces the exact original
order.
- Merge Pass-3 already `mem::replace`s the block out — so it owns the
statements — but then cloned them back into the result; it now moves
them via `into_iter`.

Behavior is unchanged — snapshot corpus and transform tests remain
byte-identical.

## Memory

Arena bytes across the full 1269-fixture corpus (fresh allocator per
file, after `compile`): **12.97 MB → 12.12 MB (−6.5%)**, recovering most
of the reactive-tree flip's churn tail. The deep `clone_in` of statement
subtrees is replaced by cheap moves (a CPU win on transform-heavy
components, in addition to the allocation drop).

## Stack

- #24664
- #24666
- #24696
- #24700
- #24702
- **this PR** — stacked on top of #24702

🤖 This PR was written with the assistance of Claude Code.
2026-07-21 09:49:25 +08:00
Boshen 165c465396 refactor(react_compiler): move the reactive-function tree into the arena (#24702)
The final subtree of the react_compiler HIR-to-arena migration: the
ReactiveFunction tree's own structural vectors and boxes move off the
heap into the compilation arena, so the reactive tree stops straddling
two allocators.

## What changed

- `ReactiveBlock` (`= Vec<ReactiveStatement>`, every block body),
`ReactiveValue::SequenceExpression.instructions`, and
`ReactiveTerminal::Switch.cases` become `oxc_allocator::Vec<'a>`.
- The tree's `Box` fields (`ReactiveStatement::Terminal`, the boxed
`ReactiveValue` sub-expressions, ...) become `oxc_allocator::Box<'a>` —
required so the element types are `!needs_drop` and can live in an arena
`Vec` (`ReactiveStatement` was `Drop` via its std boxes). This completes
the tree's move (Vec + Box) into the arena.
- The transform passes' `std::mem::take` on block bodies (arena `Vec`
has no `Default`) become `std::mem::replace(.., Vec::new_in(alloc))`;
construction sites allocate from `env.allocator`.
- `Transformed::ReplaceMany` deliberately stays a std `Vec` — a
transient replacement list that `traverse_block` drains into the arena
block — so the generic visitor enum and every pass's `ReplaceMany`
construction are untouched.

Behavior is unchanged — snapshot corpus and transform tests remain
byte-identical.

## Memory

Arena bytes across the full 1269-fixture corpus (fresh allocator per
file, after `compile`):

| | total arena used | max single fixture |
|---|---|---|
| before (tree on heap) | 9.93 MB | 228 KB |
| after (tree in arena) | 12.97 MB | 297 KB |

The +3.04 MB (+31%) is a relocation of the tree off the heap — smaller
than the CFG subtree's +82% shift, and no fixture balloons.

## Follow-up (not in this PR)

The reactive-scopes passes rebuild block bodies with a clone-prefix +
take/rebuild pattern; with the arena the abandoned spine isn't freed
until the per-file arena drops (a bounded churn tail, included in the
+31% above). Rewriting `traverse_block` and the merge Pass-3 to
drain/splice the block in place would collapse that churn tail **and**
delete a pre-existing wasteful deep-clone (a CPU win). Deferred to its
own PR because the in-place splice must be verified byte-identical
across the whole corpus.

## Stack

- #24664
- #24666
- #24696
- #24700
- **this PR** — stacked on top of #24700

🤖 This PR was written with the assistance of Claude Code.
2026-07-21 09:49:24 +08:00
Boshen b6b058fdf6 refactor(react_compiler): move the CFG HIR into the arena (#24700)
The largest step of the react_compiler HIR-to-arena migration: moves the
control-flow-graph HIR and the reactive-function tree off the global
heap into the compilation arena, eliminating the per-node
`malloc`/`free` churn (block instruction/phi vectors, predecessor sets,
phi operand maps, instruction-value vectors) that dominated the
compiler's allocation traffic.

## What changed

- `HIR`, `BasicBlock`, `Phi`, `Terminal`, `AliasingEffect`,
`ArrayPattern`, and `AliasingSignature` gain an `'a` lifetime; their
`Vec` fields become `oxc_allocator::Vec<'a>` (block instructions/phis,
terminal effects/cases, all `InstructionValue` vectors, patterns,
aliasing args/captures, function
params/context/directives/aliasing-effects, ...).
- The two per-node insertion-ordered containers move to the arena:
`BasicBlock.preds` → `ArenaOrderedSet<'a, BlockId>` and `Phi.operands` →
`ArenaOrderedMap<'a, BlockId, Place>` — new arena-backed variants
(`oxc_allocator::Vec` + `oxc_allocator::HashMap`) added alongside the
existing std `OrderedMap`/`OrderedSet`, which are kept.
- `HIR.blocks` deliberately stays a std `OrderedMap` (one map per
function, no allocation payoff, and `HirFunction` is never arena-boxed —
it lives in a std `IndexVec`). This also keeps
`get_reverse_postordered_blocks` returning a std map, bounding arena
high-water.
- Arena `Vec` is not `Clone`, so the arena-carrying HIR and
reactive-tree types drop `#[derive(Clone)]` and get hand-written
same-arena `CloneIn` impls; construction/clone sites across ~30 files
thread the allocator from `env.allocator`.

Behavior is unchanged — the snapshot corpus and transform tests remain
byte-identical.

## Memory

Arena bytes used across the full 1269-fixture corpus (fresh allocator
per file, measured after `compile`):

| | total arena used | max single fixture |
|---|---|---|
| before (HIR on heap) | 5.45 MB | 171 KB |
| after (HIR in arena) | 9.93 MB | 228 KB |

The +4.48 MB is the HIR moving from the heap into the arena (offsetting
the corresponding heap allocations). No pathological ballooning:
RPO-heavy fixtures grow ~2-3× at negligible absolute scale, and arenas
free per-file so nothing accumulates across the corpus.

## Stack

- #24664
- #24666
- #24696
- **this PR** — stacked on top of #24696

🤖 This PR was written with the assistance of Claude Code.
2026-07-21 09:49:24 +08:00
Boshen e7fa27b23a refactor(react_compiler): store ReactiveScope collections in the arena (#24696)
Continues the react_compiler HIR-to-arena migration by moving the
per-compilation `ReactiveScope` collections off the global heap and into
the compilation arena.

## What changed

- `ReactiveScope::{dependencies, declarations, reassignments, merged}`
and `ReactiveScopeDependency::path` change from `std::vec::Vec` to
`oxc_allocator::Vec<'a>`.
- Arena `Vec` is not `Clone`, so
`ReactiveScope`/`ReactiveScopeDependency` drop `#[derive(Clone)]`.
`ReactiveScopeDependency` and `DependencyPathEntry` get hand-written
same-arena `CloneIn` impls (leaves copy, the arena `Vec` recurses);
`ReactiveScopeDeclaration` becomes `Copy`.
- Construction and clone sites across the reactive-scope and inference
passes now allocate from `env.allocator` (`clone_in` /
`ArenaVec::*_in`); a few dependency-collection helpers take an explicit
`&'a Allocator`.

Behavior is unchanged — the snapshot corpus and transform tests remain
byte-identical.

## Stack

- #24664
- #24666
- **this PR** — stacked on top of #24666

🤖 This PR was written with the assistance of Claude Code.
2026-07-21 09:49:24 +08:00
Boshen bfecb1a3f8 refactor(react_compiler): back HIR ordered maps with Vec + FxHashMap (#24666)
The HIR's insertion-ordered CFG containers were `indexmap`-based:

- `HIR.blocks: FxIndexMap<BlockId, BasicBlock>` — iteration order is
reverse-postorder, which is load-bearing (forward dataflow visits each
block after its predecessors).
- `BasicBlock.preds: FxIndexSet<BlockId>` — predecessor order sets
phi-operand order.
- `Phi.operands: FxIndexMap<BlockId, Place>` — one operand per
predecessor, in that order.

`indexmap` has no arena-allocator variant, which blocks moving the HIR
into an arena. `Vec` and hash maps do (`oxc_allocator::Vec` /
`oxc_allocator::HashMap`), so this replaces those three fields with
`OrderedMap` / `OrderedSet` — ordered containers backed by `Vec<(K, V)>`
plus an `FxHashMap<K, usize>` index. They mirror the subset of the
`IndexMap` / `IndexSet` API the HIR uses
(`get`/`get_mut`/`insert`/`shift_remove`/`retain`/`drain`/`keys`/`values`/`iter[_mut]`/`get_index_of`,
`IntoIterator`, `Index`, `DoubleEndedIterator`), preserving insertion
order and O(1) lookup, so call sites are unchanged.

This is a preparatory step: the three fields are now composed of
arena-convertible primitives, so the eventual arena move is a mechanical
`Vec` -> `Vec<'a>` / `FxHashMap` -> arena `HashMap` swap rather than an
impossible `indexmap` port.

The `SymbolId`-keyed builder maps and `StabilizeBlockIds`' scratch set
stay `FxIndexMap` / `FxIndexSet` — they are pass-local working data, not
HIR CFG structures.

Pure internal change: the fixtures snapshot is byte-identical; new unit
tests cover the `shift_remove` / `retain` / `drain` reindexing.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-07-21 09:49:23 +08:00
Boshen a62edfcbb2 refactor(react_compiler): intern AliasingEffect diagnostics behind a DiagnosticId (#24664)
The `MutateFrozen`/`MutateGlobal`/`Impure` variants of `AliasingEffect`
each held an owned `OxcDiagnostic`. Objects allocated into an oxc arena
are never dropped, so `OxcDiagnostic` — which owns `Cow<'static, str>`
message/help plus a `Labels` vec — is a `Drop` type that blocks moving
the HIR into an arena.

Move the diagnostics into an `Environment`-owned side table and store a
`Copy` `DiagnosticId` in the effect instead:

- `Environment::intern_aliasing_diagnostic` interns a diagnostic and
returns its id, deduplicated on `(place, message, help)` — the same
granularity as the effect interning key (`EffectKey`). Identical errors
still collapse to one stored diagnostic, so `EffectKey` can key on the
id directly instead of cloning the message/help strings.
- The table uses `RefCell` because two effect-building passes
(`compute_signature_for_instruction`,
`compute_effects_for_legacy_signature`) only hold `&Environment`.
Interning is off the hot path — it happens only when an error effect is
produced.
- The range pass resolves the id back to the diagnostic at emit time
(`env.record_diagnostic(env.aliasing_diagnostic(id))`), the point at
which these errors were already surfaced.

`AliasingEffect` no longer carries any `Drop` diagnostic; its remaining
non-arena fields are the `Vec`s, which are the mechanical arena-`Vec`
migration tracked separately.

Pure internal change: the fixtures snapshot is byte-identical.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-21 09:49:23 +08:00
overlookmotel 5293431203 feat(str)!: move IdentBuildHasher to oxc_str crate (#24717)
Move all `Ident`\-hashing code from `oxc_allocator` into `oxc_str`, to sit alongside `Ident`'s code.

This feels like the natural place for this code, and reduces the public API surface - we no longer need to expose `Ident`'s internals (`ident_hash`). Now only `IdentHasher` and `IdentBuildHasher` are exposed.

Only change to code is removing `pack_len_hash`, as it was only used in tests.
2026-07-20 10:48:48 +00:00
Boshen 22f6523b96 refactor(react_compiler): remove dead code and tighten visibility (#24701)
A follow-up dead-code sweep of `oxc_react_compiler`. Everything here is
**behavior-preserving** — the removed branch/variant never executed, and
the rest is stale attributes, a pass-through merge, and visibility.

## Dead code removed
- **Never-wired branch in `rename_variables`** —
`rename_variables_with_parent`'s `parent_names` was always `None` (its
only caller passes `None`; it doesn't recurse), so the `Some(..)`
"outlined functions" block and the wrapper split were dead. Inlined into
`rename_variables`.
- **`ApplyArgConfig::Spread`** — never constructed anywhere (only a
defensive match arm); removed the variant, its write-only `kind` field,
`Hole.kind`, and the now-orphaned `ApplyArgSpreadKind` /
`ApplyArgHoleKind` enums. (`ApplyArgConfig` derives only `Debug, Clone`,
so nothing built it via serde either.)

## Stale attributes / redundant machinery
- Delete 4 `#[allow(dead_code)]` markers whose items are actually used
now: `OxcContext::{fbt_operands, record_error}`, `Driver.env`,
`InferredDependency::Local.context`.
- Merge the pure pass-through `Unifier::unify` into `unify_impl`
(identical signature, no accumulator) — a single `unify`.

## Visibility
Tighten 8 items referenced only within their own module to private /
`pub(crate)`: `GlobalRegistry`/`ShapeRegistry::into_inner`,
`post_dominators_of`, `collect_maybe_memo_dependencies`,
`is_always_invalidating_type`, `MEMO_CACHE_SENTINEL`, `next_type_id`,
`validate_context_variable_lvalues_with_errors`.

`14 files changed, +17 / -85`. `cargo clippy` clean; unit + transform
tests pass.
2026-07-20 14:58:13 +08:00
Boshen cb102dedd1 refactor(react_compiler): drop dead error-plumbing and coercion helpers in globals (#24697)
Follow-up cleanup of `globals.rs` (and its two callers) in `oxc_react_compiler`. Removes machinery that no longer earns its keep — no behavior change.

## What

- **Collapse the `install_type_config` split.** `install_type_config_with_errors` wrapped a private `install_type_config_inner` whose `errors: &mut Option<&mut Vec<String>>` was *always* `Some` — the sole caller passed `Some` and the recursion only forwarded it. Vestigial from a non-error variant that no longer exists. Now a single `install_type_config` taking `&mut Vec<String>`.
- **Remove `shrink_global` / `shrink_shape`.** These were `fn(&T<'static>) -> &T<'a> { x }` identity functions; lifetime variance already performs that coercion in `Registry::get`, so they were no-op noise.
- **Tighten visibility.** `build_builtin_shapes` / `build_default_globals` are only used inside the `BASE` initializer, so they no longer need to be `pub`.

Net `3 files changed, 27 insertions(+), 53 deletions(-)`.
2026-07-20 04:58:28 +00:00
Boshen 3f03a5be7f refactor(react_compiler): drop Rc<FunctionSignature> from AliasingEffect::Apply (#24662)
`AliasingEffect::Apply` held `Option<Rc<FunctionSignature>>`. Objects allocated into an oxc arena are never dropped, so a refcounted `Rc` — like any `Drop` type — blocks moving the HIR into an arena.

The `Rc` was never shared ownership for its own sake; it was a borrow-detachment device. The signature is a deterministic lookup (`env.get_function_signature(env.types[…])`), read exactly once in the same pass that builds it and ignored by every later pass. The `Rc` existed only to snapshot the registry signature into owned data so `apply_effect` could read its fields while calling `&mut env` effect computations.

Store the callee's `TypeId` instead and resolve the `FunctionSignature` on demand. `TypeId` is `Copy`/non-`Drop` and lifetime-free, so no lifetime is forced onto `AliasingEffect`/`Terminal`/`HIR`. The id is propagated unchanged through the place-substitution remap in `compute_effects_for_aliasing_signature`, preserving the exact signature identity (re-deriving from the *substituted* callee place would not). The read site clones the resolved signature into a local to reproduce the previous borrow detachment. The now-dead `get_function_call_signature` helper is removed.

Pure internal change: the fixtures snapshot is byte-identical.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---

<sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>
2026-07-19 09:58:15 +00:00
Boshen c91d2a42c6 refactor(react_compiler): derive Copy for leaf HIR types (#24660)
The `react_compiler` HIR is an SSA/CFG IR, not an AST clone, so most of it cannot be `Copy` (the aggregate nodes hold `Vec`/`FxIndexMap`/`Box`). But a number of its **leaf** types have all-`Copy` fields and were needlessly deriving only `Clone`.

This derives `Copy` on the 23 leaf HIR types where it is free — `Place`, `Identifier`, `MutableRange`, `LValue`, the pattern/element enums (`ParamPattern`, `SpreadPattern`, `ArrayPatternElement`, `ArrayElement`, `PlaceOrSpread`, `ObjectPropertyOrSpread`, `ObjectProperty`, `ObjectPropertyKey`), the JSX leaves (`JsxTag`, `JsxAttribute`, `BuiltinTag`), `LoweredFunction`, `TemplateQuasi`, `PropertyNameKind`, and the binding/dep leaves (`VariableBinding`, `NonLocalBinding`, `ManualMemoDependencyRoot`, `DependencyPathEntry`) — and drops the now-redundant `.clone()` / lazy-eval calls that clippy flags as a result.

The aggregate nodes are deliberately untouched. This is a pure internal cleanup: the fixtures snapshot is byte-identical.

Net **-151 lines** across 31 files.
2026-07-19 06:47:24 +00:00
overlookmotel d76d89996f refactor(react_compiler): shorten code by using NONE (#24630)
Pure refactor.

Shorten code in React compiler by using `NONE` instead of writing out the type long-hand.

e.g. `None::<ArenaBox<TSTypeParameterDeclaration>>` -> `NONE`
2026-07-18 02:22:44 +00:00