Formatting

oxfmt formats your .tsrx files with OXC's real formatter, then converts the result back into TSRX. Ordinary JS/TS files go straight to Oxfmt, and the output is byte-for-byte identical to running oxfmt yourself.

Input and output#

Here is one of the files this project tests against, exactly as it is committed before formatting:

type Item={id:string;label:string};export function Rows({items}:{items:Item[]})@{<ul>@for(item of items;index i;key item.id){<li data-index={i}>{item.label}</li>}@empty{<li>Empty</li>}</ul>}

becomes:

type Item = { id: string; label: string };
export function Rows({ items }: { items: Item[] }) @{
  <ul>
    @for (item of items; index i; key item.id) {
      <li data-index={i}>{item.label}</li>;
    } @empty {
      <li>Empty</li>;
    }
  </ul>;
}

Both files are committed fixtures, and the test suite formats the first one and compares it against the second on every run, so this is real output rather than a hand-written example. You can read them in tests/fixtures/control (opens in new tab).

How a format run works#

The file you wrote, byte for byte. Nothing is changed on disk at any point.

export function Cart({ items }: Props) @{
  var total = 0;
  debugger;

  <section class="cart">
    @if (items.length > 0) {
      @for (const item of items; key item.id) {
        <Row item={item} />
      }
    } @else {
      <Empty />
    }
  </section>
}

Four steps:

  1. Copy. Build a valid TSX copy, the same idea as linting, except the placeholders here are markers designed to survive formatting.
  2. Format. Oxfmt parses and formats that copy, once.
  3. Convert back. Markers become @if, @for, @switch, and @try again, and your code keeps its new formatting.
  4. Check. The result is re-read and compared against the original. If the structure does not match, the tool errors out instead of writing a broken file.

Two things are carried over rather than reformatted: dynamic closing tags are rebuilt from their opening expression, and whatever is inside a raw <style> block is copied from your file untouched.

Usage#

See it run
# Check without modifying files; exits 1 and lists files that differ
oxc-tsrx-fmt --check src/Counter.tsrx
Checking formatting...

src/Counter.tsrx (0ms)

Format issues found in above 1 files. Run without `--check` to fix.
Finished in 0ms on 1 files using 18 threads.


# Format and write files; success prints only the summary line
oxc-tsrx-fmt --write src/Counter.tsrx src/View.tsx
Finished in 0ms on 2 files using 18 threads.


# Editor/stdin mode: formatted source goes to stdout
oxc-tsrx-fmt --stdin-filepath=src/Counter.tsrx < src/Counter.tsrx
export function Counter({ start }: { start: number }) @{
  var count = start;
  console.log("mounted");
  debugger;

  <div class="counter">
    <span>{count}</span>
  </div>;
}


# Explicit config and worker count
oxc-tsrx-fmt --write --config config/format.json --threads=4 src/Counter.tsrx
Finished in 0ms on 1 files using 4 threads.


# The explicit config switched the file to single quotes, no semicolons
cat src/Counter.tsrx
export function Counter({ start }: { start: number }) @{
  var count = start
  console.log('mounted')
  debugger

  ;<div class="counter">
    <span>{count}</span>
  </div>
}
Real output, captured at build time. The sample src/Counter.tsrx starts with double quotes and no statement semicolons after JSX, so the formatter has work to do.

Writes are transactional: every file in the batch must format successfully before the first one is replaced on disk, so a crash or bad file never leaves your project half-formatted. Symbolic links are rejected.

Configuration#

Your .oxfmtrc.json works as usual. oxfmt searches upward from the current directory to find it, or takes a --config path, and the layout options you already use all apply: printWidth, singleQuote, semi, tabWidth, trailingComma, and the rest, plus overrides and ignorePatterns. Configuration has the full list.

A few options are refused with a clear error before anything is written, because they would change .tsrx output in ways this project cannot yet guarantee: sortImports, jsdoc, embedded-language formatting, experimental flags, .editorconfig, and JS or TS config files.

CSS inside <style> is preserved, not formatted#

Bytes inside a raw <style> element are copied through exactly as you wrote them. The upstream OXC CSS formatter currently can't be used without patching OXC's dependency graph, and this project's core rule is no patches, so CSS formatting waits until upstream exposes a clean package boundary.

How we know it is safe#

This is tested against a real TSRX codebase, not just fixtures. All 179 valid files format, re-parse, and settle, meaning formatting an already-formatted file changes nothing. All 12 broken files are rejected rather than mangled, and every raw <style> block comes out byte for byte identical.