Commit Graph

349 Commits

Author SHA1 Message Date
leaysgur 70918a3448 refactor(formatter): make is_cast_target position-based (#26431)
We use `preserve_parens: false`, need to detect typecast `()` from source, always.
2026-09-08 06:02:02 +00:00
leaysgur b26bf56cd5 refactor(formatter): route every assignment RHS shape rule through a cast-aware accessor (#26430)
Follow up on #26429, apply cast-aware behavior to assignment call sites.
2026-09-08 06:02:01 +00:00
leaysgur 33cdc841ac fix(formatter): keep cast-target assignment and commented parameter patterns (#26429)
Fixes #26425, #26426.

- Exclude cast target from never-break check
- Use position based comment check
2026-09-08 06:02:01 +00:00
overlookmotel 66744f0eb9 feat(parser)!: rename panicked to fatal_error in ParserReturn (#26382)
`ParserReturn` contains a property `panicked`.

The name `panicked` is misleading. The parser did _not_ panic. When `true`, it means that the parser encountered a fatal error and is unable to parse the source, because it contains an unrecoverable syntax error.

Rename this property to `fatal_error`, to better describe what it means.

This is a breaking change, but one that's trivial to fix in downstream code - the property doesn't change its meaning or behavior, just its name.

All the changes in this PR in other crates are just reacting to this change and making everywhere use the term "fatal error" instead of "panicked", so the codebase is consistent.
2026-09-07 09:41:52 +00:00
leaysgur f7acdc0bd9 fix(formatter): treat a JSDoc cast target as opaque in chain layouts (#26375)
Fixes #20181
2026-09-07 04:56:37 +00:00
leaysgur 104061b98a fix(formatter): keep JSDoc cast parens with a comment inside them (#26374)
Fixes #26274, #26275.
2026-09-07 04:56:37 +00:00
leaysgur b3613b176a test(oxfmt): use svelte repo for jsdoc conformance fixture (#26371)
Add `svelte` repo for JSDoc conformance.
2026-09-07 04:56:36 +00:00
leaysgur 0f4af5d7df test(formatter_tests): assert fingerprint survives formatting (#26322)
Historically, only JS formatter has had the `detect_code_removal` feature, which uses CI to check for missing nodes or comments.

While this would be useful in other languages (especially CSS), but setting up the mechanism is difficult. For now, just start with something simple that just guarantees the number of comments.

That said, even with a mechanism in place, it can't detect anything unless the cases are fully covered by fixtures, so this only tells us that the current state is problem-free.
2026-09-04 07:14:53 +00:00
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 5ff57f83c0 fix(formatter/sort-imports): handle custom side effect groups (#26217)
Fixes #26165

- `side_effect` is predefined selector, it cannot be used as custom group name
- respect custom group which uses `side_effect` as side-effect-import correctly
- fix metadata detection bug
2026-09-01 03:56:36 +00:00
leaysgur 2f4d759717 fix(formatter): do not skip cast comment lookup (#26216)
Fixes #26202

---

The trigger for this issue is the presence of a suppressed typecast.

```js
// oxfmt-ignore
const cast = /** c */ (x)
```

Because a comment had already been printed in a subsequent typecast check without going through the casting mechanism, it failed to find its own cast comment.
2026-09-01 03:56:35 +00:00
leaysgur 1213e12c0e fix(formatter): preserve comment for statement terminators and binary casts (#26213)
Refine comment position preserving rules, for statements and binary casts. Fixes idempotency issues for several sites.

---

Trailing is the same-line run only.
A newline-preceded comment is never trailing. It defers to the next node's leading pass, staying own-line with its blank lines.

Comments around `as`/`satisfies` keep their source side and line-start side, within the grammar-defined slots (line terminator is not allowed between `as` and before).
2026-09-01 03:56:35 +00:00
leaysgur f94b22296c test(formatter,formatter_css,formatter_json,formatter_yaml): restore coverage lost in fixture consolidation (#26129)
- Restore wrongly consolidated fixtures
- State new fixtures policy and apply
- Check `endOfLine` option and output at test harness level
2026-08-27 06:12:52 +00:00
leaysgur 2dba883bf2 test(formatter): consolidate redundant JS fixtures (#26126)
Consolidate redundant fixtures.
2026-08-27 05:34:00 +00:00
leaysgur 2d57487a30 test(formatter): consolidate redundant TS fixtures (#26125)
Consolidate redundant fixtures.
2026-08-27 05:34:00 +00:00
leaysgur 181953b92d docs(oxfmt,formatter_core,formatter,formatter_yaml,formatter_css,formatter_graphql): extract DIVERGENCES.md out from AGENTS.md (#26121)
- Extract "known divergences" section from `AGENTS.md` to explicit `DIVERGENCES.md` with code example
- Update all anchors
- Add missing pinned fixtures
2026-08-27 05:33:58 +00:00
leaysgur e4298fb23a fix(formatter/jsdoc): follow CommonMark for interrupting lists and guard wrapping from creating them (#26098)
Fixes #25825, #26026.

---

If the formatter adjusts the wrapping and causes list-like characters to appear at the beginning of a line, those characters will be mistakenly converted into a list in the next formatting step.

Therefore, it is necessary to guard meaningful characters so that they do not appear at the beginning of a line.

Also, fixed an issue where character that should have originally been interpreted as lists were not, aligning with the CommonMark behavior.
2026-08-26 07:11:32 +00:00
leaysgur a1e21c2573 fix(formatter): apply head body policy everywhere (#26074)
Covered more cases like `label` / `case`.
2026-08-25 09:28:13 +00:00
leaysgur 676b7e19e5 fix(formatter): keep comments in their for-head slot, before an empty-statement body and do-while (#26073)
Keep comments inside `for (init; test; update)`.
2026-08-25 09:28:12 +00:00
leaysgur 3bbcea6d9c fix(formatter): keep comments between a head and its body out of the braces across all constructs (#26071)
Fix inconsisntet and also non-idempotent formatting around:

```js
  m3(tagName: string, rect: number[]): void // THIS should stay
  {
    // ...
  }
```
2026-08-25 09:28:12 +00:00
leaysgur 57537a2223 fix(formatter): place comments between a head and open paren (#26058)
Fixes inconsistency and idempotency issue for control statements.

e.g.

```js
while (x // THIS should stay here
) {
  // ...
}
```
2026-08-25 09:28:11 +00:00
leaysgur e2d53e7f18 fix(formatter): place comments between a statement head and its body (#26043)
Closes #25832 and more idempotency issues.
2026-08-25 09:28:11 +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 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
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
leaysgur 2465e87905 fix(formatter): keep comments in place when dropping EmptyStatement (#25730)
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.
2026-08-16 02:26:11 +00:00
leaysgur 7e4f3b71cf fix(formatter): print JSDocUnknownType (#25729)
Fixes #25704
2026-08-16 01:52:21 +00:00
leaysgur 5238d9ee10 fix(formatter): keep nested awaited-paren member chain grouped in call arguments (#25646)
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.
2026-08-14 08:12:16 +00:00
leaysgur 2adb8fb329 feat(formatter): implement operatorPosition option (#25581)
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
2026-08-14 08:12:16 +00:00
leaysgur 9fa763702b test(formatter): normalize jsdoc fixture paths for Windows ignore matching (#25574)
Final fix for Windows CI failure.
2026-08-13 03:37:27 +00:00
leaysgur 7f350d166f fix(formatter): keep unary comment and print only needed parens (#25526)
Fixes #25494
2026-08-12 05:36:17 +00:00
leaysgur 4544183058 test(formatter_tests,formatter,formatter_css): always track idempotency violations in snapshots (#25512)
Trackable easier than before.
2026-08-12 02:25:55 +00:00
leaysgur b297fb1cb8 refactor(formatter,formatter_tests): fix up (#25478)
Cleaning up after the series of migrations.

- Docs clean up
- More strict `jsdoc` option handling in fixture tests
2026-08-12 00:48:33 +00:00
leaysgur 0fa91283fd refactor(formatter,formatter_tests): move out prettier JS,TS,JSDoc conformance from tasks/ (#25477)
Migration from tasks/prettier_conformance to each crate test using oxc_formatter_tests, for JS/TS and JSDoc.
2026-08-12 00:48:32 +00:00
leaysgur 6e1b4ddceb refactor(formatter_core,formatter_tests): extract oxc_formatter_tests crate from oxc_formatter_core (#25465)
- Extract `test_harness` feature as crate `oxc_formatter_tests`
- This crate will also provide infra for `tasks/prettier_conformance` later
2026-08-12 00:40:59 +00:00
leaysgur b63ecccb1f fix(formatter): keep comments after TS this_param (#25459)
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.
2026-08-10 02:52:32 +00:00
leaysgur 00f490d7f7 refactor(oxfmt,formatter): split sortImports validation and use type enum (#25432)
Reduce runtime validation.

Also, by using type `enum`, IDE and TS types are also covered.
2026-08-09 15:07:41 +00:00
leaysgur d9749a747d refactor(formatter_core,formatter): enforce finalize-once behind the print (#25425)
Follow the rule, #25328.
2026-08-09 14:59:36 +00:00
leaysgur 465b723d04 refactor(formatter,oxfmt): unify host services as SessionServices on FormatSession (#25388)
- dispatcher
- string_embedder
- tailwind_sorter

Group these 3 together as `SessionServices`, make them available to `FormatSession`, and pass them around.
2026-08-09 14:32:10 +00:00
leaysgur 52b146f025 refactor(formatter_core): share split_bom helper (#25333)
In order to standardise the handling of front matter, it was also necessary to standardise the handling of the BOM, which may appear before the FM.
2026-08-09 13:06:35 +00:00
leaysgur ef1d04be86 fix(formatter): break mapped type brackets (#25297)
Follow up #25296, same cases but for mapped type.
2026-08-05 04:25:24 +00:00
leaysgur 4e6f3f1356 fix(formatter): break index signature brackets (#25296)
Fixes #25286
2026-08-05 04:25:23 +00:00
leaysgur c29b587dbf fix(formatter_core): measure decided-flat fill separator as flat during group re-measure (#25276)
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.
2026-08-04 06:39:46 +00:00
leaysgur 96a9744198 refactor(formatter_core): export apply_core_options for tests (#25139)
The same as #25138, for fixture tests.
2026-07-31 08:11:23 +00:00
Cameron f766c2187d test(formatter): cover export declaration shapes (#25096)
## 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
2026-07-30 14:38:21 +09:00
camc314 c917f204a7 feat(ast)!: introduce ArrowFunctionBody enum (#24987)
## Summary

- Introduces `ArrowFunctionBody` to represent concise expression and block bodies explicitly.
- Removes the `ArrowFunctionExpression::expression` boolean.
- Makes invalid arrow body combinations unrepresentable.

## Breaking Change

The Rust AST changes from:

```rust
pub struct ArrowFunctionExpression<'a> {
    pub expression: bool,
    pub body: Box<'a, FunctionBody<'a>>,
}
```

to:

```rust
pub enum ArrowFunctionBody<'a> {
    FunctionBody(Box<'a, FunctionBody<'a>>) = 64,
    INHERIT(Expression<'a>),
}

pub struct ArrowFunctionExpression<'a> {
    pub body: ArrowFunctionBody<'a>,
}
```

Concise arrow bodies are now stored directly as expressions instead of synthetic `FunctionBody` and `ExpressionStatement` nodes. Block bodies continue to use `FunctionBody`.

## Why?

The previous representation required consumers to maintain this implicit invariant:

```text
expression == true
→ no directives
→ exactly one statement
→ that statement is an ExpressionStatement
```

Invalid combinations could cause panics, skipped work, or fallback behavior. The enum encodes the grammar directly and makes those invalid states unrepresentable.

Concise expressions also now have `ArrowFunctionExpression` as their direct traversal ancestor, without synthetic `FunctionBody` or `ExpressionStatement` nodes.

## How to migrate

Use the arrow body accessors or match on `ArrowFunctionBody` rather than inspecting `expression` and synthetic statements.

Common field migrations:

| Previous representation | New representation |
| --- | --- |
| `arrow.expression` | `arrow.is_expression()` |
| `arrow.get_expression()` | `arrow.get_expression()` |
| `&arrow.body.statements` | `arrow.get_function_body().map(\|body\| &body.statements)` |
| `&mut arrow.body.statements` | `arrow.get_function_body_mut().map(\|body\| &mut body.statements)` |
| Synthetic expression statement in `arrow.body` | Direct `ArrowFunctionBody` expression variant |
| `arrow.body = function_body` | `arrow.body = ArrowFunctionBody::FunctionBody(function_body)` |
| Expression-to-block conversion | Create a `FunctionBody` containing a return statement |
| Block-to-expression conversion | Assign the expression directly to `arrow.body` |
| Builder `(expression, body)` arguments | Pass a single `ArrowFunctionBody` |

closes https://github.com/oxc-project/backlog/issues/211
2026-07-29 13:32:32 +00:00
leaysgur f56009a676 fix(oxfmt): correct prose about comment width in fits measurement (#25054)
Audited every formatter crate against the trailing-comment width discipline.

- same-line trailing line comments ride `line_suffix` and are excluded from the fits measurement
- while trailing block comments print inline and count

Corrected the prose that misstated it.
2026-07-29 02:57:17 +00:00
leaysgur 45fd5eef28 test(formatter): move arrow parent-context fixture to js/ (#25007)
Follow up #24991
2026-07-28 00:48:55 +00:00
Cameron 7f86fdf603 test(formatter): cover concise arrow parent contexts (#24991)
Adds formatter regression coverage for concise arrow bodies involving
nested `await` expressions, assignment chains, and AMD `define()` calls.
This locks existing parent-context behavior ahead of the
`ArrowFunctionBody` AST migration.

I may have caused a regression in #24987, so this PR adds test coverage
to make sure I don't break it.
2026-07-28 09:41:10 +09:00