Overview¶
The problem¶
Opinionated formatters (Prettier, Black, gofmt, rustfmt-with-defaults) made a trade: consistency in exchange for choice. That trade works well for many teams, but it has real costs:
- Style is not arbitrary. Alignment, vertical whitespace, and line breaks carry meaning. A hand-aligned table of constants or a deliberately split boolean expression is communication; an opinionated formatter erases it.
- One size fits one team. Organizations with established conventions (e.g. 4-space JS, tab-indented SCSS, trailing-comma-free Python) must either abandon them or abandon automated formatting.
- Polyglot repositories get inconsistent. A Svelte + Python project runs 2–4 formatters, each with a different config format, option vocabulary, and ignore-comment syntax — and they disagree about shared concepts such as indentation and quote style.
- New languages wait. Supporting a DSL (a template language, a config format, an in-house query language) in an existing formatter usually means writing a full printer in the formatter's host language.
The idea¶
rainbow-fmt separates what the code is (a syntax tree), how it should
be laid out (declarative, user-overridable rules), and how to print it
(a single, language-agnostic layout engine).
Every formatting decision is an option. Every option has a documented
default, and every option can be set to preserve: keep whatever the author
wrote. A team can start with "touch nothing but indentation" and tighten
decisions one at a time.
Principles¶
- Choice over opinion. If two reasonable developers could disagree about it, it is an option. Defaults exist; mandates do not.
preserveis a first-class value. Any decision can defer to the source text. This makes adoption incremental and non-destructive.- One vocabulary across languages.
indent,quotes,trailing_comma,max_width,blank_linesmean the same thing everywhere; languages add options only for concepts that are genuinely specific to them. - Small languages, declarative overrides. A language is a grammar plus a set of small rule functions built from shared helpers; users override any rule from their configuration with a checked expression, never code (ADR 0005).
- Never change meaning. Formatting output is verified to be syntactically equivalent to the input (same tree, ignoring trivia) and idempotent (formatting twice yields the same result).
- Embedded languages are normal. HTML contains CSS and JS; Svelte contains HTML, TS, and SCSS; Python contains SQL strings and docstrings. Nesting is handled by the core, not re-implemented per language.
- Explainable.
rainbow-fmt explainreports which rule and which option (from which config file) produced a given piece of output.
Non-goals¶
- Linting or semantic transformations (renaming, import sorting beyond whitespace/ordering options, dead-code removal). Rainbow changes layout, not program meaning.
- Winning benchmarks against single-language native formatters in the first releases. Performance must be good enough for editor-on-save and CI; it is a tuning target, not the design driver.
- Being "zero-config". Presets make it low-config; zero-config is Prettier's job.
Comparison¶
| Prettier / Black | EditorConfig | clang-format | rainbow-fmt | |
|---|---|---|---|---|
| Languages | Fixed set per tool | Any (whitespace only) | C-family | Any, via plugins |
| Configurability | Minimal by design | Indent / EOL / charset | Very high | Very high |
| Preserve author choices | Rarely | N/A | Some options | Every option |
| Embedded languages | Partial | No | No | Core feature |
| Adding a language | Write a printer | N/A | Not supported | Grammar + rule functions |
| Style inference from code | No | No | No | Planned (rainbow-fmt infer) |
Glossary¶
- CST — concrete syntax tree; the parse tree including every token.
- Trivia — whitespace and comments; everything the formatter may move.
- Doc / IR — the intermediate layout representation (text, line breaks, groups, indentation) that the printer turns into output.
- Rule — a declarative mapping from a syntax-tree pattern to a Doc template, parameterized by options.
- Language pack — a plugin providing a grammar, rules, option schema, and test fixtures for one language.
- Injection — a region of one language embedded in another (e.g.
<style>in HTML).