Commit Graph

542 Commits

Author SHA1 Message Date
leaysgur 9f81002229 fix(formatter): re-add suppressed statement terminators per options.semi (#26220)
```js
// input (semi: true)
// oxfmt-ignore
const a = x

// before
// oxfmt-ignore
const a = x

// after
// oxfmt-ignore
const a = x;
```

```js
// input (semi: false)
let a = 1;
// oxfmt-ignore
[b].sort();

// before
let a = 1
// oxfmt-ignore
[b].sort(); // <- ASI failure

// after
let a = 1
// oxfmt-ignore
;[b].sort() // <- OK
```
2026-09-01 07:25:43 +00:00
leaysgur 769ffd590d fix(formatter): print suppressed typecast parens (#26218)
```js
// input
foo(
  // oxfmt-ignore
  /** @type {A} */ (x),
  /** @type {B} */ (y),
);

// before
foo(
  // oxfmt-ignore
  /** @type {A} */ x, // <-
  /** @type {B} */ (y),
);

// after
foo(
  // oxfmt-ignore
  /** @type {A} */ (x), // <-
  /** @type {B} */ (y),
);
```
2026-09-01 07:25:42 +00:00
leaysgur 662556c174 fix(formatter): print the idempotent placement for comments in dropped parens (#26041)
Fixes non-idempotent formatting with comment and dropped parens.
2026-08-25 09:28:11 +00:00
leaysgur 0fe2313d7a refactor(formatter): replace fmt_with_options with a context handoff (#26037)
Remove `fmt_with_options` impl to justify what to be generated and not.

In the first place, for `Function` one was not used.
2026-08-24 06:31:20 +00:00
leaysgur 6419ba452b refactor(formatter): make write_supressed as trait impl default (#26036)
Pure refactoring.
2026-08-24 06:31:19 +00:00
leaysgur bf37dd5c43 fix(formatter): preserve class decorators before export when the statement is suppressed (#26034)
Fixes #25911

---

As syntax, these two forms are equivalent:

```ts
@deco export class X {}
export @deco class X {}
```

However, in the former, the decorators lie outside the span of both the `ExportDeclaration` and the `Class`, they are inside no node's span.

The formatter's suppression path prints the `ExportDeclaration`'s span verbatim, so the decorators were silently dropped.
The latter form is unaffected because the decorators fall inside the export node's span.
2026-08-24 06:31:19 +00:00
Boshen 2cde1f6671 fix(rust): address nightly deprecations (#25998)
Addresses deprecations reported by the latest Rust nightly while preserving Rust 1.98 compatibility.

- Rename `unit-bindings` to `unit_bindings`: hyphenated lint names are deprecated in favor of their canonical underscore spelling.
- Replace `isize::max_value()` and `usize::max_value()` with `isize::MAX` and `usize::MAX`: the legacy numeric methods are deprecated in favor of associated constants.
- Set explicit `MAX_INDEX` values on `define_index_type!`: `oxc_index` otherwise generates `<raw>::max_value() as usize`; the explicit modern constants avoid that deprecated expansion without changing the index limits.
- Remove obsolete `clippy::legacy_numeric_constants` expectations: the corrected code no longer triggers that lint, so the expectations would be unfulfilled.
- Allow the AST scanner to consume optional `define_index_type!` configuration: it previously parsed only the struct declaration and rejected the added `MAX_INDEX` option.

AI-assisted.
2026-08-22 11:34:02 +00:00
camc314 c0f71564c3 chore(deps): bump MSRV to 1.96.0 (#25946)
## Summary

- bump the workspace MSRV from 1.95.0 to 1.96.0 under the N-2 policy after #25945
- retain an MSRV-compatible layout calculation while suppressing the Rust 1.98 Clippy suggestion
2026-08-20 18:23:40 +00:00
renovate[bot] 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>
2026-08-20 18:04:07 +00:00
leaysgur 44231fb5f7 fix(formatter): fix comment and indent issues around unions (#25665)
Fixes comment-placement bugs, idempotency and also inconsistency around unions/intersections, type aliases, conditional types, and assignment-likes.

- Fix invariant violations where comments crossed `=`, the value, or `;`
  - removed the relocation logic emulating old Prettier 3.8 behavior
- Normalize a union's leading comments to one canonical form behind the synthesized leading `|`, independent of the source shape
- Comments around a formatter-added `(` keep their source side of the `(`
- Hug conditional-type branch comments behind `?`/`:` with the content one level under the operator, matching conditional expressions
- Extend `= // c` position preservation from type aliases to all of JS
2026-08-16 15:04: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 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
overlookmotel ef8f33e5fc refactor(ast_tools): avoid #[doc] for plain doc comments (#25237)
Pure refactor. `ast_tools` generators use plain doc comments instead of the the more verbose `#[doc = "..."]` where the comment can be written statically.
2026-08-03 09:34:21 +00:00
overlookmotel 183d8b94f7 refactor(ast): use impl GetAstBuilder instead of generic params in AST builder methods (#25175)
Pure refactor. Continuation of #25174.

Simplify AST builder methods by switching from generic param `B: GetAstBuilder<'a>` to `impl &GetAstBuilder<'a>`. This makes the code shorter, and easier to read.

Before:

```rust
pub fn new_big_int_literal<B: GetAstBuilder<'a>>(
    // ... etc ...
    builder: &B,
) -> Self
```

After:

```rust
pub fn new_big_int_literal(
    // ... etc ...
    builder: &impl GetAstBuilder<'a>,
) -> Self
```
2026-08-01 15:40:55 +00:00
overlookmotel 5d129463c6 refactor(ast): simplify AST builder method generics (#25174)
Pure refactor.

Simplify AST builder methods by switching from generic params to `impl`s. This makes the code shorter, and easier to read.

Before:

```rust
pub fn new_big_int_literal<B: GetAstBuilder<'a>, S1>(
    value: S1,
    // ... etc ...
) -> Self
where
    S1: Into<Str<'a>>
```

After:

```rust
pub fn new_big_int_literal<B: GetAstBuilder<'a>>(
    value: impl Into<Str<'a>>,
    // ... etc ...
) -> Self
```
2026-08-01 15:40:55 +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
leaysgur 6bc1fe1f03 refactor(formatter): drop oxc_formatter_core re-export (#25140)
Leftovers from when we extract the `oxc_formatter_core`.
2026-07-31 08:11:23 +00:00
camc314 8541e18e91 feat(ast_tools): generate minifier scope collector (#25088)
## Summary

- generate `ChildScopeCollector` for both `oxc_traverse` and `oxc_minifier` from the same AST tools generator
- synchronize the minifier collector with the current AST schema
- prevent the two generated collector implementations from drifting
2026-07-29 16:25:26 +00:00
overlookmotel 07cff4e79b refactor(ast): rename Vec type params on AST builder methods (#25039)
Pure refactor, cosmetic change.

After #25038, all the generic params on AST builder methods are for `Vec`s. Reflect that in the naming of the generic params on these methods - name them `V1`, `V2` etc, instead of `T1`, `T2` ("V" for "Vec").
2026-07-28 22:40:04 +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
Boshen cc623d93db feat(ast_visit): add VisitJsMut visitor (#24828)
## Summary

Generate `VisitJsMut` and `walk_js_mut` using the same JavaScript-only
traversal rules as `VisitJs`.

This avoids monomorphizing pure TypeScript type-space traversal for
mutable runtime visitors.

AI assistance was used to implement this change. I reviewed and take
responsibility for the result.
2026-07-27 16:20:25 +08:00
overlookmotel fbab4c31d2 refactor(ast): monomorphise ArenaBox::new calls in AST builder methods (#24926)
Continuation of #24925. `ArenaBox::new_in` takes any `&A where A: GetAllocator`. To reduce polymorphism, in AST builder methods convert the generic builder type to a concrete `&Allocator` before passing to `ArenaBox::new_in`.

Same as #24925, this does not impact binary size, as `ArenaBox::new_in` is inlined anyway, but it may reduce compile times due to reduce polymorphism.
2026-07-25 20:13:28 +00:00
overlookmotel 1ac5ac76f2 feat(ast)!: tighten bound on AstBuild trait (#24925)
Bound any type which implements `AstBuild` to also implement `GetAstBuilder` (referring to itself).

This means the type returned by `.builder()` on any `GetAstBuilder` type can be passed to any other method which takes `&B where B: GetAstBuilder` (e.g. any AST builder method).

Use this in AST builder methods to make their calls to other AST builder methods monomorphic over the actual `AstBuilder` type. This should reduce compile times, as less polymorphic types for the compiler to deal with. Note: It will _not_ reduce binary size, as all these methods are trivial and inlined anyway.

This also opens the door to reducing binary size in Rolldown. Some of Rolldown's custom AST builder methods are _not_ trivial and are currently polymorphic over multiple `GetAstBuilder` types (of which Rolldown has many). This change will allow splitting those methods into 2 - a generic wrapper method, and an inner implementation which is always called with the same `AstBuilder` type.
2026-07-25 20:13:28 +00:00
overlookmotel c5503a9e82 refactor(ast): rename file containing AST builder methods (#24762)
Pure refactor. #24563 removed the old `ast_builder` module, so now the new one can take over its rightful name. We may introduce _other_ kinds of builders in future - this name is unambiguous.
2026-07-22 08:28:58 +00:00
overlookmotel c231146ce6 refactor(ast_tools): rename BuilderMethodsGenerator to AstBuilderGenerator (#24761)
Pure refactor. #24563 removed the old `AstBuilderGenerator`, so now the new one can take over its rightful name. We may introduce _other_ kinds of builders in future - this name is unambiguous.
2026-07-22 08:19:04 +00: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
overlookmotel 77230c5c4d feat(ast): accept arrays for ArenaVec params of AST builder methods (#24621)
Builds on #24620.

Make generated and hand-written AST builder methods take `impl IntoIn<'a, ArenaVec<'a, T>>` instead of `ArenaVec<'a, T>` for their `Vec` params, so callers can pass an array literal (e.g. `[]` or `[a, b]`) in place of building an `ArenaVec` explicitly.

This enables shortening code in a lot of places - #24622 and following PRs.
2026-07-17 13:49:38 +00:00
camc314 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.
2026-07-15 14:48:32 +00:00
Boshen d05224dbcc feat(ast_visit): generate VisitJs visitor that skips TypeScript type-space (#24499)
## What

Generates a new **`VisitJs<'a>`** trait (+ `walk_js` module) in `oxc_ast_visit` that traverses **only the JavaScript parts** of the AST. It skips pure TypeScript type-space nodes (`TSType`, `TSTypeAnnotation`, interfaces, type aliases, …) entirely, but still descends into the JavaScript nested inside a small set of TS wrapper nodes: the five expression casts (`x as T`, `x satisfies T`, `<T>x`, `x!`, `f<T>`), decorators, enum member initializers, namespace/module bodies, `export = expr`, and `import x = require(..)` value aliases.

`VisitJs` is for visiting the JavaScript parts of a **TypeScript** AST — it is not a visitor for ASTs where TypeScript has already been transformed out. The TS constructs carrying runtime JS must therefore still be walked, so their walk code remains in the binary; only the pure type grammar is pruned.

## Why (part of #24317)

The generated `walk_*` functions are generic over the visitor (`fn walk_foo<'a, V: Visit<'a>>(…)`), so every `impl Visit` that is actually traversed monomorphizes the full transitive closure of walks it reaches — **including all the pure-type `walk_ts_*` functions**. Those TS-grammar walks are ~282 KiB (≈23%) of oxlint's visitor machinery, and `oxc_linter` alone has 44 helper visitors that only care about runtime JavaScript.

Because the generated `walk_js_*` functions never reference the pure-type walks, an `impl VisitJs` monomorphizes **zero** TS type-grammar traversal. A visitor that only inspects JS can switch `impl Visit` → `impl VisitJs` and the TS walks drop out of its binary contribution.
2026-07-14 16:01:16 +00:00
leaysgur b7b82e0a33 fix(formatter): suppressed trailing semi (#24482)
Fixes suppressed statements (`oxfmt-ignore`) emitting their trailing `;` verbatim, which broke idempotency with `semi: false`.

A suppressed statement's span includes its (possibly distant) trailing `;`, so it was printed verbatim. And with `semi: false`, the formatter also prepends an ASI-guard `;` to the next statement, duplicating it (the doubled `;` = extra `EmptyStatement`):

```js
// oxfmt-ignore
while   (   1)   foo (   )

;[].sort()

// BEFORE

// oxfmt-ignore
while   (   1)   foo (   )

;
;[].sort()

// AFTER
// oxfmt-ignore
while   (   1)   foo (   )

;[].sort()
```
2026-07-14 06:28:49 +00:00
leaysgur 25306e9f21 fix(formatter): do not add extra parens with type cast comment (#24444)
Fixes #20182
2026-07-13 07:44:14 +00:00
overlookmotel 19ad4a8c17 refactor(ast): move AstKind::ty out of generated code (#24390)
Follow-on after #24381. `AstKind::ty` is pure static code. It doesn't need to be generated. Move it out of the generated code.
2026-07-11 18:03:25 +00:00
overlookmotel 347c61b410 refactor(ast): use NonNull for ref casting (#24389)
Follow-on after #24381. When casting a `&` reference, it's preferable to use `NonNull` - that safely gains the "pointer is not null" fact, rather than unsafely promising it with `as_ref_unchecked` on a raw pointer.
2026-07-11 18:03:25 +00:00
Boshen a82a400c15 refactor(rust): use stabilized ptr::as_ref_unchecked (#24381)
Rust 1.95 stabilized [`<*const T>::as_ref_unchecked`](https://doc.rust-lang.org/std/primitive.pointer.html#method.as_ref_unchecked) (MSRV bumped in #24359). This replaces the `ptr.as_ref().unwrap_unchecked()` idiom at its 5 call sites:

- `oxc_parser` `lexer/source.rs` `read()`/`read2()` — the doc comment there explains the idiom exists because producing a `&u8` carries `nonnull`/`dereferenceable` LLVM metadata that a raw deref does not (7% lexer speedup). `as_ref_unchecked` is implemented as exactly that reference production (`&*self`), so codegen is unchanged — it just skips the `Option` detour. Comment updated to name the new method.
- `oxc_codegen` `lib.rs` — `</script` search loop.
- `oxc_ast_visit` `utf8_to_utf16/translation.rs` — chunk read.
- `oxc_ast` `generated/ast_kind.rs` `AstKind::ty` — changed via its generator in `tasks/ast_tools`, regenerated with `just ast`.

Verified: `cargo test` for the touched crates, and parser/semantic/codegen conformance (test262 / babel / typescript / misc) with **zero snapshot diffs**. CodSpeed should show this perf-neutral.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-07-11 17:45:52 +00:00
camc314 66e1a25c0e chore(all): fix clippy::manual_assert_eq (#24351) 2026-07-10 09:57:05 +00:00
leaysgur 42ec8de556 fix(formatter): keep comments inside surviving parens and suppressed statement terminators (#24253)
Fix regression found in this series of PRs.
2026-07-07 09:09:55 +00:00
leaysgur 3abbed59d2 fix(formatter): print ; before jsdoc type-cast parens with no-semi (#24208)
Follows https://github.com/prettier/prettier/blob/79f6cdfd9873a91be9b25c9c6a41d26dcd9a6656/changelog_unreleased/javascript/18751.md

```js
;/** @type {string[]} */ (["foo", "bar"]).forEach(doStuff)
```

With `no-semi`, this `;` should stay the same position.

Also fixes minor bugs and idempotency issues.
2026-07-06 06:01:18 +00:00
overlookmotel 846c078eaa refactor(ast_tools): rename AssertLayoutsGenerator (#23990)
Pure refactor. Rename this generator to match the others.
2026-06-30 20:54:26 +00:00
overlookmotel a8f8e9c05c refactor(ast): enum conversion methods use NonNull (#23962)
Refactor. Enum conversion methods (`Expression::as_member_expression` etc) use `NonNull` pointers for casting from one type to another, instead of _const /_ `*mut` pointers.

This is more readable, and may also be slightly more performant - it may allow compiler to remove null pointer checks.
2026-06-30 16:32:04 +00:00
overlookmotel 26dd9e2394 feat(ast): add method to widen inherited enum ref to parent ref (#23961)
We already had conversion methods to narrow from e.g. `&Expression` to `&MemberExpression`.

Add methods to convert in opposite direction - to widen from `&MemberExpression` to `&Expression`.

Note that a method to convert `&mut MemberExpression` to `&mut Expression` is NOT added. This would be unsound, as you'd be able to widen a `&mut MemberExpression` to `&mut Expression`, and assign a `Expression::NullLiteral` to it. This would make the original `&mut MemberExpression` contain an illegal value - there is no such thing as `MemberExpression::NullLiteral`.
2026-06-30 16:32:04 +00:00
overlookmotel be1795021b refactor(ast): remove inherit_variants! macro (#23960)
Remove the `inherit_variants!` macro from `oxc_ast`.

Before:

```rust
inherit_variants! {
    #[ast]
    pub enum Expression<'a> {
        // ... own variants ...
        INHERIT(MemberExpression<'a>),
    }
}
```

After:

```rust
#[ast]
pub enum Expression<'a> {
    // ... own variants ...
    INHERIT(MemberExpression<'a>),
}
```

The way it works now is that `ast_tools` generates the data the `#[ast]` macro needs to add the inherited enum variants, and `#[ast]` macro uses that data to add the variants (from `MemberExpression`, in this case).

This has a few advantages:

- Wrapping enums in `inherit_variants! { ... }` was ugly. Now it's gone.
- `inherit_variants!` macro no longer needs to be maintained by hand. Changes to AST will now be reflected automatically.
- Inherited variants are auto-generated, so it's now statically guaranteed by codegen that variants' "payloads" and discriminants match between the inheriting and inherited enums, so we can remove 2000 lines of const assertions which checked this.
- Better IDE experience. "Jump to definition" on `MemberExpression` in `INHERIT(MemberExpression<'a>)` now jumps to `MemberExpression`'s definition.

Only changes the _means_ by which enum inheritance is implemented, does not change how it operates.
2026-06-30 16:32:04 +00:00
overlookmotel ca13cdd5b4 refactor(ast): calculate which structs are transparent in ast_tools (#23958)
Similar to #23957.

Move work from `#[ast]` macro into `ast_tools`. Calculate whether a struct can be marked `#[repr(transparent)]` or not in `ast_tools` and pass that info to `#[ast]` macro via the `StructDetails` struct. This has 2 advantages:

1. Avoids having to iterate over all fields of each struct in the macro.
2. It's more accurate. `ast_tools` knows the size of fields, so it can exclude ZST fields from the count.

A struct like this will now be marked `#[repr(transparent)]` despite having more than 1 field, because 2 of the fields contain ZSTs:

```rust
struct Foo<'a> {
    num: u32,
    _empty: [u8; 0],
    _marker: PhantomData<&'a ()>,
}
```

Moving work from macro into `ast_tools` is preferable because `ast_tools` runs only after changes to AST, whereas the macro code runs every time `oxc_ast` crate is compiled.
2026-06-30 16:32:03 +00:00
overlookmotel e94b6cde71 refactor(ast_macros): calculate which enums are fieldless in ast_tools (#23957)
Move work from `#[ast]` macro into `ast_tools`. Calculate whether an enum is fieldless or not in `ast_tools` and pass that info to `#[ast]` macro via an `EnumDetails` struct. This avoids having to iterate over all variants of each enum in the macro.

This is preferable because `ast_tools` runs only after changes to AST, whereas the macro code runs every time `oxc_ast` crate is compiled.
2026-06-30 15:45:38 +01:00
overlookmotel 287a0c3819 refactor(ast_tools): move phf::Map generation into a utility function (#23956)
Pure refactor.

Move logic for codegenning a `phf::Map` into a utility function that takes an iterator of `(name, content)` entries.
2026-06-30 15:45:38 +01:00
overlookmotel 084eb3b2a8 refactor(ast): codegen match_*! macros (#23955)
Pure refactor.

`oxc_ast` contained 9 hand-written macros e.g. `match_expression!`. Codegen these macros instead.

The macros are exactly the same as they were before, just they're now codegenned, so will reflect any changes made to AST automatically.
2026-06-30 15:45:38 +01:00
overlookmotel e6d6e5a244 refactor(ast): remove shared_enum_variants! macro (#23952)
Remove the `shared_enum_variants!` macro from `oxc_ast` crate. Generate the same code using `ast_tools` codegen instead.
2026-06-30 15:45:37 +01:00
overlookmotel 6e2205f9de refactor(ast_tools): move article_for to utils module (#23951)
Pure refactor.

`article_for` was duplicated in 2 places. Move it into `utils` module and share the implementation.
2026-06-30 15:45:37 +01:00
overlookmotel a10ac8f2ef refactor(ast_tools): EnumDef::inherits_enums return iterator of &EnumDefs (#23949)
`EnumDef::inherits_enums` returns an iterator of enums that an enum inherits variants from. Inheritance only operates on enums, so make it yield `&EnumDef`s not `&TypeDef`s.
2026-06-30 15:45:37 +01:00
overlookmotel 0ab64ecfbd fix(ast): silence deprecation warnings within files defining deprecated AstBuilder methods (#23889)
Part of #23043.

#23877 added `#[deprecated]` to all legacy `AstBuilder` methods. However, deprecation warnings are firing inside the files which define the legacy methods, because they call each other.

Silence these warnings, so users only get warnings for their own code.
2026-06-28 12:13:18 +00:00
overlookmotel 3800f01863 fix(ast): legacy AstBuilder methods take self not &self (#23891)
Part of #23043.

Intent is that unless consumer enables `disable_old_builder` Cargo feature on `oxc_ast` crate, legacy own methods of `AstBuilder` should work as before, with minimal breaking changes.

#23875 erroneously changed all the legacy methods from taking `self` to `&self` - which is a large breaking change, even without `disable_old_builder` feature enabled.

Revert that change. It's not required, as `AstBuilder` remains `Copy` unless `disable_old_builder` feature is enabled.
2026-06-28 12:02:49 +00:00