mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
docs(formatter_core): extract FORMATTER_POLICY (#25233)
This commit is contained in:
1 parent
8c2ed1a04f
commit
eaa7c692d2
9 files changed
+252
-338
No files matched your search
@@ -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
|
||||
|
||||
+11
-14
@@ -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=<cfg> <file>` equivalent)
|
||||
# Stdin (Prettier's `--config=<cfg> <file>` equivalent)
|
||||
cat <file> | node ./dist/cli.js --config=<cfg> --stdin-filepath=<file>
|
||||
# With log
|
||||
OXC_LOG=debug node ./dist/cli.js --threads=1 <file>
|
||||
@@ -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 <file> | node ./dist/cli.js --config=fmt.json --stdin-filepath=<file>
|
||||
npx prettier --config=fmt.json <file>
|
||||
node ./node_modules/prettier/bin/prettier.cjs --config=fmt.json <file>
|
||||
```
|
||||
|
||||
## Test organization (`test/` directory)
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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.
|
||||
@@ -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 <crate> # 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 <parser> --print-width=100 <file>
|
||||
```
|
||||
|
||||
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 <crate>
|
||||
# Review / accept snapshots after intentional changes
|
||||
cargo insta test --accept -p <crate>
|
||||
```
|
||||
|
||||
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 <path>
|
||||
```
|
||||
|
||||
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
|
||||
```
|
||||
@@ -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 <variant>` 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
|
||||
|
||||
@@ -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/<dir>/<file>
|
||||
```
|
||||
|
||||
### 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)
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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/<dir>/<file>
|
||||
```
|
||||
|
||||
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]
|
||||
```
|
||||
Reference in new issue
Block a user