# Coding agent guides for `apps/oxfmt`

## 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, GraphQL 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
  - Entry point: `src-js/cli.ts` which uses `run_cli()` from `src/main_napi.rs`
  - Build with `pnpm build`
- Node.js API using napi-rs
  - Entry point: `src-js/index.ts` which uses `format()` from `src/main_napi.rs`
  - Build with `pnpm build`

When making changes, consider the impact on all paths.

### Platform considerations

Oxfmt is built for multiple platforms (Linux, macOS, Windows) and architectures.

When working with file paths in CLI code, be aware of Windows path differences:

- Use `std::path::Path` / `PathBuf` instead of manual string manipulation with `/`
- Be cautious with path comparisons and normalization across platforms
  - Avoid hardcoding `/` as a path separator; prefer `Path::join()`
  - Windows uses `\` as a path separator and has drive letter prefixes (e.g., `C:\`)

### Formatter implementations

Oxfmt utilizes different implementations depending on the file extension and filename:

- 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)

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.
Two consumers today:

- JSDoc fenced code blocks
- html-in-js fallback (`format_js_in_html_as_fallback` in `oxc_formatter/src/print/template/embed/html.rs`)

Tailwind class sorting (`sortTailwindcss`) splits responsibilities:

- Rust collects classes into `FormatElement::TailwindClass`
  - `className` etc. in JS/TS, `@apply` in CSS — incl. css-in-js and JSDoc fenced blocks
- and the JS side sorts them in one batch
  - `sortTailwindClasses` → tailwind's `getClassOrder`, which needs the resolved Tailwind config

Embedded boundaries carry classes through `DispatchResult::tailwind_classes`;
each embed site calls `DispatchResult::remap_tailwind_into(collector)` before consuming `docs`.

The four data paths (JS/TS top-level / standalone CSS / embedded CSS / JSDoc fenced CSS) are documented at `ExternalFormatter::to_external_callbacks`. No CSS goes to Prettier for this; the pure Rust build never collects (no sorter available).

Consequently, managing these various formatter implementations and handling their respective options are also part of Oxfmt's responsibilities.

### CLI implementations

Oxfmt shares code with Oxlint regarding its CLI implementation.

- Rust implementation: `crates/oxc_config`
- JS implementation: `apps/shared`

Please exercise extra caution when making changes to these files.

## Verification

```sh
cargo c
cargo c --no-default-features
cargo c --features detect_code_removal
```

Also run `clippy` for the same configurations and resolve all warnings.

Run tests with:

```sh
# Run unit test in Rust
cargo t
# Run E2E test
pnpm build-dev && pnpm t
# Update snapshots
pnpm t -u

# Run conformance test for xxx-in-js and js-in-xxx
pnpm build-dev && pnpm conformance
```

To manually verify the CLI behavior after building:

```sh
pnpm build-test

# Show help
node ./dist/cli.js --help
# Stdin (`npx prettier --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>
```

NOTE: `pnpm build-dev` combines `pnpm build-js` and `pnpm build-napi`, so you don't need to run them separately.

To compare formatting output with Prettier:

```sh
# 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>
```

## Test organization (`test/` directory)

Tests are organized into specific domains, each with its own structure.

### `test/api/`: Formatting result tests

Focuses on verifying formatting output. Use the Node.js API. No fixtures, test inputs are inline in each test file.

- Multiple `*.test.ts` files coexist in a flat directory (no subdirectories)
- Snapshots are colocated in `__snapshots__/` by Vitest

### `test/cli/`: CLI fixture-driven tests

A single `cli.test.ts` auto-discovers and runs all fixture directories via `utils.ts`.

- Each fixture directory contains:
  - `options.json` — array of test cases (args, cwd, env, stdin, etc.)
  - `fixtures/` — input files for the test cases
  - `*.snap.md` — file snapshots (one per test case, named `0.snap.md`, `1.snap.md`, …)
- Adding a new CLI test: create a new directory with `options.json` and `fixtures/`, then run the test to generate snapshots
  - `fixtures/` represents a single project structure
  - Multiple test cases (different args, cwd, etc.) against the same structure go in one `options.json`
  - If a scenario needs a different project layout, create a separate directory
  - Name related directories with a shared prefix (e.g., `nested_config/`, `nested_config_no_root/`)
- If exceptional test cases are required, place a separate `*.test.ts` file for them

### `test/lsp/`: LSP integration tests

Each test directory follows the 1:1:1 rule:

- 1 test file (`*.test.ts` with the same name as the directory)
- 0 or 1 `fixtures/` directory
- Snapshots are colocated in `__snapshots__/` by Vitest

Shared helpers are in `utils.ts` at the `test/lsp/` level.

## After updating `Oxfmtrc` (`src/core/oxfmtrc.rs`)

When modifying the `Oxfmtrc` struct (and configuration options):

- Run `just formatter-schema-json` to update `npm/oxfmt/configuration_schema.json`
- Run `just formatter-config-ts` to regenerate `src-js/config.generated.ts` from the schema
- Run `cargo test -p website_formatter` to update schema markdown snapshots
  - Then, `cargo insta accept`
