lukeed/tidyPublic

History

readme.md

tidy

Prettier's style, at native speed.

tidy is a code formatter written in Rust. It prints what Prettier prints, for every language below, from one native binary. No Node required, but there's a Node binding if you want one.

It's fast. On a 12,000-file corpus of every language but XML, tidy checks everything in 280 ms. bun fmt takes 569 ms, oxfmt 1.2 s, and Prettier 18.5 s.

  • Matches Prettier: 100% of 3,906 JavaScript files and 3,308 of 3,309 TypeScript files in the bench corpus (with JSDoc formatting off)
  • Formats the code inside your code: styles and scripts in HTML and Vue, Markdown code blocks and front matter, CSS, GraphQL, HTML and Markdown in JS templates
  • Reads tidy.json, tidy.jsonc or tidy.toml, plus .editorconfig
  • Stable JS and TS output: where Prettier's output would change on its next run, tidy prints where it settles, so --check stays green in CI

Languages

LanguageStyle
JavaScript, TypeScript, JSXPrettier
HTML, Vue, Handlebars, MJMLPrettier
CSS, SCSS, LessPrettier
JSON, JSONC, JSON5Prettier
YAML, GraphQL, MarkdownPrettier
XML, SVG@prettier/plugin-xml
TOMLtaplo
Dockerfilesdprint-plugin-dockerfile
INI, .editorconfig, .npmrc, .gitconfigprettier-plugin-ini

Install

Nothing is published yet. For now, build it from a clone of this repo:

$ cargo install --path crates/tidy_cli

Usage

# print one file, formatted
$ tidy index.html

# list the files that aren't formatted; exits 1 if any
$ tidy src --check

# rewrite everything in place
$ tidy . --write

Without paths, tidy reads stdin. Run tidy --help for every flag.

Config

Each file uses the nearest tidy.json, tidy.jsonc or tidy.toml, on top of .editorconfig. Shared keys apply everywhere, and a language section overrides them:

{
  "width": 100,
  "js": { "semi": false, "quote": "single" },
  "markdown": { "wrap": "always" }
}

Node

import { format, check } from "tidyrs";

let output = format("<p   >hi</p>", { filepath: "index.html" });
//=> "<p>hi</p>\n"

check(output, { filepath: "index.html" });
//=> true

Rust

use tidy::{Language, Options, embed::Verbatim};

let language = Language::of_path("App.vue").unwrap();
let output = tidy::format(source, language, &Options::default(), Some("App.vue"), &mut Verbatim)?;

Benchmarks

Every tool checks every file of each corpus, using all cores, on an M1 Max. Times are the mean of both run orders. Embedded code is left alone here, so every tool does the same work.

CorpusFilestidybun fmtoxfmtdprintPrettier
JavaScript3,90678 ms100 ms123 ms8,528 ms
TypeScript3,30958 ms85 ms96 ms7,523 ms
JSON4,56986 ms149 ms114 ms323 ms3,528 ms
CSS3007.2 ms35 ms47 ms25 ms654 ms
SCSS1014.6 ms32 ms46 ms11 ms340 ms
Less3969.7 ms37 ms48 ms58 ms1,491 ms
HTML1516.4 ms34 ms237 ms14 ms327 ms
Vue2307.3 ms34 ms435 ms205 ms684 ms
Handlebars573.7 ms30 ms201 ms279 ms
Markdown2,83437 ms71 ms80 ms188 ms6,618 ms
YAML43524 ms35 ms46 ms104 ms1,271 ms
TOML3758.6 ms36 ms49 ms22 ms
GraphQL2,51137 ms63 ms72 ms271 ms1,988 ms
XML6,784130 ms616 ms6,017 ms
Dockerfiles253.6 ms3.7 ms458 ms
INI753.7 ms147 ms
All but XML12,071280 ms569 ms1,232 ms3,474 ms18,548 ms

With embedded code formatted too, the gap grows: on all but XML, tidy takes 286 ms, bun fmt 609 ms, oxfmt 2.4 s, and Prettier 30.9 s.

The full tables, with one-thread times, Biome, taplo, yamlfmt, lightningcss and oxc's CSS formatter, are in bench/results/format. The scripts are bench/run.ts and bench/js.ts.

Differences from Prettier

tidy prints something else on purpose in a few places: where Prettier's output changes what your code means, where it would change again on the next run, and a few choices, like formatting JSDoc comments by default, as prettier-plugin-jsdoc does. Turn that off with "js": { "jsdoc": false }. Each case is listed, with an example, in deviations.md.

Credits

tidy ports a lot of great work: Prettier, Biome, oxc, Bun's Rust ports, taplo, dprint, and more. See notice.md for all of them.

License

MIT © Luke Edwards