sparkles:dmd-fmt
A D formatter built on DMD's own lexer. It reads the token stream the compiler reads — comments and whitespace included — and consults the parse tree only as a table of byte offsets, which is why it formats files that do not parse and never has to guess where a comment belongs.
What it does to your code
Two tiers, and the distinction is the whole design:
- The layout tier is always on and changes no tokens. Your line breaks are yours; indentation, horizontal whitespace and blank-line runs are the formatter's. It is verified by token equality — lex the input and the output, compare every non-whitespace token — so it cannot silently change your program.
- The rewrite tier may add, remove or respell tokens — trailing commas, import order, string literal forms, brace style. Trailing-comma insertion is on by default; everything else is opt-in, and each rule ships with its own check because token equality cannot verify a rewrite.
Transformations that need to know types — turning format("%s", x) into an interpolated string, say — are not the formatter's job at all. Those are codemods, a separate tool built on this one.
Reference
- Layout decisions — every decision the layout tier makes, with a before/after for each. The examples are executed by the test suite, so they cannot drift from the implementation.
Configuration
Discovered from .editorconfig, honouring dfmt's key names so an existing project's configuration keeps working:
| Key | Default | Meaning |
|---|---|---|
indent_style | space | Spaces or tabs. |
indent_size | 4 | Columns per indentation level. |
tab_width | 4 | Columns a tab advances. |
dfmt_soft_max_line_length | 120 | The width wrapping targets. |
max_line_length | 120 | Accepted for dfmt compatibility. |
insert_final_newline | true | Guarantee one trailing newline. |
Unrecognised keys — including the dfmt_* style options not implemented yet — are ignored, which is the documented migration posture rather than an oversight.
Display width
Wrapping measures display cells, not UTF-8 bytes or Unicode scalar counts. The default sparkles.dmd_fmt.doc.displayWidth uses the owned base grapheme/width policy: e plus a combining acute accent is one cell, 日本語 is six, and a family ZWJ emoji (👨👩👧) or a regional-indicator flag (🇬🇧) is two. These are deterministic cell widths, not font shaping or pixel measurements.
For the document-layout API, a group containing 👨👩👧, a breakable space, and x fits flat at width four:
import sparkles.dmd_fmt.doc : group, text, line, layout, RenderOptions;
auto doc = group(text("👨👩👧"), line, text("x"));
assert(layout(doc, RenderOptions(width: 4)) == "👨👩👧 x");RenderOptions.measure can supply an alternative measurement function. Literal tab, CR and LF bytes each count as one in displayWidth; emitted newlines reset the layout column, while indentation tabs use RenderOptions.tabWidth. Malformed UTF-8 uses the base replacement policy with forward progress rather than an independent formatter decoder.
Escape hatches
// dfmt off … // dfmt on ranges are emitted byte-for-byte, and so are asm { … } bodies, #line/shebang directives, the __EOF__ tail, and every single-token construct (q{}, delimited and hex strings, interpolated literals, comments — no reflow, DDoc included).
Design documents
- M0 decision record — architecture, latency budget, scope (
D9) - Testing & decision documentation — why the reference pages here are also the test fixtures
- Codemod roadmap — where type-aware transformation lives
- The decision inventory — 160 catalogued formatting decisions, each scored for D
- The code-formatting survey — the research this is built on