diff --git a/AGENTS.md b/AGENTS.md index 2373760700..606f9ff637 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -276,6 +276,9 @@ These test suites are pre-cloned and ready to search: - **TypeScript** (`tasks/coverage/typescript/`) - TypeScript syntax and semantics - **Prettier** (`tasks/prettier_conformance/prettier/`) - Formatting expectations +NOTE: These suites are script-cloned and fully gitignored. ripgrep respects `.gitignore`, so a plain `rg` inside them silently returns nothing. +Use `rg --no-ignore` (or `-u`) when searching them. + ### Snapshot Testing - Uses `insta` crate for snapshot testing diff --git a/apps/oxfmt/AGENTS.md b/apps/oxfmt/AGENTS.md index 56842b2f69..72ffcde778 100644 --- a/apps/oxfmt/AGENTS.md +++ b/apps/oxfmt/AGENTS.md @@ -1,19 +1,21 @@ # Coding agent guides for `apps/oxfmt` +Oxfmt is the integration layer that dispatches between the Rust formatters built on `oxc_formatter_core` and Prettier. + ## Overview The `oxfmt` implemented under this directory serves several purposes. -- Pure Rust CLI - - Minimum feature set, CLI usage only, no LSP, no Stdin support - - Formats JS/TS, JSON, CSS, GraphQL, YAML and TOML files, no xxx-in-js support - - Entry point: `main()` in `src/main.rs` - - Build with `cargo build --no-default-features` - JS/Rust hybrid CLI using `napi-rs` - Full feature set like CLI, Stdin, LSP, and more - - Format many file types with embedded language formatting support like Prettier + - Format many languages with embedded language formatting support like Prettier - Entry point: `src-js/cli.ts` which uses `run_cli()` from `src/main_napi.rs` - Build with `pnpm build` +- Pure Rust CLI + - Limited feature set, CLI usage only, no LSP, no Stdin support + - Formats only languages which we rewrite formatter in Rust + - Entry point: `main()` in `src/main.rs` + - Build with `cargo build --no-default-features` - Node.js API using napi-rs - Entry point: `src-js/index.ts` which uses `format()` from `src/main_napi.rs` - Build with `pnpm build` @@ -38,15 +40,10 @@ Oxfmt utilizes different implementations depending on the file extension and fil - Tier 1: Rust implementations using `oxc_formatter`, `oxc_formatter_json`, etc found in this repository - Tier 2: Rust implementations using external libraries like `oxc_toml` - Tier 3: Delegations to Prettier via NAPI-JS calls (e.g., for Vue or Markdown) -- Tier 4: Delegations to Prettier that require additional plugins (e.g., for Svelte) +- Tier 4: Delegations to Prettier that require additional Prettier plugins (e.g., for Svelte) NOTE: Rust written formatters never fall back to Prettier, since they exist to reduce the dependency on Prettier. -`oxc_formatter_css` (uses `oxc-css-parser`, a `raffia` fork) and `oxc_formatter_graphql` (uses `oxc-graphql-parser`, an `apollo-parser` fork) both should cover everything the bundled Prettier can parse for their language. - -However, parse errors may be reported as diagnostics, what the forks reject. -This is genuinely broken input or, for CSS, the tail of postcss's error tolerance (e.g. IE star hacks, some postcss-plugin specific syntax). - Embedded languages (e.g. css-in-js) go through `src/core/external_formatter.rs`, which assembles a `FormatDispatcher` (defined in `oxc_formatter_core`) that maps each language to a Rust formatter where implemented and to the Prettier Doc→IR path otherwise. A separate string-out channel (the `embedded_callback` in the same file, NOT the dispatcher) carries the cases that don't fit IR integration. @@ -109,7 +106,7 @@ pnpm build-test # Show help node ./dist/cli.js --help -# Stdin (`npx prettier --config= ` equivalent) +# Stdin (Prettier's `--config= ` equivalent) cat | node ./dist/cli.js --config= --stdin-filepath= # With log OXC_LOG=debug node ./dist/cli.js --threads=1 @@ -123,7 +120,7 @@ To compare formatting output with Prettier: # Use a shared config file (e.g., fmt.json) because Oxfmt and Prettier have different default printWidth. # Example fmt.json: { "printWidth": 80 } cat | node ./dist/cli.js --config=fmt.json --stdin-filepath= -npx prettier --config=fmt.json +node ./node_modules/prettier/bin/prettier.cjs --config=fmt.json ``` ## Test organization (`test/` directory) diff --git a/crates/oxc_formatter/AGENTS.md b/crates/oxc_formatter/AGENTS.md index 7f2ff82498..e37d154d7b 100644 --- a/crates/oxc_formatter/AGENTS.md +++ b/crates/oxc_formatter/AGENTS.md @@ -1,8 +1,10 @@ # Coding agent guides for `crates/oxc_formatter` +Follow @../oxc_formatter_core/FORMATTER_POLICY.md , this file holds only the JS/TS-specific rules and translations. + ## Overview -Prettier compatible JS/TS formatter (`oxfmt`'s Tier 1 backend), ported from [Biome](https://github.com/biomejs/biome). +Prettier compatible JS/TS formatter (`oxfmt`'s Tier 1 backend), ported from [Biome](https://github.com/biomejs/biome)'s `biome_js_formatter`. It turns a parsed AST into an IR ("Document") of `FormatElement`s, then prints that IR via the shared `oxc_formatter_core` Printer. - Built on `oxc_formatter_core` for the language-agnostic IR + Printer + builders + macros @@ -41,10 +43,11 @@ After changing AST shapes or the generators, regenerate with `just ast`, never h - Derived from `prettier-plugin-jsdoc`, but not fully compatible - See `prettier_conformance/jsdoc` for the covered behavior + - See also prettier_conformance/jsdoc/upstream-jsdoc-bugs.md ### Sort Tailwind CSS -- Derived from `prettier-plugin-tailwindcss` +- Backed by `prettier-plugin-tailwindcss` - Classes are collected during IR construction and sorted in one batch when the IR is stringified - Requires `ExternalCallbacks` (the sort itself is delegated to the host via `TailwindCallback`) @@ -65,25 +68,17 @@ Two directions: xxx-in-js (css/graphql/html in template literals) and js-in-xxx - Always keep the big picture in mind so a fix is not a one-off patch - Comments and parentheses are especially prone to side effects, handle them with care -- We aim for Prettier compatibility, but the implementation strategy differs: - - Prettier pre-classifies comments per context, whereas oxc_formatter decides on the spot - - Biome works on a CST rather than an AST, so its code and strategy differ in detail too - -Above all, prioritize consistency, and always consider whether the divergence is a Prettier bug. +- Biome works on a CST rather than an AST, so its code and strategy differ in detail from ours (and both differ from Prettier's) ### Comment placement invariants -Two layers of rules, know which one you are editing: - -- Invariants hold uniformly - - violating one is a bug even where Prettier disagrees (the "Never let it cross" list below, and the content/terminator ownership split) -- Compat tables record measured Prettier behavior that is not derivable from principle (the variant tables below) - - extend them by measuring Prettier, never by analogy, and pin every entry in a fixture +The shared two-layer split (invariants vs compat tables) and the base invariants live in FORMATTER_POLICY.md "Comment placement invariants"; +this section records their JS/TS translation and the measured compat tables. Repositioning a comment is allowed only relative to formatter-owned punctuation (e.g. printing `;` before a same-line trailing comment, see `FormatContentWithSemicolon`). -The `;` rule applies to statement _terminators_ only, never to member _separators_: +The `;` rule applies to statement terminators only, never to member separators: - Terminator: the `;` cannot be replaced by `,` — statements, class members (property/accessor via `FormatClassElementWithSemicolon`, bodyless methods in `MethodDefinition`). @@ -97,9 +92,7 @@ The `;` rule applies to statement _terminators_ only, never to member _separator we follow Prettier for now. If Prettier extends the rule to them, follow; each is a small mechanical `FormatContentWithSemicolon` adoption -When the content's source parentheses survive in the output -(return/throw arguments, sequence/assignment in the prettier#19263 positions), -comments inside them belong to the content and stay there; +When the content's source parentheses survive in the output (return/throw arguments, sequence/assignment in the prettier#19263 positions), comments inside them belong to the content and stay there; only comments after the closing paren may move behind the terminator (see `Comments::end_including_source_parens`). The "which positions keep their parens" table is intentionally encoded twice: @@ -108,8 +101,7 @@ The "which positions keep their parens" table is intentionally encoded twice: - and `write_trailing_comments_inside_parens` (expression side, matches on the parent) in `print/semicolon.rs` The statement side promises not to hide/move the comment; the expression side actually prints it. -Change them together: if they drift, the comment silently lands elsewhere (no assert catches it), -so pin every position change in a fixture. +Change them together: if they drift, the comment silently lands elsewhere (no assert catches it), so pin every position change in a fixture. The move-behind-the-terminator policy has four deliberate variants. When extending to a new node, pick by measuring Prettier, not by analogy: @@ -121,17 +113,12 @@ When extending to a new node, pick by measuring Prettier, not by analogy: | Class property/accessor (`FormatClassElementWithSemicolon`) | yes | cancels the move (stays own-line) | | Bodyless methods (`MethodDefinition`) | yes | cancels the move | -Never let it cross: +JS-side mechanics of the shared "never cross" invariants: -- A line boundary: line-based directives (`eslint-disable-line`, ...) must keep their meaning - - Line comments are printed via `line_suffix`, and own-line comments stay own-line (they become the next node's leading comments) +- Line boundary: line comments are printed via `line_suffix`, and own-line comments stay own-line (they become the next node's leading comments) - Both are structural guarantees, keep them -- User content (code, other comments): - - e.g. Prettier relocates a comment after a trailing array hole backward across commas to the last real element, that is an attachment artifact, not a rule - - We keep the comment in place and diverge intentionally -- A suppression comment's target: `prettier-ignore` / `oxfmt-ignore` needs its original text - preserved - - When hiding comments from a node (`limit_comments_up_to`), check `has_trailing_suppression_comment` first, or the node loses its suppression +- User content: e.g. Prettier relocates a comment after a trailing array hole backward across commas to the last real element — an attachment artifact, we keep the comment in place and diverge intentionally +- Suppression: when hiding comments from a node (`limit_comments_up_to`), check `has_trailing_suppression_comment` first, or the node loses its suppression Prettier's comment _attachment_ is position-heuristic and sometimes asymmetric (e.g. it moves `export type T = string /* c */;`'s comment behind the semicolon but not the non-exported form). @@ -160,46 +147,30 @@ Accepted edges (byte-identical to Prettier, semantically inert, idempotent): sound because no statement leaves its own trailing `;`, except a verbatim empty-statement body (`with (1) ;`, that `;` IS the body, i.e. content), where guard plus verbatim `;` re-parse as one extra inert `EmptyStatement` +## Known divergences + +Admission reasons and rules: see FORMATTER_POLICY.md "Known divergences". +Entries documented in this file so far — both of the comment-attachment-artifact class, details in "Comment placement invariants" above; this is not yet an exhaustive audit against the conformance snapshots: + +- A comment after a trailing array hole stays in place; Prettier relocates it backward across commas to the last real element +- Asymmetric attachment like `export type T = string /* c */;` (comment moved behind `;` only in the exported form): one uniform rule instead of emulating the asymmetry + ## Verification +Feature configs to check: + ```sh cargo c -p oxc_formatter cargo c -p oxc_formatter --features detect_code_removal ``` -Run `clippy` for the same configurations and resolve all warnings. +### Fixture tests -### Fixtures tests - -Snapshot tests driven by fixture files under `tests/fixtures/{js,ts}/`. -`build.rs` auto-discovers every `.{js,jsx,ts,tsx}` file and generates a test function per file; options are resolved from the nearest `options.json` up the directory tree. See `tests/README.md` for the full workflow. +See `tests/README.md` for the full workflow. ```sh # Run all fixtures tests cargo test -p oxc_formatter --test mod # Run a subset by module path (directory structure = module hierarchy) cargo test -p oxc_formatter fixtures::js::comments -# Review / accept snapshots after intentional changes -cargo insta test --accept -p oxc_formatter --test mod -``` - -Add a case by dropping a new file into `tests/fixtures/`, no manual registration needed. - -### Prettier conformance - -Compares output against Prettier's snapshots and tracks failures (not passes); results live in `tasks/prettier_conformance/snapshots/`. - -```sh -cargo run -p oxc_prettier_conformance -``` - -### Embedded conformance (`apps/oxfmt`) - -The embedded-language features (xxx-in-js / js-in-xxx) are validated end-to-end through the Oxfmt. - -Requires a dev build first. - -```sh -pnpm --dir apps/oxfmt build-dev -pnpm --dir apps/oxfmt conformance ``` diff --git a/crates/oxc_formatter_core/AGENTS.md b/crates/oxc_formatter_core/AGENTS.md index ac1f7ae064..67dd345590 100644 --- a/crates/oxc_formatter_core/AGENTS.md +++ b/crates/oxc_formatter_core/AGENTS.md @@ -2,9 +2,9 @@ ## Overview -Language-agnostic formatting infrastructure, ported from [Biome](https://github.com/biomejs/biome)'s `biome_formatter` crate. +Language-agnostic formatting infrastructure. -Every language-specific formatter in the oxc ecosystem (`oxc_formatter` for JS/TS, `oxc_formatter_json`, and future CSS/GraphQL/etc.) builds on this crate. +Every language-specific formatter in the oxc ecosystem builds on this crate. It owns the IR and the printing pipeline; it knows nothing about any concrete language (no comment placement rules, no quote rules, those live in the consumer crates). ### The IR ("Document") and pipeline @@ -19,10 +19,7 @@ Key IR pieces are all exported from the crate root. The semantics of each building block live in the `builders.rs` rustdocs. e.g. the mechanisms for verbatim multi-line content (`exact_line_breaks()` for blank runs exempt from newline collapsing, `literal_line_break()`, multiline `text()`, `text(..).without_expand_parent()`, and `mark_as_root` / `dedent_to_root`), with the non-obvious behaviors pinned by printer tests verified against Prettier's `printDocToString`. -Prettier doc primitives are ported on demand; still missing: - -- `hardlineWithoutBreakParent` (markdown tables) -- and the `trim` doc +Prettier doc primitives are ported on demand; still missing: `hardlineWithoutBreakParent` (for markdown tables) and the `trim`. ### The printer never trims @@ -58,7 +55,7 @@ The core is parameterized over a consumer-supplied context so it stays language- - `FormatContext` trait: no lifetime parameter - (avoids `oxc_allocator`'s `'ast` propagating through struct bounds and blocking anonymous lifetimes) - The allocator lives on `FormatState`, not the context -- `FormatOptions` trait: `indent_style()`, `indent_width()`, `line_width()`, `line_ending()`, `apply_core(CoreFormatOptions)`; +- `FormatOptions` trait: - `as_print_options() -> PrinterOptions` is provided from the getters - Core option types: `IndentStyle`, `IndentWidth`, `LineWidth`, `LineEnding` (exactly the `PrinterOptions` inputs; see the boundary section below) - `CoreFormatOptions`: the four bundled, for handing them across a host boundary (config resolver → language options) in one piece; @@ -69,23 +66,18 @@ The core is parameterized over a consumer-supplied context so it stays language- ### Embedded-language infrastructure (`embedded.rs`) -`EmbeddedContext` / `FormatDispatcher` / `DispatchResult` / `TailwindCollector` let one -formatter's IR be built inside another's document (e.g. graphql-in-js): +`EmbeddedContext` / `FormatDispatcher` / `DispatchResult` / `TailwindCollector` let one formatter's IR be built inside another's document (e.g. graphql-in-js): -- The orchestrator (oxfmt) assembles the dispatcher, mapping language names to - formatter implementations (or a Prettier fallback); formatter crates only invoke it +- The orchestrator (oxfmt) assembles the dispatcher, mapping language names to formatter implementations (or a Prettier fallback) + - Formatter crates only invoke it - Parent and child share one arena and one `GroupId` space through `EmbeddedContext` -- A language crate's `format_to_ir` entry returns `EmbeddedIr` (IR + pre-sort - Tailwind classes) — one shape for every child language, no per-crate tuples -- Cross-language contract data is first-class on `DispatchResult` (`tailwind_classes`); - only truly language-pair specific data crosses as `dyn Any` (e.g. HTML's `has_multiple_root_elements`), - core never learns concrete languages -- Consumers access `DispatchResult.docs` directly - (single-doc takes `docs.into_iter().next()`, multi-doc walks `docs`); - call `DispatchResult::remap_tailwind_into` first when the child may carry classes, - the printer's `debug_assert` catches a forgotten merge +- A language crate's `format_to_ir` entry returns `EmbeddedIr` (IR + pre-sort Tailwind classes), one shape for every child language, no per-crate tuples +- Cross-language contract data is first-class on `DispatchResult` (`tailwind_classes`) + - Only truly language-pair specific data crosses as `dyn Any` (e.g. HTML's `has_multiple_root_elements`), core never learns concrete languages +- Consumers access `DispatchResult.docs` directly (single-doc takes `docs.into_iter().next()`, multi-doc walks `docs`) + - Call `DispatchResult::remap_tailwind_into` first when the child may carry classes, the printer's `debug_assert` catches a forgotten merge -### What belongs in core (the boundary) +## What belongs in core (the boundary) Three layers, three admission rules. A type/fn that fits none of them belongs in a consumer crate. @@ -142,11 +134,11 @@ Consumers still need their own `insta` dev-dep so the recorded `source:` header ## Verification +Feature configs to check: + ```sh cargo c -p oxc_formatter_core cargo c -p oxc_formatter_core --features test_harness ``` -Run `clippy` for both configurations and resolve all warnings. - This crate has basic tests only of its own, it is exercised through the conformance/snapshot tests of its consumers. diff --git a/crates/oxc_formatter_core/FORMATTER_POLICY.md b/crates/oxc_formatter_core/FORMATTER_POLICY.md new file mode 100644 index 0000000000..08255b7a2e --- /dev/null +++ b/crates/oxc_formatter_core/FORMATTER_POLICY.md @@ -0,0 +1,146 @@ +# Formatter policy + +Shared policy for every formatter crate in the oxc ecosystem: + +- `oxc_formatter` (JS/TS) +- `oxc_formatter_json` +- `oxc_formatter_css` +- `oxc_formatter_graphql` +- `oxc_formatter_yaml` + +using `oxc_formatter_core`, integrated by `apps/oxfmt`. + +Each crate's `AGENTS.md` holds only language-specific rules and the crate-local translation of these policies; when they conflict, the crate file wins for that crate (and the conflict should be fixed). + +`apps/oxfmt` is bound by this policy as the integration side of its contracts (error handling, embedded conformance); everything about tier dispatch, configuration, and delegation to Prettier stays in its own `AGENTS.md`. + +## Prettier compatibility + +- Matching Prettier's output is the GOAL, not an absolute rule + - The higher the conformance coverage the better, but 100% parity is not what we promise + - What we provide above all is CONSISTENT, PREDICTABLE, EXPLAINABLE behavior + - When parity conflicts with that, "Known divergences" below arbitrates +- The canonical reference is Prettier's OUTPUT + - Its source code is an analysis aid, not a porting target + - Match its layout decisions, do not invent new ones + - Never mirror its internal logic 1:1; pin behavior with fixtures instead + - Comments explain BEHAVIOR and mechanism, never Prettier's code structure + - Naming an upstream function as a search key is fine (greppable in any Prettier version) — most valuable when pinning an artifact explanation or a compat-table entry + - File names / line numbers are not (not reproducible without a commit hash) +- Implementation strategies legitimately differ (e.g. Prettier pre-classifies comments per context; we decide on the spot with positional cursors) + - Compatibility is judged on bytes out, not code shape +- Before matching a mismatch, always consider whether it is a Prettier bug or artifact (see "Known divergences") +- The oracle version is the one pinned by the conformance submodule and `apps/oxfmt` bundles; fixtures are verified against it + - The LATEST Prettier is still worth consulting as a forward-looking aid: whether a bug we diverged on has been fixed upstream, or a behavior is about to change + - When the pin catches up to an upstream fix, converge and drop the divergence entry + +### Known divergences + +Deliberate divergences from Prettier's output. Admission reasons: + +- (1) Prettier's output would change program semantics: Formatting must NEVER do that (this reason is mandatory to act on, the others are judgment calls) + - Idempotency breakage is a symptom of this class: output that fails to re-parse, or re-parses to a different value/structure + - Verify semantics claims with the reference compiler/parser (tsc, dart-sass, lessc, ...), not intuition +- (2) Prettier's behavior is a bug: acknowledged as an open issue, OR explainable as an artifact (of comment attachment, parser structure, token lexing, ...) rather than an intended layout rule + - Typical case: comment relocations that are attachment artifacts of Prettier's parser + - "Looks wrong" without an artifact explanation is NOT enough; follow Prettier until the mechanism is understood +- (3) Prettier's behavior conflicts with a uniform rule across our formatter crates + - The uniform rule wins, even where Prettier's behavior is normal and internally consistent + - e.g. re-quoting SCSS `@warn "x"` per `singleQuote` where Prettier keeps the raw string verbatim + - This also covers Prettier's internal inconsistencies (same construct, different output depending on node kind or context): one principle beats emulating the inconsistency +- (4) The impact does not justify the matching cost (layout-only, rare trigger) + +Rules: + +- Style debates (`status:needs discussion` issues) are still followed; do not "improve" on taste + - Applying a rule already established across our crates (reason 3) is not taste; inventing a new style neither Prettier nor our crates have is +- Every divergence is documented in the owning crate's AGENTS.md "Known divergences" section, with the reason +- Every divergence is pinned by a fixture whose comments say which lines deviate from Prettier and why +- Affected conformance fixtures stay counted as failures; a new conformance failure is acceptable only under this policy and must be documented +- The rule cuts both ways: never "fix" a conformance failure by following Prettier into a documented divergence + - Check the crate's divergence list and open oxc issues for intent before treating a diff as a plain bug + +## Comment placement invariants + +Two layers of rules; know which one you are editing: + +- Invariants hold uniformly: violating one is a bug even where Prettier disagrees +- Compat tables record measured Prettier behavior that is not derivable from principle: extend them by measuring Prettier, never by analogy, and pin every entry in a fixture + +The invariants: + +- A comment never crosses user content (code, other comments, other tokens): it stays on its source side of every token + - When Prettier relocates a comment across tokens, that is an attachment artifact to diverge from (see "Known divergences"), not a rule to emulate +- A comment never crosses a line boundary: line-based directives (`eslint-disable-line`, ...) must keep their meaning + - Line comments print via `line_suffix`; own-line comments stay own-line +- A suppression comment (`prettier-ignore` / `oxfmt-ignore`) never loses its target, and its original text is preserved +- Repositioning is allowed only relative to formatter-OWNED punctuation: the formatter owns terminators (e.g. a statement's `;`) and the trivia up to them; the user owns content + - Terminator vs separator: a terminator cannot be replaced by another token (`;` after a JS statement); a separator can (`,`/`;` between interface members) + - Comments may move behind a terminator (per-language compat tables decide when); they always stay before a separator + +Per-language translations (which tokens are terminators, the compat tables, cursor bounds disciplines) live in each crate's AGENTS.md. + +## Error semantics + +`format()` / `format_to_ir()` return `Err` whenever they cannot produce output they can stand behind: + +- Any parse error bails out, even from an error-tolerant parser: never format a broken AST +- Print-stage internal errors are also `Err` +- The caller (oxfmt) decides what happens next: diagnostics for standalone files, template-as-is for embedded + +A parse error is a SAFE failure (the input is left as-is); silent token corruption is the UNSAFE one. +When triaging a report: don't corrupt → then accept → then pretty-print. + +## Verification + +Every crate: + +```sh +cargo c -p # plus any crate-specific feature configs listed in its AGENTS.md +``` + +Run `clippy` for the same configurations and resolve all warnings. + +Whenever comparing output against Prettier (fixture verification, manual checks), use the oxfmt-bundled one: + +```sh +# A bare `npx prettier` is NOT installed at the repo root; the bundled one is the same version as the oracle +node apps/oxfmt/node_modules/prettier/bin/prettier.cjs --parser --print-width=100 +``` + +NOTE: Prettier's default `printWidth` is `80`, but Oxfmt is `100`. + +### Fixture tests + +Snapshot tests driven by fixture files under `tests/fixtures/`; they cover what the Prettier conformance suite does not (suppression, divergence pins, embedded shapes, ...). +`build.rs` auto-discovers every fixture file and generates a test per file — add a case by dropping a file in, no registration needed. Options are resolved from the nearest `options.json` up the directory tree. + +```sh +cargo test -p +# Review / accept snapshots after intentional changes +cargo insta test --accept -p +``` + +Every expected output must be verified against Prettier, except fixtures pinning a "Known divergence". + +### Prettier conformance + +Compares output against Prettier's snapshots and tracks failures (not passes); results live in `tasks/prettier_conformance/snapshots/`. + +```sh +cargo run -p oxc_prettier_conformance +# Debug a specific test +cargo run -p oxc_prettier_conformance -- --filter +``` + +Failures must be either fixed or classified under "Known divergences". + +### Embedded conformance (`apps/oxfmt`) + +The embedded-language features (e.g. xxx-in-js / js-in-xxx) are validated end-to-end through Oxfmt. Requires a dev build first. + +```sh +pnpm --dir apps/oxfmt build-dev +pnpm --dir apps/oxfmt test +pnpm --dir apps/oxfmt conformance +``` diff --git a/crates/oxc_formatter_css/AGENTS.md b/crates/oxc_formatter_css/AGENTS.md index a203b48487..b0569d9813 100644 --- a/crates/oxc_formatter_css/AGENTS.md +++ b/crates/oxc_formatter_css/AGENTS.md @@ -1,5 +1,7 @@ # Coding agent guides for `crates/oxc_formatter_css` +Follow @../oxc_formatter_core/FORMATTER_POLICY.md , this file holds only the CSS/SCSS/Less-specific rules and translations. + ## Overview Prettier compatible CSS/SCSS/Less formatter (`oxfmt`'s Tier 1 backend), using the `oxc_formatter_core` APIs. @@ -8,13 +10,7 @@ Prettier compatible CSS/SCSS/Less formatter (`oxfmt`'s Tier 1 backend), using th - See `crates/oxc_formatter_core/AGENTS.md` for the IR/pipeline details - Two entry points: - `format()`: standalone files (returns a printable `Formatted`) - - `format_to_ir()`: embedded use via the dispatcher (e.g. css-in-js); - tolerates `${}` placeholders and `TopLevelDeclaration` -- The canonical reference is Prettier's OUTPUT (the conformance snapshots); its source is an analysis aid, not a porting target - - match its layout decisions, do not invent new ones - - never mirror its internal logic 1:1: pin behavior with fixtures, and keep implementation details of Prettier's code out of comments - - ONE exception: never reproduce output that changes program semantics - - See "Known divergences" section + - `format_to_ir()`: embedded use via the dispatcher (e.g. css-in-js); tolerates `${}` placeholders and `TopLevelDeclaration` ### Forked parser @@ -47,40 +43,30 @@ Prettier operates on `postcss` + three sub-parsers (`postcss-selector-parser`, ` ### Error semantics -`format()` / `format_to_ir()` return `Err` whenever they cannot produce output they can stand behind: +The shared policy applies; CSS specifics: -- `oxc-css-parser` is error-tolerant via `parser.recoverable_errors()`, but any parse error bails out; - - Never format a broken AST - - Exception: `TopLevelDeclaration`, tolerated ONLY by `format_to_ir()` - - The dominant css-in-js shape, `` css`display: flex;` ``) - - Standalone `format()` still rejects it as invalid CSS/SCSS/Less (Dart Sass rejects it too) -- print-stage internal errors are also `Err` -- The caller (oxfmt) decides what happens next - (diagnostics for standalone files, template-as-is for embedded) +- `oxc-css-parser` is error-tolerant via `parser.recoverable_errors()`, but any parse error still bails out +- Exception: `TopLevelDeclaration`, tolerated ONLY by `format_to_ir()` + - The dominant css-in-js shape, `` css`display: flex;` `` + - Standalone `format()` still rejects it as invalid CSS/SCSS/Less (Dart Sass rejects it too) ### Comments `oxc-css-parser` does not attach comments to the AST; they are collected via `ParserBuilder::comments()` into a positional cursor over `CssComment { span, inline }` (`inline` = `//`). -- Statement-level comments: flushed before each statement - (`flush_leading_comments`); consecutive same-line comments stay glued - (`*/ /*!`), but a comment is always followed by a line break before a node -- Value-level comments: flushed inside fill entries before the component they - precede (`flush_value_comments`); `//` comments expand the parent group and force a hardline after +- Statement-level comments: flushed before each statement (`flush_leading_comments`); + consecutive same-line comments stay glued (`*/ /*!`), but a comment is always followed by a line break before a node +- Value-level comments: flushed inside fill entries before the component they precede (`flush_value_comments`); + `//` comments expand the parent group and force a hardline after - Trailing (`value /* c */;`): flushed by `write_declaration` with the source gap before `;` preserved -- After each statement, the sequence DISCARDS unclaimed comments inside the - statement span (cursor must never point before a printed position) +- After each statement, the sequence DISCARDS unclaimed comments inside the statement span + (cursor must never point before a printed position) #### Placement invariants -The placement rules follow `crates/oxc_formatter/AGENTS.md` "Comment placement invariants" -(the invariant / compat-table two-layer split, and the terminator vs separator ownership rule); +The shared invariants (FORMATTER_POLICY.md "Comment placement invariants") apply; this section records their CSS translation: -this section records their CSS translation: - -- A comment never crosses user content (other values, other comments): - - it stays on its source side of every value/argument - `,` is a list SEPARATOR: a comment between an element and its comma stays BEFORE the comma - `a /* c */, b`; comments after it lead the next element - Declaration value lists (`write_value_groups`) and function arguments (`write_function`) route every comma through `write_group_comma` with the comma offset paired to its group @@ -92,12 +78,11 @@ this section records their CSS translation: - The positional cursor makes ownership a bounds discipline, not an attachment one: - a flush's upper bound must never extend past the next piece of user content, - and a declaration's `tail_bound` may only be consumed by the LAST comma group (`write_value_groups` clears it for every other group) -- Never let a comment cross a line boundary - - `//` comments force a hardline after; own-line comments stay own-line, and never move a suppression comment off its target +- Line-boundary rule in CSS terms: `//` comments force a hardline after; own-line comments stay own-line ### Line endings -`parse_stylesheet` normalizes `\r\n` / lone `\r` to `\n` BEFORE parsing. +`parse_stylesheet` normalizes `\r\n` and lone `\r` to `\n` BEFORE parsing. Unlike other formatters that normalize locally where needed, CSS has too many verbatim slices to handle case by case. And without this, raw `\r` reaching the core `text()` builder would panic. @@ -134,7 +119,7 @@ the exact set of supported positions (incl. id / attribute-value / class selecto ### Unknown at-rule params print VERBATIM -Prettier's parser hands params to sub-parsers only for a fixed allowlist (see `parser-postcss.js`, `is_value_parsed_at_rule`); +Prettier's parser hands params to sub-parsers only for a fixed allowlist (`is_value_parsed_at_rule`); everything else (`@apply`, `@tailwind`, `@custom-variant`, `@variant`, `@source`, ICSS `@value`, etc) stays a plain string the printer emits raw (`write_verbatim_at_rule_tail`). Re-spacing those tokens CORRUPTS Tailwind syntax: `dark:bg-x` → `dark: bg-x`, `py-1.5` → `py-10.5`, `@custom-variant dark (&:is(...))` → `dark(&: is(...))`. @@ -173,27 +158,20 @@ So the target behavior is finite: never destroy tokens Prettier wouldn't destroy When a dialect report comes in, first translate it: "which GENERAL postcss behavior are we missing?" Not "how do we support plugin Xxx?". Then absorb it at the highest possible rung of the escape-hatch hierarchy (top = cheapest, each rung covers whole classes of dialects at once): -1. Unknown at-rule prelude verbatim (`write_verbatim_at_rule_tail`) - zero-cost bucket: Tailwind, postcss-mixins, ICSS ride it for free -2. Raw fallbacks when the typed grammar rejects (raw component values, `TokenSeq`, `ImportPrelude.modifiers`) - `[attr=;]`, weird import tails -3. postcss word rules at the separator layer (`is_word_glued_number`, the `1#{$var}` glue, solidus words) - variant-agnostic, fixes xstyled + `theme()` + future unknown tokens in one place -4. `ParserOptions` flag + typed node (postcss-simple-vars) - ONLY when the formatter must make layout decisions INSIDE the construct. +1. Unknown at-rule prelude verbatim (`write_verbatim_at_rule_tail`) zero-cost bucket: Tailwind, postcss-mixins, ICSS ride it for free +2. Raw fallbacks when the typed grammar rejects (raw component values, `TokenSeq`, `ImportPrelude.modifiers`) `[attr=;]`, weird import tails +3. postcss word rules at the separator layer (`is_word_glued_number`, the `1#{$var}` glue, solidus words) variant-agnostic, fixes xstyled + `theme()` + future unknown tokens in one place +4. `ParserOptions` flag + typed node (postcss-simple-vars) ONLY when the formatter must make layout decisions INSIDE the construct. Promotion criteria, all three: (a) real user demand, (b) Prettier itself formats it structurally (not verbatim), (c) rungs 1-3 can't express it -5. A dedicated `CssVariant` - only for real languages with reference compilers (css/scss/less). +5. A dedicated `CssVariant` only for real languages with reference compilers (css/scss/less). Never for a plugin. Parser-side leniencies (in the `oxc-css-parser` fork) must be additive: -accept only input that previously errored, and never change the AST of input that already parsed -(e.g. dotted words try the typed `foo.$var` / `foo.bar(...)` parse first; only dart-sass-invalid shapes take the lenient path). +accept only input that previously errored, and never change the AST of input that already parsed (e.g. dotted words try the typed `foo.$var` / `foo.bar(...)` parse first; only dart-sass-invalid shapes take the lenient path). Every lenient path carries a comment citing the reference-compiler vs postcss behavior, a test pinning the strict shapes, and shows up as a visible expected-error flip in the parser's conformance snapshots. -Triage order for reports (failure modes are asymmetric, a parse error is a SAFE failure, oxfmt leaves the file/template as-is; silent token corruption like `sandstone.10` → `sandstone 0.1` is the UNSAFE one): -don't corrupt (verbatim paths) → then accept (leniency) → then pretty-print (structure). +Triage order for reports follows the shared policy (don't corrupt → then accept → then pretty-print); the CSS-shaped example of the UNSAFE failure is silent token corruption like `sandstone.10` → `sandstone 0.1`. Red flags that the approach is drifting: specific plugin names accumulating in code, or a leniency that reinterprets previously-valid input. @@ -209,15 +187,9 @@ Covered: NOT covered: `$(var)` interpolation (`margin-$(dir): 10px`, `.icon.is-$(network)`), selector-position bare `$var` (`.$prefix`), comment substitutions (`<<$(var)>>`). -### Known divergences +## Known divergences -Deliberate divergences from Prettier. Three admission reasons: - -1. Prettier's output would change program semantics (formatting must never do that) -2. The impact does not justify the matching cost -3. A uniform principle across our formatter crates beats emulating a Prettier inconsistency - -Notable divergences are: +Admission reasons and rules: see FORMATTER_POLICY.md "Known divergences". Notable divergences are: - An end-of-line trailing `//` comment never counts toward the print width (reason 3) - Prettier's postcss printer does not distinguish `//` from `/* */`: it prints both inline, @@ -335,45 +307,12 @@ Notable divergences are: ## Verification -```sh -cargo c -p oxc_formatter_css -``` - -Run `clippy` and resolve all warnings. - ### Fixture tests -Snapshot tests driven by fixture files; covers what the Prettier conformance suite does not (placeholder at-rules, custom-property re-parsing, embedded css-in-js, etc.). - -Fixtures are grouped per language (`format/{css,scss,less}/`; test modules mirror the directories), with the shared `options.json` at the `format/` / `embedded/` level (the harness walks up to the nearest one). -`embedded/scss/` is explicit about the dispatcher's variant=Scss hardcoding. - -Unit tests in `tests/fixtures/mod.rs` cover parse-error `Err` semantics (`parse_error_is_err`). -Fixtures under `embedded/` route through `format_to_ir` instead of `format()`; the `embedded_debug` example formats files the same way for quick comparison. - -Every expected output must be verified against Prettier (3.9.6, the current submodule). -`npx prettier@3.9.6 --parser ` at both `--print-width 80` and `100` (the harness snapshots both). - -Exception: a fixture may pin an entry from "Known divergences" (e.g. `map-item-parens.scss`); -its comments must say which lines deviate from Prettier and why. - -```sh -cargo test -p oxc_formatter_css -# Review / accept snapshots after intentional changes -cargo insta review -p oxc_formatter_css -``` +The harness snapshots both `--print-width 80` and `100`; verify fixtures at both widths. ### Prettier conformance -Compares output against Prettier's snapshots and tracks failures (not passes); -results live in `tasks/prettier_conformance/snapshots/prettier.css.snap.md` / `prettier.scss.snap.md` / `prettier.less.snap.md`. - -```sh -cargo run -p oxc_prettier_conformance -# Debug a specific test -cargo run -p oxc_prettier_conformance -- --filter css/atrule -``` - At the current version (v3.9.6), the divergences of seven files have been confirmed and are intentional (see "Known divergences"): - CSS: `css/stylefmt-repo/at-media/at-media.css`, `css/stylefmt-repo/cssnext-example/cssnext-example.css`, `css/postcss-plugins/postcss-nesting.css` @@ -394,17 +333,6 @@ Two more files fail with MIXED hunks; they can't pass as files (the intentional - within-a-word runs (`1+1+1+1`, `calc(100%+2px)`) match - glued number-ish runs are ONE postcss word and print raw (see `is_word_glued_number`) -### Embedded conformance (`apps/oxfmt`) - -The embedded-language features (css-in-js) are validated end-to-end through the Oxfmt. - -Requires a dev build first. - -```sh -pnpm --dir apps/oxfmt build-dev -pnpm --dir apps/oxfmt conformance -``` - ### Manual checks ```sh diff --git a/crates/oxc_formatter_graphql/AGENTS.md b/crates/oxc_formatter_graphql/AGENTS.md index fb73e654e6..bc73a90704 100644 --- a/crates/oxc_formatter_graphql/AGENTS.md +++ b/crates/oxc_formatter_graphql/AGENTS.md @@ -1,5 +1,7 @@ # Coding agent guides for `crates/oxc_formatter_graphql` +Follow @../oxc_formatter_core/FORMATTER_POLICY.md , this file holds only the GraphQL-specific rules and translations. + ## Overview Prettier compatible GraphQL formatter (`oxfmt`'s Tier 1 backend), using the `oxc_formatter_core` APIs. @@ -8,12 +10,7 @@ Prettier compatible GraphQL formatter (`oxfmt`'s Tier 1 backend), using the `oxc - See `crates/oxc_formatter_core/AGENTS.md` for the IR/pipeline details - Two entry points: - `format()`: standalone files (returns a printable `Formatted`) - - `format_to_ir()`: embedded use via the dispatcher (e.g. graphql-in-js); - allocates from the shared `EmbeddedContext` arena, emits no BOM / trailing newline, - and leaves `propagate_expand()` to the parent document -- The canonical reference is Prettier's OUTPUT (the conformance snapshots); its source is an analysis aid, not a porting target - - match its layout decisions, do not invent new ones - - never mirror its internal logic 1:1: pin behavior with fixtures, and keep implementation details of Prettier's code out of comments + - `format_to_ir()`: embedded use via the dispatcher (e.g. graphql-in-js) ### Forked parser @@ -31,12 +28,10 @@ The fork aligns with graphql-js v17.x, which Prettier 3.9 targets: ### Error semantics -`format()` / `format_to_ir()` return `Err` whenever they cannot produce output they can stand behind: +The shared policy applies; GraphQL specifics: -- `oxc-graphql-parser` is error-tolerant (returns an AST even for invalid input), but any parse error bails out; never format a broken AST. +- `oxc-graphql-parser` is error-tolerant (returns an AST even for invalid input), but any parse error still bails out - Several printer shortcuts (e.g. `close_delim_start`) are sound only under this guarantee -- The caller (oxfmt) decides what happens next - - Diagnostics for standalone files, template-as-is for embedded ### Comments @@ -46,7 +41,7 @@ Prettier collects them from the token stream and attaches leading/trailing/dangl This crate instead collects comment spans into a positional cursor, drained in source order by claim points spread through the printers (the `flush_*` helpers in `src/comments.rs`; behavior pinned by `tests/fixtures/graphql/comments-inside-node-spans.graphql`). -Placement invariant (same as `oxc_formatter`'s): a comment stays between the source tokens it sat between, and a same-line trailing comment stays on its line. +The shared placement invariants apply: a comment stays between the source tokens it sat between, and a same-line trailing comment stays on its line. Two bounded exceptions: @@ -89,8 +84,8 @@ Blank-line runs inside block strings are part of the string VALUE and are emitte ## Known divergences -Deliberate divergences from Prettier, all one class: Prettier relocates a comment (an attachment artifact of `graphql-js` node boundaries, not a layout rule), -we keep it between its source tokens on its source line. +Admission reasons and rules: see FORMATTER_POLICY.md "Known divergences". +All current entries are one class: Prettier relocates a comment (an attachment artifact of `graphql-js` node boundaries, not a layout rule), we keep it between its source tokens on its source line. - `"desc" type # c`: Prettier pulls the comment backwards across the keyword onto the description's line - `"""d"""` + break + `# c` + break + `type A`: Prettier pushes the comment forward across the keyword (`type # c` + break + `A`) @@ -100,65 +95,17 @@ we keep it between its source tokens on its source line. ## Verification -```sh -cargo c -p oxc_formatter_graphql -``` - -Run `clippy` and resolve all warnings. - -### Fixtures tests - -Snapshot tests driven by fixture files under `tests/fixtures/graphql/`, -covering what the Prettier conformance suite does not (suppression, string re-encoding, comment positions, extensions, ... — see the file names). -`build.rs` auto-generates a test case from every `.graphql` file using the core `test_support` harness. - -Unit tests in `tests/fixtures/mod.rs` cover parse-error `Err` semantics and BOM preservation. - -```sh -cargo test -p oxc_formatter_graphql -# Review / accept snapshots after intentional changes -cargo insta review -p oxc_formatter_graphql -``` - -Add a case by dropping a new `.graphql` file into the directory, the build script picks it up. - -### Prettier conformance - -Compares output against Prettier's snapshots and tracks failures (not passes); -results live in `tasks/prettier_conformance/snapshots/prettier.graphql.snap.md`. -The `graphql` language is part of the shared conformance binary. - -```sh -cargo run -p oxc_prettier_conformance -# Debug a specific test -cargo run -p oxc_prettier_conformance -- --filter graphql// -``` - -### Embedded conformance (`apps/oxfmt`) - -The embedded-language features (gql-in-js) are validated end-to-end through the Oxfmt. - -Requires a dev build first. - -```sh -pnpm --dir apps/oxfmt build-dev -pnpm --dir apps/oxfmt conformance -``` - -### Manual checks +Manual checks: ```sh cargo run -p oxc_formatter_graphql --example graphql_formatter [filename] -# Compare with Prettier -npx prettier --parser=graphql [filename] ``` A good large real-world stress input is GitHub's public GraphQL schema (~72k lines). -It is too large and third-party to commit as a fixture, -bug-catching shapes are distilled into `tests/fixtures/graphql/implements-width.graphql`): +It is too large and third-party to commit as a fixture, bug-catching shapes are distilled into `tests/fixtures/graphql/implements-width.graphql`): ```sh curl -sL https://docs.github.com/public/fpt/schema.docs.graphql -o /tmp/github-schema.graphql -diff <(npx prettier --parser=graphql /tmp/github-schema.graphql) \ +diff <(node apps/oxfmt/node_modules/prettier/bin/prettier.cjs --parser=graphql /tmp/github-schema.graphql) \ <(cargo run -q -p oxc_formatter_graphql --example graphql_formatter /tmp/github-schema.graphql) ``` diff --git a/crates/oxc_formatter_json/AGENTS.md b/crates/oxc_formatter_json/AGENTS.md index 8792159cf9..0d700bafb5 100644 --- a/crates/oxc_formatter_json/AGENTS.md +++ b/crates/oxc_formatter_json/AGENTS.md @@ -1,5 +1,7 @@ # Coding agent guides for `crates/oxc_formatter_json` +Follow @../oxc_formatter_core/FORMATTER_POLICY.md , this file holds only the JSON-specific rules and translations. + ## Overview Prettier compatible JSON/JSONC/JSON5/JSON.stringify formatter (`oxfmt`'s Tier 1 backend), using the `oxc_formatter_core` APIs. @@ -37,33 +39,3 @@ Parsing always uses `SourceType::default()` (JS); `variant` only gates comment v - Because the lenient JS parse accepts them (also matching Prettier, which routes every variant through its JS printer) See the doc comments on `JsonVariant` in `src/options.rs` for the per-variant rules. - -## Verification - -```sh -cargo c -p oxc_formatter_json -``` - -Run `clippy` and resolve all warnings. - -### Fixtures tests - -Snapshot tests driven by fixture files under `tests/fixtures/{json,jsonc,json5,json-stringify}/` (one directory per variant). -`build.rs` auto-generates a test case from every `.{json,jsonc,json5}` file using the core `test_support` harness. -Each directory's `options.json` sets the `variant` (and any per-case options); `json-stringify` fixtures use the `.json` extension. - -```sh -cargo test -p oxc_formatter_json -# Review / accept snapshots after intentional changes -cargo insta review -p oxc_formatter_json -``` - -Add a case by dropping a new file into the matching variant directory, the build script picks it up. - -### Prettier conformance - -Compares output against Prettier's snapshots and tracks failures (not passes); results live in `tasks/prettier_conformance/snapshots/`. The `json` language is part of the shared conformance binary. - -```sh -cargo run -p oxc_prettier_conformance -``` diff --git a/crates/oxc_formatter_yaml/AGENTS.md b/crates/oxc_formatter_yaml/AGENTS.md index 970933d854..60a9fcb3dd 100644 --- a/crates/oxc_formatter_yaml/AGENTS.md +++ b/crates/oxc_formatter_yaml/AGENTS.md @@ -1,5 +1,7 @@ # Coding agent guides for `crates/oxc_formatter_yaml` +Follow @../oxc_formatter_core/FORMATTER_POLICY.md , this file holds only the YAML-specific rules and translations. + ## Overview Prettier compatible YAML formatter (`oxfmt`'s Tier 1 backend), using the `oxc_formatter_core` APIs. @@ -7,10 +9,7 @@ Prettier compatible YAML formatter (`oxfmt`'s Tier 1 backend), using the `oxc_fo - Built on `oxc_formatter_core` for the language-agnostic IR + Printer + builders + macros - See `crates/oxc_formatter_core/AGENTS.md` for the IR/pipeline details - Two entry points (see their docs in `src/format.rs`): - `format()` for standalone files, `format_to_ir()` for embedded use via the dispatcher (e.g. yaml-in-markdown) -- The canonical reference is Prettier 3.9's OUTPUT (the conformance snapshots); its source is an analysis aid, not a porting target - - match its layout decisions — EXCEPT where they are bugs or internally inconsistent (see below) - - never mirror its internal logic 1:1: pin behavior with fixtures, and keep implementation details of Prettier's code out of comments + - `format()` for standalone files, `format_to_ir()` for embedded use via the dispatcher (e.g. yaml-in-markdown) ### Parser @@ -22,11 +21,11 @@ The split between the scalar's own output and the inter-item gap is printer poli ### Error semantics -`oxc-yaml-parser` is fail-fast (no partial AST), so `format()` / `format_to_ir()` return `Err` on any syntax error and never format a broken AST. -The caller (oxfmt) decides what happens next. +The shared policy applies; YAML specifics: -Under-indented multi-line flow scalars (prettier#8602) are one such error. -Prettier 3.9.5 also rejects them since its `yaml@2` upgrade, so the string corruption reported there cannot happen in either implementation. +- `oxc-yaml-parser` is fail-fast (no partial AST), so any syntax error is an `Err` +- Under-indented multi-line flow scalars (prettier#8602) are one such error + - Prettier 3.9.6 also rejects them since its `yaml@2` upgrade, so the string corruption reported there cannot happen in either implementation ### Line endings @@ -44,16 +43,7 @@ Stream-tail end comments (`write_end_comments`) are the one document-layer excep ## Known divergences -Follow Prettier by default. -The exceptions (shared policy across all the formatter crates), are the cases where consistent output beats conformance percentage: - -- Prettier bugs acknowledged as open issues -- behaviors that are internally inconsistent (same construct, different output depending on node kind or context) - -Affected conformance fixtures stay counted as failures. -Style debates (`status:needs discussion` issues) are still followed, do not "improve" on taste for now. - -Current divergences: +Admission reasons and rules: see FORMATTER_POLICY.md "Known divergences". Current divergences: - anchor/tag order (prettier#19524): source order is preserved, never reordered - `# prettier-ignore` range (prettier#13008): suppresses exactly one node, never every following node @@ -78,42 +68,10 @@ Current divergences: ## Verification -```sh -cargo c -p oxc_formatter_yaml -``` - -Run `clippy` and resolve all warnings. - -### Fixture tests - -Snapshot tests driven by fixture files under `tests/fixtures/yaml/`, covering what the Prettier conformance suite does not (`# oxfmt-ignore`, divergence shapes, etc). -`build.rs` auto-generates a test case from every `.yaml` file using the core `test_support` harness; add a case by dropping a new file into the directory. - -```sh -cargo test -p oxc_formatter_yaml -# Review / accept snapshots after intentional changes -cargo insta review -p oxc_formatter_yaml -``` - -### Prettier conformance - -Compares output against Prettier's snapshots and tracks failures (not passes); results live in `tasks/prettier_conformance/snapshots/prettier.yaml.snap.md`. -The `yaml` language is part of the shared conformance binary. - -```sh -cargo run -p oxc_prettier_conformance -# Debug a specific test -cargo run -p oxc_prettier_conformance -- --filter yaml// -``` - -Failures must be either fixed or classified: a new failure is acceptable only when it falls under the non-follow policy above, and it must be documented there. - -### Manual checks +Manual checks: ```sh cargo run -p oxc_formatter_yaml --example yaml_formatter [filename] # Dump the formatter IR DUMP_IR=1 cargo run -p oxc_formatter_yaml --example yaml_formatter [filename] -# Compare with Prettier -npx prettier --parser=yaml [filename] ```