Formatting

oxc-tsrx-fmt formats .tsrx files with Oxfmt, OXC's formatter, and then converts the result back into TSRX. Ordinary JS/TS files are formatted by Oxfmt directly, with a byte-for-byte-identical-output guarantee versus running oxfmt yourself.

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

Step by step:

  1. The file is scanned and projected to a valid-TSX copy, the same idea as linting, except here the placeholders are special markers designed to survive formatting.
  2. Oxfmt parses and formats that copy. Once.
  3. The lift walks the formatted output and turns it back into TSRX: markers become @if/@for/@switch/@try again, your code keeps its new formatting, dynamic closing tags are rebuilt from their opening expression, and raw <style> contents are copied from your original file untouched.
  4. As a final safety check, the lifted result is re-scanned and must match the structural fingerprint of the input. If anything doesn't line up, the tool errors out instead of writing a broken file.

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#

oxc-tsrx-fmt finds one .oxfmtrc.json or .oxfmtrc.jsonc by searching upward, or takes --config/-c. The standard layout options work: printWidth, singleQuote, semi, useTabs, tabWidth, trailingComma, arrowParens, bracketSpacing, singleAttributePerLine, and more, plus overrides and ignorePatterns. The full list is in Configuration.

Options that could silently change TSRX output in unsupported ways are rejected with a clear error before anything is formatted or written: sortImports, jsdoc formatting, embedded-language formatting, experimental flags, .editorconfig, and JS/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 the lift is safe#

A pinned, read-only corpus of real-world TSRX (the Markless oracle) proves that all 179 parser-valid tracked files format, re-parse, and converge (formatting a formatted file changes nothing), and that all 12 known invalid fixtures are rejected. Every raw <style> payload is compared byte-for-byte.