|
| 1 | +# Coding agent guides for `crates/oxc_formatter` |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Prettier compatible JS/TS formatter (`oxfmt`'s Tier 1 backend), ported from [Biome](https://github.com/biomejs/biome). |
| 6 | +It turns a parsed AST into an IR ("Document") of `FormatElement`s, then prints that IR via the shared `oxc_formatter_core` Printer. |
| 7 | + |
| 8 | +- Built on `oxc_formatter_core` for the language-agnostic IR + Printer + builders + macros |
| 9 | + - See `crates/oxc_formatter_core/AGENTS.md` for the IR/pipeline details |
| 10 | +- This crate holds only the JS/TSX-specific layer |
| 11 | +- Parses with `oxc_parser` |
| 12 | + - Comments and other JS-specific concerns live here, not in core |
| 13 | + |
| 14 | +### Public API |
| 15 | + |
| 16 | +Call-sites use text-in entry points; |
| 17 | +The AST-wrapping IR primitives (`AstNode`, `Format`, `Buffer`, …) are `pub(crate)` and not part of the contract. |
| 18 | + |
| 19 | +- `format`: Format a whole file (text-in) |
| 20 | +- `format_fragment`: Format a JS/TS fragment for js-in-xxx embedding, parameterized by `FragmentContext` |
| 21 | + - Drives context-dependent decisions like forced parentheses / quote style |
| 22 | + - The formatter knows nothing about Prettier/Vue vocabulary, callers pass wrapped source |
| 23 | +- `format_program`: Special-purpose AST-in entry point |
| 24 | +- `ExternalCallbacks` (in `external_formatter.rs`): Callbacks for embedded-doc / Tailwind formatting delegated back to the host |
| 25 | + |
| 26 | +### Generated code |
| 27 | + |
| 28 | +`ast_nodes/generated/` and the `Format` glue are generated by `tasks/ast_tools`. |
| 29 | +After changing AST shapes or the generators, regenerate with `just ast`, never hand-edit files under `generated/`. |
| 30 | + |
| 31 | +## JS formatter specific features |
| 32 | + |
| 33 | +### Sort imports (`ir_transform/sort_imports/`) |
| 34 | + |
| 35 | +- Inspired by `eslint-plugin-perfectionist`, but not a 1:1 match (default group definitions differ too) |
| 36 | +- Implemented purely as a Rust IR transform; requires no JS callback |
| 37 | + |
| 38 | +### Format JSDoc (`formatter/jsdoc/`) |
| 39 | + |
| 40 | +- Derived from `prettier-plugin-jsdoc`, but not fully compatible |
| 41 | +- See `prettier_conformance/jsdoc` for the covered behavior |
| 42 | + |
| 43 | +### Sort Tailwind CSS |
| 44 | + |
| 45 | +- Derived from `prettier-plugin-tailwindcss` |
| 46 | +- Classes are collected during IR construction and sorted in one batch when the IR is stringified |
| 47 | +- Requires `ExternalCallbacks` (the sort itself is delegated to the host via `TailwindCallback`) |
| 48 | + |
| 49 | +### Embedded language formatting |
| 50 | + |
| 51 | +- Two directions: xxx-in-js (e.g. css/graphql/html in template literals) and js-in-xxx (e.g. vue/svelte) |
| 52 | +- Both work by Oxfmt injecting Prettier calls through `ExternalCallbacks` |
| 53 | + - This crate stays unaware of Prettier and only invokes the supplied callbacks |
| 54 | + |
| 55 | +## Fixing IR construction |
| 56 | + |
| 57 | +- Always keep the big picture in mind so a fix is not a one-off patch |
| 58 | + - Comments and parentheses are especially prone to side effects, handle them with care |
| 59 | +- We aim for Prettier compatibility, but the implementation strategy differs: |
| 60 | + - Prettier pre-classifies comments per context, whereas oxc_formatter decides on the spot |
| 61 | + - Biome works on a CST rather than an AST, so its code and strategy differ in detail too |
| 62 | + |
| 63 | +Above all, prioritize consistency, and always consider whether the divergence is a Prettier bug. |
| 64 | + |
| 65 | +## Verification |
| 66 | + |
| 67 | +```sh |
| 68 | +cargo c -p oxc_formatter |
| 69 | +cargo c -p oxc_formatter --features detect_code_removal |
| 70 | +``` |
| 71 | + |
| 72 | +Run `clippy` for the same configurations and resolve all warnings. |
| 73 | + |
| 74 | +### Fixtures tests |
| 75 | + |
| 76 | +Snapshot tests driven by fixture files under `tests/fixtures/{js,ts}/`. |
| 77 | +`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. |
| 78 | + |
| 79 | +```sh |
| 80 | +# Run all fixtures tests |
| 81 | +cargo test -p oxc_formatter --test mod |
| 82 | +# Run a subset by module path (directory structure = module hierarchy) |
| 83 | +cargo test -p oxc_formatter fixtures::js::comments |
| 84 | +# Review / accept snapshots after intentional changes |
| 85 | +cargo insta test --accept -p oxc_formatter --test mod |
| 86 | +``` |
| 87 | + |
| 88 | +Add a case by dropping a new file into `tests/fixtures/`, no manual registration needed. |
| 89 | + |
| 90 | +### Prettier conformance |
| 91 | + |
| 92 | +Compares output against Prettier's snapshots and tracks failures (not passes); results live in `tasks/prettier_conformance/snapshots/`. |
| 93 | + |
| 94 | +```sh |
| 95 | +cargo run -p oxc_prettier_conformance |
| 96 | +``` |
| 97 | + |
| 98 | +### Embedded conformance (`apps/oxfmt`) |
| 99 | + |
| 100 | +The embedded-language features (xxx-in-js / js-in-xxx) are validated end-to-end through the Oxfmt. |
| 101 | + |
| 102 | +Requires a dev build first. |
| 103 | + |
| 104 | +```sh |
| 105 | +pnpm --dir apps/oxfmt build-dev |
| 106 | +pnpm --dir apps/oxfmt conformance |
| 107 | +``` |
0 commit comments