100 Commits
Author SHA1 Message Date
Yuji SugiuraandClaude Opus 5.5 e6c4feffcc fix(formatter): decide template and object key layouts from collapsed Tailwind classes (#28791)
Follow-up to #28744, same root cause in other places.

With `sortTailwindcss` on and `preserveWhitespace` off, Tailwind class
strings and templates are printed with their whitespace collapsed, but
these layout decisions read the source text, so formatting took two
runs:

```ts
// Sole template argument of a Tailwind call
const a = clsx(`
  p-4 flex items-center ${active ? "bg-white" : "bg-black"} text-black
`);
// Arrow body tagged with a Tailwind function
const b = () => tw`
  p-4 flex items-center bg-white text-black
`;
// Short object key (tabWidth: 4)
const c = clsx({ "a   b": someLongCondition });
```

- `is_multiline_template_starting_on_same_line` now returns `false` for
a template whose line breaks are collapsed
- It reads the current Tailwind context (what `TemplateElement` reads)
or the one a Tailwind tag pushes, so the arrow body, sole argument and
member chain routing callers follow the print
- `write_tailwind_string_literal` returns its printed width, and
`write_member_name` uses it for the short key decision
  - An upper bound: the sorter also drops duplicates of known classes
- `tailwind_context_for_callee` is shared by the call and tagged
template pushes and the layout check, like
`tailwind_context_for_jsx_attribute` in #28744

Outputs match Prettier + prettier-plugin-tailwindcss and are idempotent.

Not covered here: `is_short_argument` (#28745), Tailwind calls through
`MemberChain` never pushing the context, and computed keys.

AI assistance (Claude Code) was used for this change. I reviewed and
tested it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-04 04:51:40 +00:00
Yuji Sugiura 07627f7a9b perf(oxfmt): use worker_threads for tinypool again (#28787)
napi-rs/napi-rs#3670 fixed deadlock issue in our usecase, so now we can
use it again.
2026-11-04 10:22:08 +09:00
Yuji SugiuraandClaude Opus 5.5 303d9e803f docs(formatter): strengthen start-token-no-double-parens rationale (#28711)
The leading parens only guard a statement or arrow body start, so
Prettier prints the same object lookup idiom without them elsewhere
(`const get = ({ a: 1, b: 2 } as const)[key];`).
List that sibling and the assignment arrow body in `Why`, and replace
the `export default` example with a measured one.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:45:32 +09:00
Yuji SugiuraandClaude Opus 5.5 dbcc358aee fix(formatter): keep arrow conditional body parens before a leading function/class (#28710)
`should_add_parens` skipped the parens of an arrow's conditional body
when it starts with an object, function or class, assuming the leading
node prints its own.
Only an object does in an arrow body, so `() => (function () {}).a ? b :
c` printed with no parens at all.

Matches Prettier: skip them only for a leading object.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 17:45:31 +09:00
leaysgur b6f1ac9b05 perf(formatter_markdown): bump parser to 0.0.4 and skip unread work (#28713)
- bump oxc-markdown-parser 0.0.4 which skips autolink literal attempts that cannot match
- Compute a text's next words only under `proseWrap: always`, the only mode that reads them
- Print link destinations with nothing to escape as is

About 15% CPU instruction reduction.
2026-11-02 08:26:19 +00:00
leaysgur 633ee960da perf(formatter_css): use u32 spans (#28708)
- bump oxc-css-parser 0.0.19 which changes `Span` from `usize` to `u32`
- update conversion codes

Peak memory is reduced 5% ~ 20% in large CSS file.
2026-11-02 08:26:19 +00:00
leaysgur f680ee1f2e perf(formatter_yaml): use multiline flag for flow scalars (#28707)
- bump `oxc-yaml-parser` which has `multiline` flag
- eliminate all `\n` contains check from formatter

Reduce 5~7% CPU instructions.
2026-11-02 08:26:18 +00:00
leaysgur ff7c203711 perf(formatter_css): avoid repeated scans and allocations in values (#28706)
Use `smallvec` and optimize `split_comma_groups()`.
2026-11-02 08:26:18 +00:00
leaysgur 7cd46cb70b perf(formatter_htmlish): search script end tags by < in embedded (#28704)
Optimize `</script` finding in js-in-html.
2026-11-02 05:03:29 +00:00
leaysgur 46f8efd1fb perf(formatter_graphql): print block string lines borrowed from the source (#28701)
GraphQL block string `"""..."""` may or may not contain CR,CRLF.

But if not, we can use source as-is.
2026-11-02 05:03:28 +00:00
leaysgur dce14853f6 perf(formatter_htmlish): skip escaping attr values without the chosen quote (#28700)
Skip useless attr escape if possible.
2026-11-02 05:03:28 +00:00
leaysgur 73a368faee test(formatter_css): classify grammar-owner-rejected errors (#28705)
And add as tests.
2026-11-02 04:57:27 +00:00
leaysgur e9392c88c6 perf(formatter_core): search line terminators with memchr (#28699)
For languages like CSS, YAML, Markdown, etc, `normalize_newlines` is called for CRLF normalization.

About 3~6% improvement for CPU instruction.
2026-11-02 04:52:00 +00:00
leaysgur ceb26be83c perf(formatter_htmlish): use char to escape attr quotes (#28697)
single character `&str` with `cow_replaces` is not efficient, `char` is enough here.
2026-11-02 04:52:00 +00:00
leaysgur fef809a7f3 fix(formatter_htmlish): handle XHTML (#28703)
- bump `oxc-htmlish-parser` to support Xhtml dialect
- Use it in formatter
2026-11-02 04:52:00 +00:00
leaysgur cd2d9ba8e8 docs(skills): guide migrate-oxfmt to check known divergences (#28698) 2026-11-02 02:17:17 +00:00
Yuji Sugiura a82ce838ea docs(oxfmt): update unclassified divergence (#28685) 2026-11-02 10:21:41 +09:00
Yuji Sugiura d42fa0ea89 chore(deps): update @types/node aligned with .node-version (#28684)
Both are aligned with v26.
2026-11-02 10:21:41 +09:00
Yuji SugiuraandClaude Opus 5.5 04bfe30224 test(oxlint): use code that fails to parse regardless of source type (#28687)
#28438 fixed how `let` expression in sloppy mode, this test should be
updated as well.

---

<sub>Stack created with <a
href="https://github.com/github/gh-stack">GitHub Stacks CLI</a> • <a
href="https://gh.io/stacks-feedback">Give Feedback 💬</a></sub>

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 10:21:40 +09:00
Yuji Sugiura e3f90b1f80 test(oxfmt): track classified divergences (#28533)
Finalize on the stack of changes: classifying the divergences and
cleaning up.
2026-10-30 22:15:37 +09:00
Yuji Sugiura 211126a771 test(formatter_tests): own options parsing in the harness (#28532)
Cleaned up the code now that Prettier conformance is gone and each
formatter crate just runs fixture tests.
2026-10-30 22:15:36 +09:00
Yuji Sugiura 8f228876d8 test(oxfmt): run prettier suite only in run_prettier (#28531)
There are 2 types of Oxfmt conformance tests:

- those that compare against static Prettier snapshots
- those that dynamically execute `format()` for comparison

From the latter, remove any cases already covered by the former.
2026-10-30 22:15:36 +09:00
Yuji Sugiura b6f6f3fd20 test(formatter_tests): remove each crate's prettier conformance (#28530)
Remove each crate side Prettier conformance, already tested by Oxfmt
side.
2026-10-30 22:15:36 +09:00
Yuji Sugiura 60efdd3d0a test(oxfmt): unified prettier conformance (#28529)
Up until now, conformance with Prettier was handled separately by each
formatter crate and oxfmt. This was because the individual formatters
were unable to handle embedded formatting.

While that approach worked fine for languages like JSON and CSS, I
realized it would become inconvenient as we moved forward with
supporting languages like Vue.
2026-10-30 22:15:35 +09:00
Yuji Sugiura 7f18c20029 docs(formatter_htmlish): note class and srcset are formatted with embeddedLanguageFormatting: off (#28465)
`class` and `srcset` are inside HTML language, should be formatted.
2026-10-30 18:04:14 +09:00
Yuji Sugiura 336b2c1c11 feat(oxfmt): support html-in-js templates closing a dynamic tag name (#28389)
Since HTML formatter supports `htm`'s `<//>` (special closing tag), we
should also support other cases like:

```js
html`
  <${For} each=${() => items}>
    <li class="item">${() => content}</li>
    <li class="item">more items</li>
  </${For}>
`;
```
2026-10-30 08:56:34 +00:00
Yuji Sugiura 0a42af6086 feat(formatter_htmlish): format end tags of omitted html, head and body (#28388)
Some HTML tags can be written with ommiting it's opening tag.
2026-10-30 08:50:14 +00:00
Yuji Sugiura 01ed68ac81 docs(oxfmt,formatter_htmlish,formatter): clean up behavior comments (#28384)
We do not need to note about Prettier internals.
2026-10-30 08:45:37 +00:00
Yuji Sugiura a5b0c0a054 refactor(formatter_htmlish): split tree/build modules (#28383)
Just splitting files.
2026-10-30 08:41:30 +00:00
Yuji Sugiura f3dd201682 fix(formatter_htmlish): keep SVG text under xml:space="preserve" as written (#28382)
```html
<svg>
  <text x="0" y="10" xml:space="preserve">  a  <tspan>b</tspan>  c</text>
</svg>
```

Preserve these as-is.
2026-10-30 16:54:21 +09:00
Yuji Sugiura fb3aefa8d6 fix(formatter): keep source parens around html-in-js placeholders past comments (#28381)
It's a rare case, but just to be safe.
2026-10-30 16:53:03 +09:00
Yuji Sugiura e738daede1 fix(formatter): quote keys added in an HTML event handler with single quotes (#28380)
Regardless of the location, the attribute value must be quoted by single
quote.
2026-10-30 16:52:45 +09:00
Yuji Sugiura 2e7a629be8 fix(oxfmt): format HTML in a <script> body as an embedded part (#28379)
```html
<script type="text/html">
  <div style="color:red;background:blue" onclick="foo( 1 )" class="  a   b  ">x</div>
</script>
```

will be formatted:

```html
<script type="text/html">
  <div style="color:red;background:blue" onclick="foo( 1 )" class="  a   b  ">
    x
  </div>
</script>
```

Note attributes are NOT.
2026-10-30 07:49:53 +00:00
Yuji SugiuraandClaude Opus 5.5 b1def652d3 test(formatter_htmlish): rename nul fixture directory for Windows (#28522)
`crates/oxc_formatter_htmlish/tests/fixtures/html/nul/` (added in
#28272) uses `nul`, a reserved device name on Windows, which breaks all
Windows jobs on main:

- `Test Windows`: `error: invalid path
'crates/oxc_formatter_htmlish/tests/fixtures/html/nul/nul-as-written.html'`
- `Test NAPI * (windows)`: `Could not find a part of the path '\\.\nul'`
while downloading the repo as a local action

Renamed the directory to `nul-byte/` and updated the `.gitattributes`
entry and the `DIVERGENCES.md` pin. The snapshot content is unchanged.

`cargo test -p oxc_formatter_htmlish` passes
(`fixtures::html::nul_byte::nul_as_written` included).

This PR was created with AI assistance (Claude Code).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 07:40:34 +00:00
Yuji Sugiura c8cfcc9ab8 refactor(formatter_htmlish): simplify attribute, sibling and sensitivity printing (#28378)
Update HTML attributes list and simplify printing.
2026-10-30 07:37:56 +00:00
Yuji Sugiura 296623d868 feat(oxfmt)!: format parser:html files by oxc_formatter_htmlish (#28273)
Fixes #28263 

- Before this PR: `.html` and also html-in-{js,md,etc} are formatted by
Prettier.
  - Because of that, we had to run JS, which became an overhead.
- After this PR: All of them are formatted natively by
`oxc_formatter_htmlish`.
  - No overhead.

At the same time, since we are fixing Prettier bugs and inconsistent
behavior, please refer to DIVERGENCES.md for the differences.
2026-10-30 16:29:13 +09:00
Yuji Sugiura 44d8c2b824 feat(formatter_htmlish): Implement HTML-ish formatter Html variant (#28272)
Part of #28263
2026-10-30 16:00:59 +09:00
Yuji Sugiura a363c2765f docs(formatter_css,formatter_yaml): point parser citations at AGENTS.md (#28466) 2026-10-30 13:05:54 +09:00
leaysgur 658605088a docs(formatter_core): document tab width in text width policy (#28393) 2026-10-29 07:51:18 +00:00
leaysgur 49fbffbfe4 docs(formatter_core): tighten FORMATTER_POLICY (#28206) 2026-10-27 13:05:41 +00:00
leaysgur 36e5ddb0f2 feat(oxfmt): add debug span for reading files (#28204)
Reading a file happens outside the `oxfmt::format` span, so time spent in I/O did not show up with `OXC_LOG=debug`.

On a machine where an endpoint security agent scans every `open()`/`close()`, most of the wall time was spent there, but the log only showed formatting time.

```
DEBUG oxfmt::read{path=src/foo.tsx}: oxfmt::cli::service: close time.busy=92.6µs time.idle=2.38µs
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-27 09:25:14 +00:00
leaysgur 9ead5c42df fix(formatter): handle this.b and super.b like foo.b (#28200)
For consistency.
2026-10-27 06:09:38 +00:00
leaysgur 72a54cf447 fix(formatter): break parens of (a + b)[c] like (a + b).c (#28199)
```js
// before
(aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
  bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb)[c];

// after
(
  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +
  bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
)[c];
```
2026-10-27 06:09:37 +00:00
leaysgur b8f4f34bba fix(formatter): print x.b and x.#b samely (#28193)
There is no reason to handle them separately.

This change also fixes idempotency issue with comments.
2026-10-27 06:09:37 +00:00
leaysgur 9ddccb7105 fix(formatter): keep parens for private field (#28188)
Fixes #28123, closes #28124.

- Keep parens around `(a ?? b)?.#x`
- Add a helper to handle similar cases together
  - `PrivateFieldExpression` was often missed...
2026-10-27 06:09:36 +00:00
leaysgur 8d86bd250b refactor(formatter): treat a sibling starting at the enclosing end as outside for trailing comments (#28174)
- Use `>=` for trailing comment bound
- Remove the explicit trailing comment prints that worked around it in `JSXExpressionContainer`, `write_jsx_spread` and `FormatOpeningElement`
2026-10-26 08:29:50 +00:00
leaysgur 5042019bc5 fix(formatter): keep fill layout for array with comment after [ (#28170)
Aligned with Prettier and also fix idempotency issue.
2026-10-26 08:29:49 +00:00
leaysgur 9d37ff614d fix(formatter): use position-based comment checks for concise arrays (#28169)
Closes #28104.

`can_concisely_print_array_list` shared one cursor-based comments iterator across elements.
Checking an element consumed the comments of later ones, so a comment inside a later signed number was missed and the array fill-printed on the second pass.

- Check each element with the position-based `has_any_comment_in_range`, and the trailing line comment check with `all_comments_in_range` over the array
  - Layout decisions use the position-based queries, see `crates/oxc_formatter/AGENTS.md`
- `js/arrays/numbers-negative-comment-after-minus.js` is now idempotent
2026-10-26 08:29:49 +00:00
leaysgur 068a8ea8c0 docs(formatter): fix up wording (#28167)
Follow up on previous fixes.
2026-10-26 08:29:48 +00:00
leaysgur 190bb1e67f fix(formatter): keep parens around an instantiation expression ending an operand through any tight parent (#28168)
Closes #28108

Fixes `(<any>(make<T>)) + y` (#28108) case, and also `(#p in make<T>) > y` and `(<A>await make<T>) + y` cases.
2026-10-26 06:23:43 +00:00
leaysgur a5314a1c8a fix(formatter): keep ; before in and instanceof class members (#28166)
Closes #28065

Prettier's `shouldPrintSemicolonAfterClassProperty` (and also biome) already has this rule, but our port missed it.

- Treat an `in` / `instanceof` key like a computed key in `needs_semicolon`, with the same modifier exclusions
- Check a string key by its value, since `"in"` may be printed unquoted
  - Prettier unquotes it and drops the `;`, producing invalid code
  - DIVERGENCES.md#string-binary-keyword-class-key
2026-10-26 05:43:53 +00:00
leaysgur 23d23d69ae fix(formatter_markdown): honor range ignore inside directives (#28164)
Fixes #28154

Range ignore is supported only at the top level.

Prettier does not support directives, so the contents of `:::` end up at the top level and the range works there.
We parse `:::` as a container directive, so its contents were not top-level and the range was ignored.

This PR treats a `:::` directive with no blockquote / list item / footnote above it as top-level.

Supporting ranges everywhere is possible, but it has concerns such as indentation drift in verbatim output. So this starts small.
2026-10-26 03:52:44 +00:00
leaysgur 3e7f0f5b73 chore(oxfmt): bump prettier-plugin-astro to 1.1.2 (#28162)
Also updated peerDeps range `^0.5.0` only.
2026-10-26 03:52:43 +00:00
leaysgur 2b5a3fd678 chore(formatter_css): bump oxc-css-parser to 0.0.18 (#28161)
Fixes #28102
2026-10-26 03:52:43 +00:00
leaysgur 2604b5482b chore(oxfmt): remove stale DIVERGENCES comment from fixtures (#28160)
Prettier fixed this in the last release.
2026-10-26 03:52:42 +00:00
leaysgur f525b34636 refactor(formatter_core): make embedded dispatch parent-to-child only (#28159)
Remove `child_context`, every usecases are handled only parent -> child direction.
2026-10-26 03:52:42 +00:00
Yuji Sugiura 014e3d2676 chore(deps): bump tokio to 1.54.1 (#28163)
This fixes RUSTSEC-2026-0386 which makes `cargo deny` fail.

[v1.53.3](https://github.com/tokio-rs/tokio/releases/tag/tokio-1.53.3)
seems enough to fix, but for sure.
2026-10-26 02:26:28 +00:00
leaysgur 1ed93afeda perf(data_structures): copy short strings without a memcpy call (#27953)
Fast path for short strings.

---

According to local benchmark, it seemed to be effective for the formatter as well, but on CodSpeed, it appears to be effective only for codegen... 🤔
2026-10-23 02:52:52 +00:00
leaysgur f1d90329e2 refactor(formatter_core): remove JS-only method (#28099)
Just inlined in `oxc_formatter`.
2026-10-22 07:45:34 +00:00
leaysgur 5f1fd3dcb9 fix(formatter): measure width heuristics by display width (#28098)
Standardizing the inconsistent ways of measuring width used across various places.

NOTE: JSDoc is left as-is for now, there are many exceptions...
2026-10-22 07:45:33 +00:00
leaysgur 4829118e52 refactor(formatter): add AstNodes::is_binary_like for parent paren checks (#28097) 2026-10-22 07:36:10 +00:00
leaysgur a5a52cf5f7 fix(formatter): keep parens around private in expressions (#28096)
Should keep:

```ts
class C {
  #x;
  m(a, b, c) {
    x = (#x in b).c; // THIS parens with `PrivateInExpression`
```
2026-10-22 07:36:09 +00:00
leaysgur db55461552 docs(formatter_core): document text width divergence (#28095) 2026-10-22 06:50:42 +00:00
leaysgur f7eb849e25 test(oxfmt): skip entries under symlinked dirs in snapshot (#28093)
This change is introduced by Node.js v26.9.0.
2026-10-22 06:28:46 +00:00
leaysgur b93e169ab5 chore(deps): bump .node-version to 26.12.1 (#28090)
Bump `.node-version` to 26.12.1.

- Node 24 moved to Maintenance LTS on 2026-10-20.
- Node 26 enters Active LTS on 2026-10-28.
- Includes the fixes from the [October 2026 security releases](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases).
- Update the Node.js version in snapshots.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-10-22 05:12:51 +00:00
leaysgur 18b6022d69 test(oxfmt): compare real-world conformance sources on Prettier-formatted input (#28067)
Before: conformance fixtures (may or may not be formatted) are directly compared
After: For may not be formatted fixtures, compare with prettier-formatted
2026-10-22 02:22:11 +00:00
leaysgur f32360093c refactor(formatter_core): narrow FormatElement and TextWidth apis (#28017)
Inlines internal usages.
2026-10-21 05:21:14 +00:00
leaysgur 7948f0b356 perf(formatter_core): shrink FormatElement to 16B (#28012)
Remaining room of improvement relate to #24558.

Sizes on 64-bit platforms (size / align, in bytes):

- `FormatElement`: 24 / 8 -> 16 / 8
- `Interned`: 16 / 8 -> 12 / 4
- `BestFittingElement`: 16 / 8 -> 12 / 4
- `FormatElement::{Token, Text}` text: `&str` 16 / 8 -> `ThinStr` 12 / 4
- `TextWidth`: 4 / 4 -> 3 / 1
- `LabelId`: 8 / 8 -> 4 / 4
- `Prefix`: 8 / 8 -> 8 / 4
2026-10-21 05:21:14 +00:00
leaysgur 880d229614 docs(formatter): add vitest-test-call to known divergences (#28006) 2026-10-21 02:15:22 +00:00
leaysgur 788af22210 perf(formatter): keep AstNodes on the stack, and only chain links in the arena (#27963)
Stop allocating every `AstNode` wrapper in the arena, following the approach of #21931.

### What changes

- `AstNode<'parent, 'a, T> { inner, parent, following_span_start }` is `Copy` and has no `&Allocator`.
  - `'parent` is the lifetime of the parent `AstNode` that `parent` refers to.
  - Generated child accessors return an owned `AstNode` that borrows its parent.
  - The parent's `write` keeps its children on its stack frame.
  - The unsafe lifetime extension (`transmute_self`) is gone.
- The arena-backed `as_ast_nodes()` is replaced by a generated owned `AstNodeKind` (`as_kind()`).
  - `NeedsParentheses for AstNode<Expression>` is generated as one direct match.

### Sites that are not a plain recursive descent

A loop that walks down a chain cannot keep each node on the stack, as the next node refers to it as its parent.
#21931 hit this at about 10 sites.

- Chain walks (binary-like flattening, member chains, arrow chains, assignment-like, nested namespaces) keep their loops.
  - Only the links that the walk continues past go into the arena, through `AstNode::in_arena` / `AstNodeKind::in_arena`.
  - The rule is documented once on `AstNode`.
- Checks that never ask for a parent read the AST directly, with no wrapper.
  - Left sides: one table, `LeftSide::of`, which `left_span_of`, `leftmost` and the `AstNode` walk `left_of` derive from.
  - The shape checks of assignment-like chains.
- The binary-like split and the flattened logical expression are loops too, so long chains need no more stack than on `main`.

### Compared to #21931 (Jim's PoC)

- Enum nodes resolve through the generated `as_kind()`, so call sites keep the `match` shape of `as_ast_nodes()`.
  - PoC rewrote each of them by hand, as a `match` on `inner` with `with_inner`.
- The arena is used only through `in_arena`, and only for the links a chain walk continues past.
  - PoC put an `f.allocator().alloc(..)` at each site that broke, kept the recursion, and allocated at the entry of a flatten.
- Checks that never ask for a parent use no wrapper.
  - PoC allocated a wrapper at each step of e.g. `get_innermost_expression`.
- `ImportExpression::arguments` takes the allocator, instead of leaving `to_arguments` as a TODO.

### Alternatives tried

Both follow directions suggested in #21931.

- #27958: no arena at all
  - As suggested "replace loop with recursive function call".
  - The chain walks recurse, and a walk that needs the whole chain keeps it in a `StackList` on the stack, consumed through continuation callbacks.
  - The recursion uses stack in proportion to the chain length: with 2 MiB workers, `return a.b().b()…` overflows at 4000 calls, where `main` formats 32000.
  - So it needs a workaround, e.g. 8 MiB `oxfmt` workers, which still leaves the napi API and the LSP on default stacks.
  - About 0.5 points faster than this PR on CodSpeed, but more code.
- #27959: no parent pointer
  - As suggested "Alternative approach".
  - Ancestors on a `NonEmptyStack` like `Traverse`, and `following_span_start` computed on demand.
  - 2-4% more instructions than #27958, quadratic on long chains, and the ancestry is only checked in debug builds.
2026-10-21 00:52:40 +00:00
leaysgur 6912d1f70a fix(formatter): keep an arrow signature that exactly fills the line for any body (#27989)
Revert #24440 and reclassify DIVERGENCES.md#arrow-exact-fill-signature as "uniform-rule".

---

> It seems like oxc is correct here and Prettier has a bug?
> https://github.com/oxc-project/oxc/issues/24416

Having the opportunity to revisit the details..., I found that this is indeed the truth.
2026-10-20 06:12:30 +00:00
leaysgur 324432178d refactor(formatter_core): check width via overflows() in fits_text() (#27988)
Now it uses `overflows()` everywhere.
2026-10-20 06:12:29 +00:00
leaysgur eb1d9f95ac fix(formatter_core): count a flat line space in fits only when text follows (#27987)
Check overflow at every `Fits::Yes` exit like Prettier's loop head, replacing the expanded-line pending space resolution.

Seems a side effect of #20954.
2026-10-20 05:57:01 +00:00
leaysgur e236de026a fix(formatter_tests): skip conformance spec calls with non-literal options (#27957)
Skip invalid spec like `printWidth: Infinity`.
2026-10-19 07:56:56 +00:00
leaysgur 1325225d72 docs(oxfmt): update useless comment prefix (#27956) 2026-10-19 07:48:35 +00:00
leaysgur a5d8184f92 fix(jsdoc): parse code spans after quotes in descriptions (#27955)
Fixes #27852, closes #27925.

- Recognize Markdown code spans after quotes in JSDoc descriptions, so a span wrapping onto the next line does not swallow subsequent tags.
- Preserve quoted backticks inside types, optional parameter defaults, parenthesized function arguments, and `@import` specifiers.
- Add parser regressions and formatter input/expected-output fixtures for single and double quotes.
2026-10-19 07:06:03 +00:00
leaysgur e220293686 perf(formatter): check only the operator suppression mark in is_quiet (#27952)
In `is_quiet`, the preceding check already guarantees no unprinted comment ends before the node, so the `comments_before_iter` scan in `is_suppressed` always returns `false`.
2026-10-19 04:03:52 +00:00
leaysgur 2645d2c76c fix(formatter_markdown): treat link destinations and footnote references as opaque to code span fences (#27948)
Fixes #27844, closes #27943.

Also fixed potential bugs with footnote.
2026-10-19 01:52:32 +00:00
leaysgur 4d8195c183 fix(formatter_yaml): keep a root plain scalar starting with a document marker verbatim (#27947)
Similar case with #27946, save more cases.
2026-10-19 01:42:25 +00:00
leaysgur 529b057811 fix(formatter_core): keep front matter verbatim when body starts with indented delimiter (#27946)
```
---
 --- # <- THIS should be kept, otherwise closes FM!
---

```
2026-10-19 01:42:25 +00:00
leaysgur c22a9b2742 fix(formatter): treat static block with only empty statements as empty (#27945)
Fixed panic on `{ ; }`.

`EmptyStatement` is removed, then `group` for `StaticBlock` failed to get its content.
2026-10-19 01:42:24 +00:00
leaysgur d0dd140fcf docs(formatter): note lossy cooked round-trip in template embeds (#27944)
May fix in the future?
2026-10-19 01:42:24 +00:00
leaysgur 7bc0c0469e test(oxfmt): stop counting both-error conformance cases as passed (#27826)
- Exclude Prettier's `_errors_/` and `range/` fixtures from `js-in-vue`
- Rename `issue-19667.ts` to `.tsx` so it parses
- Treat both sides failing to parse as a failure
2026-10-16 12:16:17 +00:00
leaysgur eb8731fea5 test(formatter_tests): count conformance rate per option set (#27825)
- Count `(input, option set)` runs instead of files
- Stop counting unparsable runs as passed
- Count matched runs as `1.0` in the match ratio
2026-10-16 10:17:41 +00:00
leaysgur 8a3a6c8764 docs(oxfmt): update stale notes on the JSDoc string-out channel and fixture origins (#27824) 2026-10-16 10:17:41 +00:00
leaysgur d3ff3f6622 fix(formatter/jsdoc): write descriptions as written to not close the comment (#27823)
For example, `__/` should be kept as-is.

Previously converted to `**/` and it closes the block comment itself, then break the code.
2026-10-16 10:17:40 +00:00
leaysgur c4c17279e6 fix(formatter/sort_imports): keep comments after the last import (#27822)
Fixes idempotency issue.
2026-10-16 10:17:40 +00:00
leaysgur a64677e2a7 fix(formatter): print JSDoc embedded snippets with LF (#27821)
Otherwise, CRLF is appeared in IR, then panics.
2026-10-16 10:17:39 +00:00
leaysgur 2c1cc20fa4 fix(formatter): keep comment only Vue bindings as-is (#27820)
Preserve them as-is, incl. spaces.
2026-10-16 10:17:39 +00:00
leaysgur 1bf236d2be fix(formatter/sort_imports): do not use comments as import source (#27819)
`from /* c */ "z"` was wrongly sorted as module `c`.
2026-10-16 10:17:38 +00:00
leaysgur 52ee29d1be fix(formatter_yaml): never start col-0 line with document marker or BOM (#27818)
- Bump `oxc-yaml-parser` to 0.0.7
  - handles NUL as content, BOM only outside a document, markers only at column 0
- Keep `---`/`...` content on the `---` line, keep `?` on a root key starting with a marker, and indent a flow scalar continuation line starting with a marker
- Add `---` before a document whose first text is U+FEFF
2026-10-16 10:17:38 +00:00
leaysgur 81c75e75a6 fix(formatter/css): keep glued css-in-js statement placeholders glued (#27814)
Keep them as-is.

```js
css`${a}${b}`;
css`${a} ${b}`;
```

Also noted DIVERGENCES relates to `!important`.
2026-10-16 06:08:38 +00:00
leaysgur a1ef7f4f9b fix(formatter): keep typecast parens in same-operator binary and arrow chain (#27812)
Saves like `x = a && /** @type {T} */ (b && c);` and `f = (a) => /** @type {T} */ ((b) => c);`.
2026-10-16 05:31:33 +00:00
leaysgur de110ced8b fix(formatter): keep more parens around TSInstantiationExpression (#27811)
Like `(make<T>)<U>();`.
2026-10-16 05:26:31 +00:00
leaysgur 8b02e90cf7 fix(formatter_json): reject invalid JSON instead of dropping them (#27785)
Found by Prettier's `_errors_` tests.
2026-10-16 04:21:11 +00:00
leaysgur 814eaeb2c6 fix(formatter_toml): bump oxc-toml to keep commented inline table (#27782)
https://github.com/oxc-project/oxc-toml/releases/tag/v0.15.1
2026-10-16 04:21:11 +00:00
leaysgur 6fe4c1b911 fix(formatter): treat lone CR in JSX text and argument blank lines (#27780)
Also disables BOM and shebang variants.
2026-10-16 04:04:07 +00:00
leaysgur 78b6f0260c test(formatter_tests): run conformance with CRLF/CR/BOM variants (#27779)
Fixes will land in later PR.
2026-10-16 04:04:07 +00:00
leaysgur 5605362017 fix(oxfmt): validate editorconfig as like oxfmtrc (#27778)
Fixes various bugs.
2026-10-16 04:04:06 +00:00
leaysgur 5b2acb0427 fix(formatter_core): report error position releative to CRLF source (#27777)
e.g. CSS, YAML formatter replace CRLF beforehand, so when reports error, need to restore positon.
2026-10-16 04:04:06 +00:00