Safety
A formatter that rewrites your source automatically — inside your build, without being asked — has to be unable to damage it.
Curb verifies every file in memory before anything is written to disk. A file that fails verification is reported and left exactly as it was. It is never partially written, and the build tells you rather than continuing quietly.
| Check | What it catches | On failure |
|---|---|---|
| Token stream unchanged | Spaces and newlines moved; nothing else | Reported as CURB0002, file left untouched |
| Output re-parses | A printer bug that welds two tokens together | Same |
| Formatting is idempotent | format(format(x)) = format(x) |
Caught by the test suite and the CI corpus gate |
All #if branches covered |
One file can have several token streams | Each symbol set is verified independently |
For syntax style rules, which change tokens deliberately, the verifier is told exactly which rewrite was requested. Each is allowed for specifically. Everything else is still a failure, so opting into a rewrite widens the check by exactly one thing rather than switching it off.
The re-parse is conditional: the printer tracks whether it did anything capable of moving a token boundary. Where it did not, the check is skipped. That is a deliberate saving — it is also why Curb does not need the unconditional re-parse that costs other formatters a second parse per file. See Design principles for the full reasoning.
Idempotency matters more than it sounds. A formatter that does not converge makes curb check fail
on files curb format just wrote, which turns a build integration into an infinite loop of diffs.
Curb does not have a dedicated printer for every construct in C#. Anything it does not recognise is emitted verbatim from the original source. Coverage grows without ever putting code at risk.
curb check ./src --coverage
This reports which syntax kinds are still being emitted verbatim, and how often.
[Generated/*.cs]
generated_code = true
[Legacy/*.cs]
dotnet_diagnostic.IDE0055.severity = none
Files carrying an <auto-generated> header are skipped as well.
These are enforced on every push, against a 1,196-file, 6.5 MB corpus:
- Zero failed files and zero unparsable files.
- Two format passes produce identical output.
- Byte-identical to
dotnet format whitespacewith reflow off, and 100% with reflow on; 99.9% with reflow on andcsharp_keep_existing_linebreaks = true. - A native-AOT publish on all five supported platforms, each smoke-tested before packing.
- An allocation-ratio ceiling, measured on the AOT binary rather than the JIT build.
Curb reports the file, leaves it untouched, and exits non-zero. In a build, that is
CURB0002 — an error regardless of your warning settings, because a formatter that could not verify its
own work has verified nothing.
If you hit one, it is a bug worth reporting: it means Curb found a construct it would have mangled, and stopped itself.