```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
```
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.
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.
## 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
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>
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
## 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
Pure refactor. `ast_tools` generators use plain doc comments instead of the the more verbose `#[doc = "..."]` where the comment can be written statically.
## 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
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").
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)`.
## 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.
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.
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.
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.
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.
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.
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.
## 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.
## 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.
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()
```
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.
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)
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.
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`.
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.
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.
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.
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.
`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.
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.
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.