tidy v0.0.0

Technical details

How tidy is built, why its output matches Prettier, and how that is kept true.

tidy is not a formatter that happens to resemble Prettier. It is a port of Prettier's printers, checked against Prettier on every change.

The document printer

Prettier's design separates two jobs. A printer turns a syntax tree into a document: a tree of groups, line breaks, indents and concatenations that describes how the output could be laid out. A second pass walks that document with a width and decides which groups fit on one line and which break.

tidy ports both, in crates/tidy/src/doc. Because the second pass is the same algorithm with the same measurements — down to the table of character widths, so that a CJK character or an emoji counts the same — a document built the same way lays out the same way.

That is why matching Prettier is feasible at all. The hard, subtle part is shared; each language only has to build the same document.

The parsers

Each language needs a tree shaped the way Prettier's printer expects, which usually means porting the parser Prettier uses, not a faster one with a different shape:

Language Parser
JavaScript, TypeScript oxc, through oxc's formatter and Bun's port
HTML, Vue angular-html-parser
Handlebars the Handlebars parser, simple-html-tokenizer and @glimmer/syntax
CSS, SCSS, Less PostCSS, postcss-scss, postcss-less and their value and selector parsers
YAML the yaml package's lexer and CST parser, plus yaml-unist-parser's tree
GraphQL graphql-js, through Bun's port
TOML taplo
XML @xml-tools/parser, rewritten as a hand-written lexer and parser
Dockerfile dprint-plugin-dockerfile
INI prettier-plugin-ini
Markdown its own, following Prettier's handling

A tree with a different shape produces a different document, and a different document lays out differently. Where a port changes the shape — XML's parser is hand-written rather than generated by chevrotain — it is written to reject the same input and produce the same tree.

How it stays true

The test suite runs Prettier itself. For each corpus, every file goes through both tools and the bytes are compared. A difference fails the build unless it is a recorded deviation, and each deviation has its input in crates/tidy/tests/deviations.json with a test that fails if tidy stops printing what the entry says.

That is the real reason to trust the claim: it is not a promise, it is a test over 12,000 files.

It also means tidy tracks a Prettier version. tidy follows Prettier 3.9.9, and Markdown follows 3.9.10. When Prettier changes its output, tidy is behind until it is ported.

Stability

Prettier's output is not always a fixed point. For some inputs, formatting twice gives a third result, because the first pass changes something the second pass measures differently.

Where that happens, tidy prints the result Prettier settles on. So tidy check does not report a file that Prettier itself would reformat on the next run. This is not counted as a deviation, because the two agree once Prettier has stopped changing its mind.

Speed

Most of it is not clever. It is the absence of overhead:

  • No runtime start. A Node process costs tens of milliseconds before it reads a file.
  • No garbage collector. Trees are built in a bump allocator and dropped whole.
  • Buffers are reused. A Formatter keeps its scratch space between files, so a 12,000-file run does not allocate 12,000 times.
  • Every core. The walk and the formatting are spread across a thread pool. --threads 1 turns that off.
  • The walk is tuned too. tidy_walk is a separate crate, with a thread count that measured best per platform.

Repository layout

Crate What it is
crates/tidy The library: every parser, printer, the document printer, and config reading.
crates/tidy_cli The tidy command.
crates/tidy_napi The tidyrs npm package.
crates/tidy_walk The parallel directory walk.

Licensing

tidy is MIT, and ports MIT, ISC and Apache-2.0 code. Every project it ports from is listed in Credits, with the license.

Edit this page on GitHub MIT · tidy v0.0.0
Esc