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
Fixes#25705
---
JS formatter drops `;` (`EmptyStatement`) in a statement list.
However, this raises a question: how are the surrounding comments handled when it is dropped?
This happens because when the comment cursor retrieves that information from the `span`s of the preceding and following nodes, the `EmptyStatement`, which no longer exists, was the one reporting that position.
Found by real-world repo testing for `operatorPosition`.
- Check the nearest nested `await` is enough
- `needs_parens` check is not needed
Now aligned with Prettier's output.
NOTE: For now, behavior is the same as Prettier.
- Default is `end`
- This works on intersection(`&`) type, but has no effect on union(`|`) type
For future refs: https://github.com/oxc-project/oxc/issues/16367
Prettier uses the `conditionalGroup` IR for JS and YAML.
When this appeared in JS(containing `conditionalGroup`)-in-HTML(`script` tags)-in-JS (`oxc_formatter`), we previously bypassed the `Doc` to IR conversion.
This PR adds support for this conversion and removes the string format fallback path.
Fixes#25410
Syntactically, this `this` is written inside the function parameters.
However, in the AST, `this_param` and `params` are adjacent fields, and since the inclusion relationship of the spans is broken, exception handling is required.
Pure renaming.
- `DispatchOutcome` -> `DispatchResponse` (counter part of `DispatchRequest`)
- `DispatchResult` -> `DispatchPayload` (lives in `DispatchResponse`)
As JSDoc is always contained within the ` * ` fenced comments, the `printWidth` option should not have been used as is.
The dedicated `jsdocPrintWidth` option has not yet been implemented. #24633
Due to historical reasons, the IR was passed as `[Doc, (Doc, ...Doc)]` only when processing `gql-in-js`, whilst for other cases such as `css-in-js` and `html-in-js`, it was always `[Doc]`.
Now, every result is single `Doc`.
- dispatcher
- string_embedder
- tailwind_sorter
Group these 3 together as `SessionServices`, make them available to `FormatSession`, and pass them around.
In the embedding format, issuing a `DispatchRequest` returns a `DispatchOutcome`.
Additionally, by managing the nesting depth, made it possible to limit the number of recursion.
## 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
## Summary
Narrow `TSInterfaceHeritage` from a general JavaScript `Expression` to `TSTypeName`.
```rust
pub struct TSInterfaceHeritage<'a> {
pub type_name: TSTypeName<'a>,
pub type_arguments: Option<Box<'a, TSTypeParameterInstantiation<'a>>>,
}
```
## Breaking Change
The Rust AST field changes from:
```rust
expression: Expression<'a>
```
to:
```rust
type_name: TSTypeName<'a>
```
## Why?
The current AST can represent shapes such as:
```ts
interface A extends foo() {}
interface A extends A + B {}
interface A extends new Foo() {}
interface A extends true {}
```
Which is an overly wide type since all of the above variants are invalid.
This PR tightens up the AST, making these invalid variants irrepresentable.
## How to migrate?
This should be a trivial migration:
1. instead of accessing `expression` on `TSInterfaceHeritage`, now access `type_name`
2. change any pattern matching on the `type_name` field to use `TypeName` (this is a smaller subset so should allow deleting code!)
closes https://github.com/oxc-project/backlog/issues/215
## 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
Fixes#21916, As it turned out, it served as a follow-up to #16320.
---
When a `group` inside a `fill` item re-measures (after a preceding hard line reset `measured_group_fits`), the fits walk escapes the item's entry and measures the following separator with the fill's own expanded mode, materializing expanded-only content like the raw JSX whitespace `{" "}` and over-counting the width.
Prettier avoids this because its command stack holds the decided separator command before the item prints.
The fix: `print_fill_item` exposes that mode via `PrinterState::fill_separator_mode`, and the fits walk applies it (single-shot) to the first entry at the printed fill's own level, exactly that separator.
Skipping the re-measure instead (as Biome upstream does unconditionally) was rejected: it was removed in #16320 for diverging from Prettier.
NOTE: As noted in comment, there is a known limitation for now. This PR covers only 1-level nested fill. But in practice, I couldn't find actual code snippet hits this.
## Summary
- add focused formatter coverage for declaration-bearing exports, local
named exports, and source re-exports
- cover comment attachment and prettier-ignore behavior for each export
shape
- include TypeScript type exports and re-export attributes
- covers edge cases to prep for #25095