mirror of
https://github.com/oxc-project/oxc.git
synced 2026-09-14 19:36:11 +08:00
codex/parser-grouped-expression-allocation
509 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
60fa13878c |
release(crates): oxc v0.149.0 (#26397)
### 💥 BREAKING CHANGES - |
||
|
|
894c8f9cd8 |
release(crates): oxc v0.148.0 (#26198)
### 💥 BREAKING CHANGES - |
||
|
|
4e258430cd |
release(crates): oxc v0.147.0 (#26046)
### 🚀 Features - |
||
|
|
0389c4010e |
release(crates): oxc v0.146.0 (#25912)
### 🚀 Features - |
||
|
|
134fb2228d |
fix(isolated-declarations): preserve unresolved parameter types (#25909)
#25292 made isolated declaration emit report a false TS9025 for a defaulted parameter whose annotation is an indexed access type. This breaks Vite's `UserConfig["base"]` case when Rolldown upgrades Oxc, even though the indexed property already includes `undefined`. Oxc's isolated declaration emitter has no type checker, so it cannot safely resolve indexed accesses, type references, conditional types, or similar annotations. This change uses a syntax-only classification: widen or diagnose types whose syntax is conclusive, and preserve checker-dependent types rather than emit an unstable error. The union/intersection rules retain the distinct behavior of `any`, `never`, `unknown`, and `void`, and an explicit `undefined` union member is never duplicated. Regression coverage includes the Rolldown/Vite case and a compact matrix of the relevant type algebra. The implementation and tests were reduced to one commit after review. Validated with the isolated-declarations tests, strict Clippy, the all-features workspace suite, lint, docs, AST generation, and TypeScript 5.6.1-rc/6.0.3 type-algebra checks. The full local `just ready` run stopped only at a runner-dependent source-map snapshot because the repository expects Node 26.5.0 and this machine has 25.8.1; the remaining suite passed with that and the known terminal-color snapshot group skipped. Follow-up to #25292. Related Rolldown failure: https://github.com/rolldown/rolldown/actions/runs/32158377691/job/95782148042?pr=10707 Disclosure: this change was prepared and verified with AI assistance (Codex). |
||
|
|
4a4a1fb2e7 |
release(crates): oxc v0.145.0 (#25865)
### 💥 BREAKING CHANGES - |
||
|
|
53f9295ac5 |
fix(isolated-declarations): preserve undefined for defaulted any (#25292)
## Summary - emit `any | undefined` for default-initialized `any` parameters followed by required parameters - add a regression fixture for #25290 and update the existing parameter snapshot - preserve the existing handling of `unknown` Closes #25290 |
||
|
|
b846abc2a4 |
fix(isolated-declarations): handle ambient expando properties (#25655)
## Summary - treat value declarations in ambient namespaces as implicitly exported for expando-function validation - preserve local versus exported namespace visibility to match TypeScript declaration merging - add regression coverage for accepted ambient value declarations and rejected local or type-only declarations ## TypeScript compatibility The regression fixture was checked with TypeScript 6.0.3. TypeScript and Oxc produce TS9023 only for the exported-function/local-namespace mismatch and the type-only namespace property. Closes #24975 |
||
|
|
b6f1458dec |
release(crates): oxc v0.144.0 (#25474)
### 💥 BREAKING CHANGES - |
||
|
|
030857b509 |
refactor(diagnostics): update oxc-miette to v4 (#25395)
Remove the miette `Report` wrapper and update oxc-miette to v4's narrowed diagnostic and source protocol. AI-assisted by OpenAI Codex. |
||
|
|
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
|
||
|
|
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
|
||
|
|
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
|
||
|
|
45a17c25d1 |
release(crates): oxc v0.143.0 (#25250)
### 💥 BREAKING CHANGES - |
||
|
|
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
|
||
|
|
468b970613 |
fix(isolated-declarations): strip re-export attributes (#25090)
## Summary - transform declaration-less named exports instead of cloning them unchanged - preserve re-export specifiers, source, and type modifiers while omitting import attributes from declaration output - retain existing empty-export marker behavior Stacked on #25089. |
||
|
|
ecc666a4b1 |
test(isolated-declarations): cover re-export syntax (#25089)
## Summary - cover statement-level and specifier-level type-only re-exports - cover string-named export specifiers - cover re-exports using import attributes |
||
|
|
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
|
||
|
|
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)`. |
||
|
|
e07718f887 |
perf(isolated_declarations): avoid large types on stack (#25036)
Continuation of #25034. In isolated declarations, 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. |
||
|
|
fc702c1fa9 |
release(crates): oxc v0.142.0 (#24977)
### 💥 BREAKING CHANGES - |
||
|
|
e935064d33 |
refactor(ast_visit): migrate JS-only mutable visitors (#24849)
## Summary Migrate seven runtime-only `VisitMut` implementations to `VisitJsMut`. This avoids monomorphizing pure TypeScript type-space traversal for these consumers. ## Binary size On arm64 macOS, `cargo build --release -p oxc_transform_napi` produces a stripped dylib that changes from 3,365,584 to 3,332,480 bytes: **-33,104 bytes (-0.98%)**. Its Mach-O `__text` section changes from 2,909,108 to 2,876,892 bytes: **-32,216 bytes (-1.11%)**. AI assistance was used to implement this change. I reviewed and take responsibility for the result. <sub>Stack created with <a href="https://github.com/github/gh-stack">GitHub Stacks CLI</a></sub> |
||
|
|
baf022d74d |
refactor(ast)!: remove old AstBuilder methods (#24563)
Closes the migration tracked in #23043. The new builder API (methods on the AST types themselves) has been in place for a while, all Oxc crates are migrated, and every crate which uses `AstBuilder` already enables `disable_old_builder` in its tests. The module docs said "after a few weeks, we will remove `AstBuilder`'s own methods entirely" — this does that. ## What's removed - `crates/oxc_ast/src/generated/ast_builder.rs` and `builder/methods.rs` — `AstBuilder`'s own node and primitive constructors. - `AstBuilderGenerator`, which produced the former. - The `disable_old_builder` Cargo feature, plus the dev-dependency hacks it required across 33 manifests. Eight crates depended on `oxc_ast` *purely* to turn the feature on; those deps and their `cargo-shear` ignore entries are gone too. ## One thing worth reviewing `AstBuilderGenerator` owned the `#[builder(default)]` / `#[builder(skip)]` attr registration that `BuilderMethodsGenerator` only *reads* — its doc comment said as much. Deleting it naively would have silently dropped `skip`/`default` handling for the 12 AST types using those attrs, so `attrs()` and `parse_attr()` move to `BuilderMethodsGenerator`. The evidence this was faithful: after regenerating, `builder_methods.rs` and every other generated file are **byte-identical**. The only generated change in the diff is the deletion. ## Breaking changes - `AstBuilder`'s own methods are gone. Use the AST type methods: `Statement::new_expression_statement(span, expr, &builder)` instead of `builder.statement_expression(span, expr)`. - `AstBuilder` is no longer `Copy`/`Clone`, and its `allocator` field is private — use the `allocator` method from `GetAllocator`. - `AstBuilder` and `NONE` are no longer re-exported from the crate root; import them from `oxc_ast::builder`. (The `disable_old_builder` feature already gated these off, so nothing in-tree used the root path.) - The `disable_old_builder` feature no longer exists — remove it from any manifest that sets it. A migration note covering all of the above is in `crates/oxc_ast/src/builder/mod.rs`. --- Implemented with AI assistance (Claude Code); design, review, and verification driven by the author per the repo AI-usage policy. |
||
|
|
14533a3dc1 |
release(crates): oxc v0.141.0 (#24714)
### 💥 BREAKING CHANGES - |
||
|
|
99b1dc4773 |
fix(isolated-declarations): separate static accessors (#24716)
## Summary - keep static and instance accessors with the same key in separate inference pairs - retain property-key normalization while adding staticness to the pairing identity - cover both instance-getter/static-setter and static-getter/instance-setter directions ## Root cause Accessor pairing identified entries by property key alone. A same-named accessor in the opposite staticness domain could therefore provide an inferred type, even though TypeScript treats the static and instance properties independently. [TS Playground](https://www.typescriptlang.org/play/?isolatedDeclarations=true) Current behaviour (oxc is before, ts (correct) is after. ```diff @@ -1,8 +1,8 @@ declare class InstanceGetterStaticSetter { - get value(): string; + get value(); static set value(value: string); } declare class StaticGetterInstanceSetter { - static get value(): number; + static get value(); set value(value: number); } ``` |
||
|
|
0e9b46079a |
fix(isolated-declarations): normalize accessor keys (#24690)
## Summary - normalize equivalent static accessor keys before getter/setter pairing - retain structural matching for computed symbol keys - cover identifier/string and numeric/template accessor pairs ## Root cause Accessor pairing compared the AST key nodes structurally, so equivalent JavaScript property names expressed with different key syntax did not match. [TS Playground](https://www.typescriptlang.org/play/?isolatedDeclarations=true#code/MYGwhgzhAEDCD2BbADgVwC4FMAmBldATgJYB2A5gOKbpYHQDeAUNNGddAG5giqYAUASgbMW0AtVQES0AERcemGQG4RAXxER2AbTndeMgLp8OQpi3XrGoSDAQoMOfMXK5qtAEzCWbdNB3B4VBJ0Q0EvUXF0SWkZEnh0AFp0AAtMBOxMawIwdCJ4EiSAT2RFFXMNdgCg9GMALmgSVEQAI0wCUzVGS2soOCQ0LGwAOSa2omAqGjaAZnCfPwBGIw7RMQkpaEJeMuh1Fk1fLQADBaOjE3CLRiA) Current behaviour (oxc is before, ts (correct) is after. ```diff @@ -1,12 +1,12 @@ declare class ComputedStringGetter { get value(): string; - set ["value"](v); + set ["value"](v: string); } declare class ComputedStringSetter2 { - get ["count"](): string; + get ["count"](): number; set count(v: number); } declare class ComputedNumericGetter3 { get [1](): boolean; - set [`1`](v); + set [`1`](v: boolean); } ``` |
||
|
|
aeeccac8db | refactor(isolated-declarations): simplify literal key check (#24691) | ||
|
|
a2ef382fe6 |
fix(isolated-declarations): reject window.Symbol as global symbol reference (#24689)
## Summary - Align computed property validation with TypeScript: allow Symbol and globalThis.Symbol keys, but reject window.Symbol keys. - Add an isolated-declarations regression fixture covering all three forms. [TS Playground](https://www.typescriptlang.org/play/?isolatedDeclarations=true#code/FAUwHgDg9gTgLgAgMYBsCGBnDCDKBPAWwCMoUBpEPDARgQG9gEEBtfY0gOgEs4QY04sALoAKAJQAuBAEle-QTAA8AVwB2Aa1VQA7qoB89RkwQwQcZTFUshrQiRTc5A4eIDcRgL7Av4aPGToWLh2pBRUAEyGTMwA5ihQRGgoACoAFlwYHGz2jnzOMKKSMk4KKhpaugYMxiZmFlbMNtmcPHkKhe5MXj6QsIiomNjN5JQYAMxRLNpcqgAmOlkhDq3yLkWybbBlmjr6k0ym5pbWtuzLJWudCN1AA) |
||
|
|
c06982adff |
refactor(ast_visit, isolated_declarations, minifier, transformer): pass array literals for from_array_in builder args (#24624)
Pure refactor. Just shorten code. #24621 made AST builder methods take arrays as params. Replace `ArenaVec::from_array_in(...)` arguments written inline at AST builder method call sites with the array literals. ```rs let block = Statement::new_block_statement(SPAN, ArenaVec::from_array_in([x, y], ctx), ctx); ``` \-> ```rs let block = Statement::new_block_statement(SPAN, [x, y], ctx); ``` |
||
|
|
b28051fb12 |
refactor(all): pass [v] for single-value ArenaVec builder args (#24623)
Pure refactor. Just shorten code. #24621 made AST builder methods take arrays as params. Replace `ArenaVec::from_value_in(v, alloc)` with `[v]` where the `ArenaVec` is passed to an AST builder method. ```rs let block = Statement::new_block_statement( SPAN, ArenaVec::from_value_in(return_stmt, ctx), ctx, ); ``` \-> ```rs let block = Statement::new_block_statement(SPAN, [return_stmt], ctx); ``` |
||
|
|
c1a074cdc8 |
refactor(all): pass [] for empty ArenaVec args to AST builder methods (#24622)
Pure refactor. Just shorten code. #24621 made AST builder methods take arrays as params. Replace `ArenaVec::new_in(...)` arguments written inline at AST builder method call sites with the empty array literal `[]`. ```rs let block = Statement::new_block_statement(SPAN, ArenaVec::new_in(ctx), ctx); ``` \-> ```rs let block = Statement::new_block_statement(SPAN, [], ctx); ``` |
||
|
|
b14d52e647 |
refactor(parser): move require after optional param check to parser (#24600)
## Summary - report TS1016 while parsing formal parameter lists - preserve initialized-parameter and rest-parameter behavior - remove the equivalent semantic check and diagnostic - update parser and semantic coverage snapshots Follows #24596, which has now merged. |
||
|
|
8e0ed2ebb9 |
release(crates): oxc v0.140.0 (#24453)
### 🚀 Features - |
||
|
|
f17514b21c |
fix(isolated-declarations): emit const readonly fields as types (#24288)
## Summary Fixes #24284. TypeScript 6.0.3 emits bare readonly literal class fields as value initializers, but emits readonly `as const` literal fields as type annotations. Oxc previously unwrapped `as const` in the readonly class-property initializer path, so `static readonly A = "a" as const` became `static readonly A = "a";` instead of `static readonly A: "a";`. This changes the readonly class-property handling to preserve value initializers only for bare literals. Const assertions now flow through the existing const-expression type conversion. |
||
|
|
69b2dfc181 |
release(crates): oxc v0.139.0 (#24218)
### 🚀 Features - |
||
|
|
a4db73113e |
perf(isolated_declarations): use ReplaceWith instead of TakeIn (#24016)
Replace `take_in` with `replace_with` in as many places as possible in isolated declarations. See #24012 for explanation of the advantage of `replace_with`. |
||
|
|
8bf69a3076 |
release(crates): oxc v0.138.0 (#23934)
### 💥 BREAKING CHANGES - |
||
|
|
9f6ee3b41a |
perf(isolated-declarations): pool scope maps to avoid per-scope alloc/rehash (#23761)
## What `ScopeTree` (resolves declaration references during `.d.ts` generation) walks a scope **stack**, allocating two fresh `FxHashMap`s on every `enter_scope` and dropping them on leave. ## Why For type-heavy files this is an allocate → grow → rehash → free cycle *per scope*, repeated thousands of times. Profiling the isolated-declarations benchmark (`vue-id.ts`) attributed ~10% of the run to this HashMap churn (`reserve_rehash` 4.4% + entry churn + alloc/free). ## How Scopes are entered and left in a stack pattern, so recycle them: on leave, `drain` / `clear` the maps (which keeps their heap buffer and capacity) and push the scope onto a free-list; on enter, reuse a pooled scope instead of allocating. Behaviour is identical — the maps still start empty. ## Result Interleaved A/B on the isolated-declarations benchmark (`vue-id.ts`): **~13.6% faster** (~5.24 ms → ~4.53 ms, consistent across 3 rounds). All 20 unit + snapshot tests pass (snapshots byte-identical); `cargo clippy` clean. --- _Disclosure: developed with AI assistance (Claude Code). Benchmarks, tests and clippy were run and the change reviewed._ |
||
|
|
b4f5b4b20f |
perf(isolated_declarations): remove redundant clone of formal parameter pattern (#23912)
## What
`transform_formal_parameter` first builds an **owned** `pattern` (a deep
`clone_in` of the source binding pattern) and mutates it in place:
```rust
let mut pattern = if let BindingPattern::AssignmentPattern(pattern) = ¶m.pattern {
pattern.left.clone_in(self.allocator())
} else {
param.pattern.clone_in(self.allocator())
};
FormalParameterBindingPattern::remove_assignments_from_kind(self, &mut pattern);
```
In the inferred-type / optional branch it then cloned that already-owned
`pattern` a **second** time into the arena and returned, dropping the
original:
```rust
return Some(FormalParameter::new(
param.span,
ArenaVec::new_in(self),
pattern.clone_in(self.allocator()), // <- redundant: `pattern` is owned and dropped right after
...
));
```
The sibling branch just below already moves `pattern` directly into the
identical `FormalParameter::new`, so the second clone is redundant. This
PR moves the owned `pattern` in instead.
## Behaviour
Unchanged. The output is a freshly-built AST that is codegen'd to a
`.d.ts` string (no symbol identity involved), so moving the
already-cloned, already-mutated pattern is equivalent to cloning it
again. `cargo test -p oxc_isolated_declarations` passes with no `.d.ts`
snapshot changes.
## Evidence
Removing the redundant clone is a deterministic reduction in arena
allocation. Measured over `vue-id.ts` (the `bench_isolated_declarations`
corpus), the output `Allocator::used_bytes()` after
`IsolatedDeclarations::build` drops:
```
19,594,584 -> 19,530,256 bytes (-64,328)
```
This is reproducible (it depends only on the code path taken, not on
machine state). It removes genuine work — fewer arena allocations, node
copies and drops — rather than reordering existing work. CodSpeed cycle
counts may read flat, as this is a small fraction (~0.3%) of the build.
---
*Disclosure: this change was prepared with AI assistance (Claude). I
reviewed, tested and verified it myself — behaviour is byte-identical
(insta snapshots unchanged) and the `used_bytes` reduction above was
measured locally before/after.*
|
||
|
|
53d083f080 |
perf(isolated_declarations): use TakeIn not CloneIn (#23847)
There's no need to clone the node here. Both call sites already cloned the entire `BindingPattern` before calling `remove_assignments_from_kind`. Instead use `take_in`, which is much cheaper if the pattern is deeply nested. `BindingPattern::take_in` is cheaper than `AssignmentPattern::take_in`, so do it that way around. https://github.com/oxc-project/oxc/blob/84a5aa3246bf514d6544384bbb577cd989c9d3b9/crates/oxc_ast/src/generated/derive_dummy.rs#L1071-L1078 https://github.com/oxc-project/oxc/blob/84a5aa3246bf514d6544384bbb577cd989c9d3b9/crates/oxc_ast/src/generated/derive_dummy.rs#L1080-L1092 |
||
|
|
3d61dea7e2 |
docs(all): correct capitalization in comments (#23887)
Just fix capitalization in comments. |
||
|
|
94fbacb524 |
feat(ast)!: only export AstBuilder and NONE in builder module (#23876)
Part of #23043. Do not export `AstBuilder` and `NONE` from root of `oxc_ast` crate. They are available via `builder` module e.g. `use oxc_ast::builder::NONE;`. We're going to be adding more AST builder types, and I don't think we should throw them all into the crate's top-level exports. This change is behind the `disable_old_builder` Cargo feature (see #23875) so is not an immediate breaking change - consumers would need to make this change while migrating to new `AstBuilder`. |
||
|
|
f2091b3846 |
feat(ast): unify old and new AstBuilders (#23875)
Part of #23043. Recent PRs have introduced a new `AstBuilder` type, and migrated all Oxc's crates over to it. To allow downstream consumers to incrementally migrate from old `AstBuilder` to new one, unify the two implementations into a single `AstBuilder` type. Remove the `builder_new` Cargo feature and instead add a new feature `disable_old_builder`. When enabled, this feature removes all the methods from `AstBuilder`, and makes `AstBuilder` non-`Copy`. Consumers should migrate, and then enable this feature to ensure later changes don't use the methods which are slated for removal. Enable the `disable_old_builder` feature in all Oxc crates which use `AstBuilder` _in tests_. This ensures we don't go back to using the deprecated methods, while not forcing the feature on consumers (e.g. Rolldown) who use e.g. `oxc_transformer` but also use `AstBuilder` directly. |
||
|
|
83b64d8508 |
refactor(ast): hide allocator field of new AstBuilder (#23874)
Part of #23043. Do not expose `allocator` field of new `AstBuilder`. It's available via `allocator` method provided by `GetAllocator` trait. This gives us more flexibility in future. |
||
|
|
b274d8ff8d |
refactor(isolated_declarations): switch to new AstBuilder (#23839)
Part of #23043. Migrate `oxc_isolated_declarations` over to new `AstBuilder`. |
||
|
|
8c6481bbf9 |
refactor(allocator): export ArenaBox and ArenaVec aliases (#23746)
Pure refactor.
It's a common pattern in repo to import `Vec` and `Box` etc from `oxc_allocator` and alias them to `ArenaBox` / `ArenaVec`.
```rust
use oxc_allocator::{Box as ArenaBox, Vec as ArenaVec};
```
Make this pattern less verbose by re-exporting `Box` and `Vec` as these aliases from `oxc_allocator`.
Ditto other arena types: `ArenaHashMap`, `ArenaHashSet`, `ArenaStringBuilder`.
Use those aliases in all other crates to shorten code.
```rust
use oxc_allocator::{ArenaBox, ArenaVec};
```
|
||
|
|
d02588770d |
perf(isolated_declarations): allocate AST nodes in arena directly (#23709)
Small perf optimizations around AST builder calls. - AST nodes which end up in `Box`es, allocate into the `Box` as early as possible, to increase chance compiler sees the type can be built directly in arena, rather than built on the stack, and then copied from stack into arena. - Functions return `Box<T>` rather than `T` where the value needs to be boxed anyway. If function is not inlined, this avoids stack allocation + copy. Also, shorten code where possible by using more specific `AstBuilder` methods. In `transform_formal_parameters`, clone the `Box<FormalParameters>` instead of cloning `FormalParameters` on stack and then moving it into a `Box`. |
||
|
|
36009ddf68 |
feat(allocator)!: GetAllocator::allocator take &self (#23676)
Make `GetAllocator::allocator` method take `&self`, instead of `self`. Alter `TakeIn::take_in` accordingly - now takes `&A` where`A:GetAllocator` (previously took owned `A`). This requires changing every `take_in` / `take_in_box` call site. However, this change also means that `GetAllocator` can be implemented on all types which hold an `&Allocator` e.g. `TraverseCtx`, which shortens the code everywhere. - Before: `expr.take_in(ctx.ast)` - After: `expr.take_in(ctx)` Change all those call sites to this shorter version. This matches the pattern that new `AstBuilder` is going to introduce (#23043), and clears the way for that change. |
||
|
|
6d56d57535 |
release(crates): oxc v0.137.0 (#23591)
### 💥 BREAKING CHANGES - |
||
|
|
827248a7fb |
release(crates): oxc v0.136.0 (#23443)
### 💥 BREAKING CHANGES - |