Commit Graph

34 Commits

Author SHA1 Message Date
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 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 3b054de895 fix(formatter_graphql): align comment printing after open { (#26271)
Aligned the handling of line comments immediately following an opening `{`.

(For GraphQL only,) previously kept their own line, now matches other languages by inserting a line break and treating them as leading comments for the next element.

This brings it in line with Prettier's style. The only drawback is that the behavior of `-line` style comments. However, since most of these can be replaced with `-next-line` style comments, we are going to standardize on this consistent behavior for now.

prettier/prettier#1267 (won't fix)
2026-09-03 08:17:35 +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 faf11d9d6e fix(formatter_yaml): keep trailing whitespace in block scalars (#26072)
WS inside block scalar value is content, should be preserved.
2026-08-25 04:44:04 +00:00
Yuji Sugiura 223e7855fe docs(oxfmt/conformance): Update notes on conformance (#25757)
Filled current state.
2026-08-17 09:25:11 +09:00
leaysgur 033ddf6d38 refactor(oxfmt,formatter): simplify js-in-html-in-js handling (#25464)
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.
2026-08-10 06:02:17 +00:00
leaysgur a9e76e9e1f fix(oxfmt): rework tsx-in-vue support (#25106)
Follow-up to #25063.

tsx-in-vue was supported in #25063 with a parse-retry (ts fails → retry as tsx), chosen to keep the common ts path single-parse.

However, this leaves a hole: a `lang="tsx"` block without JSX parses fine as plain ts, so the retry never fires and the lone generic comma is removed:

```vue
<script setup lang="tsx">
const identity = <T,>(x: T) => x; // → <T>(x: T) => x, breaks as TS17008
</script>
```

Since parse failure fundamentally cannot detect this, the only fix is to scan the `lang` declaration itself.

The JS host now scans the SFC once per file and passes `_oxfmtVueScriptLang: "tsx"` down to `textToDoc()`. This makes the retry obsolete, so it is removed.

This PR also settles the comma policy: keep it only where it is load-bearing.

- JSX grammar (`<T>` would be a JSX opener)
- and `.mts/.cts` (TS7060, #18928)

and remove it everywhere else, keyed on the grammar, regardless of context.

Deliberate divergence: Prettier keys on filepath and keeps the comma in ts-in-vue/svelte (while its own ts-in-md embed uses `dummy.ts` as `filepath` and removes it). Plain-TS sources now behave identically wherever they live.
2026-07-30 05:27:07 +00:00
leaysgur 1b57f783fb feat(oxfmt): support tsx-in-vue (#25063)
Part of #16608, closes #25046.

---

Even with a simple regexp, I don't want to impact the performance of the vast majority of ts-in-vue paths.

With this approach, only tsx-in-vue use cases will be affected, and even that will be kept to a minimum since it can be handled entirely on the Rust side. (JS <-> Rust roundtrip is also slow)
2026-07-29 07:27:43 +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 22fdda4634 fix(oxfmt/conformance): fix typo (#25053)
Typo...
2026-07-29 02:57:17 +00:00
leaysgur 62a5ceaa4e test(oxfmt): add yaml conformance fixtures (#24892) 2026-07-28 07:43:14 +00:00
leaysgur e037709234 fix(formatter): stop preserving indent for embedded template exprs (#24972)
Fixes idempotency issue in html|css-in-js. (= align gql-in-js behavior)
2026-07-27 08:04:57 +00:00
leaysgur ae1a39b71c fix(formatter_css): align wrapped selector-arg indent (#24971)
Fixes #24843

---

Tracing back to the root cause, this is also related to what had diverged because of Prettier's inconsistent behavior.
Since I cannot determine for sure how it should be output in more complex cases (like that issue) at this point, decided to revert to being Prettier-compatible.
2026-07-27 06:47:19 +00:00
leaysgur dbf85def42 refactor(formatter_graphql): rework comment printing (#24970)
Also fixes #24927

- rework comment printing to follow the rules and align with other formatters
- bump `oxc-graphql-parser`
  - to track comments inside a node's span more accurately
2026-07-27 05:31:01 +00:00
leaysgur 33e32d8e51 fix(formatter_css): use line_suffix for EOL line comment (#24580)
The same motivation as #24579
2026-07-16 01:37:31 +00:00
leaysgur cd2452e170 fix(formatter_css): align SCSS output to Prettier 3.9.1 (#24097)
Part of #23923

Addressed these changes:

- https://github.com/prettier/prettier/blob/79f6cdfd9873a91be9b25c9c6a41d26dcd9a6656/changelog_unreleased/scss/18530.md
- https://github.com/prettier/prettier/blob/79f6cdfd9873a91be9b25c9c6a41d26dcd9a6656/changelog_unreleased/scss/18535.md
- https://github.com/prettier/prettier/blob/79f6cdfd9873a91be9b25c9c6a41d26dcd9a6656/changelog_unreleased/scss/19091.md

and also fixed minor formatting diffs regarding to trailing comment in map.

```scss
$m: (
  a: 1,
  b: 2, // THIS
);
```
2026-07-03 07:29:32 +00:00
leaysgur af77116ac2 test(oxfmt): add css + less + scss conformance (#23435)
- Added a few repos for oxfmt conformance
  - CSS: mantine, docsaurous
  - LESS: NG-ZORRO
  - SCSS: Reuse vben, gitlab
2026-06-29 03:15:27 +00:00
leaysgur f5a49cd383 test(oxfmt): add gitlab as graphql conformance (#23421)
- Add `gitlabhq/gitlab` repo to oxfmt conformance
  - There are 700~ graphql files
- Once `gitlab` repo is migrated Oxfmt, we can add it to ecosystem-ci
2026-06-29 03:15:26 +00:00
leaysgur accbc49def feat(oxfmt)!: format parser:css,less,scss files + css-in-js by oxc_formatter_css (#23321)
- Use `oxc_formatter_css` from Oxfmt to handle CSS/LESS/SCSS files
- css-in-js is also handled by `oxc_formatter_css`
  - But it will fallback to Prettier if formatting failed
  - No fallback for no-embedded path
- At the point of this PR, it will fallback to Prettier when tailwind is required to format

NOTE: Both fallback will be removed in later PR.

In that sense, as a result of this series of PRs, Oxfmt's CSS support will no longer be 100% compatible compared to Prettier.

Since the parsers used are also different, it will no longer be possible to support certain postcss-plugin-specific syntax, invalid syntax or legacy syntax.

> [!WARNING]
> TODO: Move fork under `oxc-project` org and replace refs
2026-06-29 03:15:24 +00:00
leaysgur 838061bf83 test(oxfmt): Update conformance (#22299)
Update html-in-js fixtures.
2026-05-11 07:39:20 +00:00
leaysgur d29c5d60bb test(oxfmt): Arrange conformance fixtures dir (#22297)
Prepare for running conformance on CI as well.
2026-05-11 07:23:46 +00:00
leaysgur 47fb154918 test(oxfmt): Add tests and conformance (#21832) 2026-05-11 01:24:38 +00:00
Dunqing d10df39ae6 fix(formatter): resolve pending space in fits measurer before expanded-mode early exit (#20954)
Closes #20519
Alternative to #20858

## Summary

The `fits()` measurer in the printer defers `Space` elements via a `pending_space` boolean flag. When the measurer exits early through an expanded-mode line break (`Fits::Yes`), any unresolved `pending_space` is silently lost — causing the measured width to be off by one.

This caused the decorator in `@property(...) prop: Type` to incorrectly stay on the same line because the space after `:` was not counted in the width measurement.

### Root cause

In `FitsMeasurer::fits_element`, `Space` sets `pending_space = true` (deferred), while `HardSpace` immediately increments `line_width`. When `fits()` returns `Fits::Yes` via the expanded-mode line break path (line 1040), the pending space is never resolved.

For the decorator case:
1. After `@property(...)` + space + `tooltipPlacement` + `:` → `line_width = 80`
2. `Space` after `:` → `pending_space = true` (line_width stays 80)
3. Union type group inherits Expanded mode → `if_group_breaks(separator)` included
4. Separator's `SoftOrSpace` in Expanded mode → `return Fits::Yes` **without resolving pending_space**
5. The +1 char from the space is lost, making the decorator group incorrectly "fit"

### Fix

Resolve `pending_space` before returning `Fits::Yes` from the expanded-mode early exit path. This matches Ruff's approach where `Space` is counted eagerly via `fits_text(Text::Token(" "))`.

### Conformance

- JS: 746/753 (unchanged)
- TS: 591/601 (unchanged — `18148.ts` fixed, `comment.ts` regressed)

The `comment.ts` regression (`typescript/union/consistent-with-flow/comment.ts`) is a **pre-existing Prettier bug** — at `printWidth: 81`, Prettier itself produces the same double-indented output with a spurious blank line:

```ts
// printWidth: 81 → Prettier also double-indents:
type SuperLong...Blaa =
                         ← spurious blank line
    | Fooo1000           ← 4 spaces (double indent)
```

At exactly 80 chars, Prettier avoids this only because its own fits check has the same off-by-one (the space is not counted), causing the Fluid layout's inner group to accidentally "fit".

## Test plan

- [x] `cargo test -p oxc_formatter` — 262 passed
- [x] `cargo test -p oxc_formatter -- fixtures` — 213 passed (including new `issue-20519.ts`)
- [x] `cargo run -p oxc_prettier_conformance` — JS 746/753, TS 591/601
- [x] `cargo clippy -p oxc_formatter` — clean

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-02 08:32:15 +00:00
leaysgur 23050faba6 feat(oxfmt): Support markdown-in-js substitution (#20683)
Part of #15180, and the last piece...!
2026-03-24 13:13:56 +00:00
leaysgur 4087295040 feat(oxfmt): Support angular-in-js substitution (#20676)
Part of #15180, closes #18891

Angular-in-JS consists of 2 cases: `template` and `styles`.

- template: Similar to html-in-js, use the `parser: angular`
- styles: Same as css-in-js (already supported)
2026-03-24 13:13:56 +00:00
leaysgur 47c56b6a98 test(oxfmt): Refactor conformance tests (#20642)
Pure refactoring;

- Use explicit path for unsupported notes
- Download fixtures in parallel
- Test more variants
2026-03-23 08:06:57 +00:00
leaysgur c21c5a7e72 feat(oxfmt): Support html-in-js substitution (#20193)
Part of #15180

The basic flow is the same as css-in-js: replace `${expr}` with placeholder strings, let Prettier format the HTML, then substitute back with formatted `expr`s.

However, there are a few key differences:

### `options.htmlWhitespaceSensitivity`

This HTML-specific option also affects html-in-js (template wrapping strategy, hug detection), so it needed to be held in `oxc_formatter::FormatOptions`.

### `prettier.__internal.formatOptionsHiddenDefaults`

Prettier adds hidden fields to the `options` object during its internal print phase for embedded formatters.

However, `__debug.printToDoc()` runs `normalizeFormatOptions()` which strips unknown keys. Unlike internal `textToDoc()` (which uses `passThrough: true`), `printToDoc()` does not preserve them.

To work around this, we pre-register keys in `formatOptionsHiddenDefaults` so they survive normalization.

### NOTE: Not fully supported cases

#### js-in-html-in-js

Currently, the process of converting Docs to our IR does not support `conditionalGroup`.

Since Prettier only uses this for YAML and JS formatting.
YAML is never embedded, and we would like to use `oxc_formatter` for JS.

However, applying `oxc_formatter` to the **js**-in-html-in-js case was not straightforward. Therefore, I have implemented a fallback to a simpler formatting process for that case for now.

#### `label({ embed, hug })` equivalent

With current implementation, `is_huggable_html_embed_single_arg()` always hugs via early-return, ignoring line width.

Prettier uses `label({ embed, hug })` + `conditionalGroup` to try hugged first and fall back to expanded.

We have the equivalent `BestFitting` 3-variant flow in `write_grouped_arguments`, but `ExpandParent` from HTML template's `hardline` propagates through `will_break` / `propagate_expands` / fit check, forcing the expanded variant to always be chosen.

To fix, we need to add new IR for boundary (prevents `ExpandParent` propagation in `will_break` / `propagate_expands` / fit check), then add HTML embeds to `can_group_expression_argument` so they enter the existing `BestFitting` 3-variant flow.

However, I'm not sure this is worth to add. So just leave it for now.
2026-03-23 07:30:16 +00:00
leaysgur f8c452aa90 fix(formatter,oxfmt): Handle css-in-js comment inside template (#20452)
Fix the bug found in https://github.com/oxc-project/oxc/blob/605de80b541b8d85e5129ab620eae63ed07a6a1e/apps/oxfmt/conformance/snapshots/diffs/xxx-in-js-comment/comment-inside.js.md
2026-03-17 15:32:32 +00:00
leaysgur c5aeae4d93 feat(formatter,oxfmt): Support /* LANG */ comment for gql|html-in-js (#20224)
Part of #15180, supports ``/* HTML */ `...` `` and ``/* GraphQL */ `...` ``.
2026-03-17 15:32:32 +00:00
leaysgur 88ee826b90 fix(oxfmt): Handle literalline for script-in-vue (#20130)
Fixes #20084, closes #20085

Currently, in the context of `js-in-vue`, there are 2 types of code paths:

- Script block: `run_full()`
  - Converts a **string** formatted by `oxc_formatter` into Prettier's Doc format by splitting it into lines, joining with `hardline`
- Other cases: `run_fragment()`
  - Converts the **IR** formatted by `oxc_formatter` into Prettier's Doc format through an IR -> Doc implementation

The current issue can be described as a bug in the former.

As a result of simply splitting by lines, newlines within strings (newlines within `TemplateLiteral`s) are also treated as `hardline` (which are affected by parent indentation, it should be `literalline`), causing the nesting to deepen with each format.

To fix this, the former code path now also goes through an IR -> Doc conversion process.

Additionally, by doing this, the `printWidth` is also handled correctly.

In #20085, which involves re-converting a formatted string into an AST and then traversing it, was deemed undesirable.
2026-03-12 23:55:23 +00:00
leaysgur ab446efc72 test(oxfmt): Update conformance to save each diff (#20170)
It's more efficient for both humans and AI to be able to see individual diffs.
2026-03-10 03:44:12 +00:00
leaysgur 935c340dd2 test(oxfmt): Add css-in-js conformance (#20020)
Add tests for updated css-in-js.
2026-03-06 04:00:19 +00:00
leaysgur 57bbbd3678 test(oxfmt): Move conformance tests to standalone script (#19947) 2026-03-03 07:22:49 +00:00