What style translates to
Every flag molto fmt and molto lint take, the exact command each one runs, and the clang-format and clang-tidy configuration Molto writes from format.json and linter.json.
Formatting and linting is the argument: Molto owns one vocabulary and renders it as
the backend’s own. This page is the receipt — every flag, every key, and the exact bytes that end
up in .bin/style/.
molto fmt
Writing is the default. --check and --diff are two ways of asking what it would do without
doing it, and passing both is a usage error: two answers to one question.
| Flag | Short | What it does |
|---|---|---|
--check |
-c |
Report what would change and write nothing; exits 1 if any |
--diff |
-d |
Print the unified diff to stdout and write nothing; exits 1 if any |
--refresh-tools |
Ask pickup again which formatter this machine has | |
--refresh-analysis |
Format every file again instead of skipping what did not change | |
--jobs <n> |
-j |
Format at most n files at once; every core by default |
There is no --profile and no --format. A profile decides which defines are in force, and a
formatter never sees a define; the output shape is a diff or a count, and neither is negotiable.
molto lint
| Flag | Short | What it does |
|---|---|---|
--profile <name> |
-p |
debug (default), release, bench, custom |
--format <fmt> |
-f |
rich, text, json. No default — see below |
--refresh-toolchain |
Resolve the compiler again instead of reusing the cached one | |
--refresh-tools |
Ask pickup again which linter this machine has | |
--refresh-analysis |
Analyse every file again instead of replaying what did not change | |
--jobs <n> |
-j |
Analyse at most n files at once; every core by default |
-j takes a count between 1 and 1024. Anything else — zero, a negative, a word, a number with a
suffix — is refused with the value quoted back, and exits 4. A build told to take two workers and
silently taking the whole machine is worse than one that stops.
The parser accepts --opt value, --opt=value, -o value and bare boolean flags, and --help is
always there.
What each command reads
| Command | Directories | Extensions |
|---|---|---|
fmt |
src/, include/ |
.c .cpp .cc .h .hpp .hh |
lint |
src/ |
.c .cpp .cc |
fmt includes headers because a header is not a translation unit but it is code. lint does not
analyse headers on their own — a header is analysed as part of every source that includes it, and
the compiler pass records which ones it read so a change to any of them re-analyses the source.
Both then subtract what exclude matches. A directory that is not there is not a failure: a
project with no src/ has nothing to do and exits 0.
Streams and exit codes
| Command | stdout | stderr |
|---|---|---|
molto fmt |
Whatever the formatter said | N files formatted |
molto fmt --check |
Whatever the formatter said | N files would change |
molto fmt --diff |
The unified diff | N files would change |
molto lint |
The diagnostics | Checking (debug), 2 errors, 7 warnings |
molto lint -f json |
The JSON document | Nothing |
So molto fmt --diff > patch yields a patch and nothing else, and molto lint --format json
leaves stdout parseable — the header and the tally are suppressed rather than moved.
| Code | When |
|---|---|
| 0 | Nothing would change; no error-severity diagnostic |
| 1 | fmt --check/--diff found a file; lint produced an error; a tool would not run |
| 2 | format.json or linter.json is invalid; no Project.toml above here |
| 3 | fmt found no formatter on this machine; lint found no toolchain for [target] |
| 4 | Bad usage: --check with --diff, an unknown profile, an unknown --format, a bad -j |
A warning is reported and still succeeds. Only fmt exits 3 for a missing tool — a missing
linter is not an error, because the compiler pass is what molto lint promises with nothing
installed.
--format has no default on purpose
Absent is not an implicit text. Absent means “whatever suits the stream”: rich at a terminal,
text through a pipe. NO_COLOR drops the colour from rich but not the frames. Write the flag
down when the output has to be the same wherever it goes.
What actually runs
molto fmt, once per file:
clang-format --style=file:.bin/style/clang-format.yaml -i src/foo.c # write
clang-format --style=file:.bin/style/clang-format.yaml --dry-run --Werror src/foo.c # --check
clang-format --style=file:.bin/style/clang-format.yaml src/foo.c # --diff
--Werror is what makes --dry-run exit non-zero exactly when the file would change. -i exits
zero either way, so a write reads the file back afterwards to know whether it changed at all. In
diff mode the formatter’s stdout is the formatted file, so nothing else may be mixed into it and
Molto computes the diff itself, with three lines of context.
molto lint, twice per source:
gcc -fsyntax-only -fdiagnostics-color=never src/foo.c \
-MMD -MF .bin/lint/src/foo.c.d \
-Wall -Wextra -Wpedantic <std, defines, includes and flags of [target] and the profile> \
-I<root>/src
clang-tidy --config-file=.bin/style/clang-tidy.yaml --quiet src/foo.c -- \
<the same compile arguments> -I<root>/src
Three things are deliberate there. -O and -g are not passed: a syntax-only pass stops
before optimisation, so -O2 would advertise -Wmaybe-uninitialized and friends without being
able to deliver them. The preset’s warnings go first, because the last flag wins in gcc and
clang, and a preset -Wall appended after your -Wno-unused would re-enable what you turned off.
And the defines do go through, because #ifdef decides what even compiles: linting with the
wrong profile analyses code the build never sees. Both passes run under the project’s [env].
format.json, key by key
Ten lines are written every run, whether or not the file exists:
# Generated by molto from format.json. Do not edit.
BasedOnStyle: LLVM
Standard: c++03
IndentWidth: 4
ColumnLimit: 100
UseTab: Never
BreakBeforeBraces: Attach
PointerAlignment: Right
SortIncludes: CaseSensitive
SpaceBeforeParens: Never
ReflowComments: true
| Key | clang-format option | false / left |
true / right |
|---|---|---|---|
indent_width |
IndentWidth |
1–512 | 1–512 |
line_width |
ColumnLimit |
1–512 | 1–512 |
use_tabs |
UseTab |
Never |
ForIndentation |
pointer_alignment |
PointerAlignment |
Left |
Right |
sort_includes |
SortIncludes |
Never |
CaseSensitive |
space_before_paren |
SpaceBeforeParens |
Never |
ControlStatements |
column_limit_comments |
ReflowComments |
false |
true |
brace_style |
BreakBeforeBraces |
|---|---|
attach |
Attach |
break |
Stroustrup |
linux |
Linux |
allman |
Allman |
ForIndentation rather than Always: the canonical key promises to indent with tabs, and Always
would put them inside alignment too. ReflowComments is emitted as a boolean and not as the
Always/Never enum that clang-format 20 introduced, because clang-format 19 refuses the whole
file over the enum — and a rejected configuration leaves molto fmt reporting that it formatted
nothing rather than that it could not.
Standard comes from the manifest
There is no key for the language. .h carries none in its extension and clang-format has no C mode
at all — it parses everything as C++ and Standard only picks the dialect.
[target].cpp_std |
Standard |
|---|---|
| Absent — a C project | c++03 |
c++03, gnu++03 |
c++03 |
c++11, gnu++11 |
c++11 |
c++14, gnu++14 |
c++14 |
c++17, gnu++17 |
c++17 |
c++20, gnu++20, c++2a, gnu++2a |
c++20 |
| Anything else | Latest |
c++03 is the point of the exercise for C: it is the only dialect in which requires, concept
and co_await are ordinary identifiers, which is what they are in C.
Everything Molto has no word for
BasedOnStyle: LLVM is the base, and the ten keys are layered on top of it. That is most of
clang-format: LLVM decides your continuation indent, your alignment, your short-function rule.
Seven keys is what RFC-0005 opened with, not the ceiling.
preset in format.json changes nothing today. It is read and validated — molto and none
are accepted, kernel and gnu are refused by name — but the base is BasedOnStyle: LLVM either
way. In linter.json it has teeth.
linter.json, rule by rule
# Generated by molto from linter.json. Do not edit.
Checks: '-*,clang-diagnostic-*,bugprone-*,readability-magic-numbers,-modernize-*'
WarningsAsErrors: 'bugprone-*'
The list is composed in one order, always:
-*first. clang-tidy composes what it is given on top of its own default, which isclang-analyzer-*. Clearing it is what makes the generated file say exactly whatlinter.jsonsays — and keeps a different clang-tidy, whose default is its own to change, from enabling a different set on the next machine.clang-diagnostic-*,bugprone-*whenpresetismolto, which is the default.noneappends nothing.- Each rule, in the order you wrote it.
warnanderrorappend the rule’s checks, skipping one the preset already enabled.erroralso appends them toWarningsAsErrors.offappends each positive check negated and drops the ones that were already negative — negating the list as a whole would only negate its first element.
| Rule | clang-tidy checks |
|---|---|
bugprone |
bugprone-* |
performance |
performance-* |
portability |
portability-* |
modernize |
modernize-* |
readability |
readability-* |
dataflow |
clang-analyzer-core.*, clang-analyzer-unix.* minus unix.Stream, clang-analyzer-valist.*, clang-analyzer-deadcode.* |
security |
clang-analyzer-security.* minus security.insecureAPI.DeprecatedOrUnsafeBufferHandling |
naming_snake_case |
readability-identifier-naming |
readability_magic_numbers |
readability-magic-numbers |
identifier_length |
readability-identifier-length |
swappable_parameters |
bugprone-easily-swappable-parameters |
spurious_wakeup |
bugprone-spuriously-wake-up-functions |
unused |
clang-diagnostic-unused* |
shadow |
clang-diagnostic-shadow |
uninitialized |
clang-diagnostic-uninitialized |
implicit_conversion |
clang-diagnostic-conversion |
sign_compare |
clang-diagnostic-sign-compare |
The analyzer separates its families with a dot, not the hyphen the rest of clang-tidy uses:
clang-analyzer-security-* matches nothing at all. unix.Stream is subtracted because it reads
while((n = fread(...)) > 0) as a read past the end of the file, and the Annex K check is
subtracted because it asks for memcpy_s and snprintf_s, which are optional in C11 and absent
from glibc — it would fire on every call to the C library and bury the rest.
The two named individually — swappable_parameters and spurious_wakeup — are bugprone checks
that describe C badly. The first flags any two adjacent parameters of one type, which is how most C
functions are written; the second cannot see a retry loop one function above the wait. A project
should be able to refuse them without giving up the ninety-odd checks around them.
preset in linter.json also arms the compiler
It is the only key that reaches the compiler pass. molto adds -Wall -Wextra -Wpedantic to it;
none leaves that pass with your [target].flags and nothing else. Setting "preset": "none"
therefore turns off both halves at once, which is usually not what someone reaching for it wants.
The severity map does not silence the compiler
rules is translated into clang-tidy’s configuration, and nowhere else. "unused": "off" stops
clang-tidy from repeating the warning; it does not stop gcc from raising -Wunused-variable under
the preset’s -Wall. To silence the compiler, put -Wno-unused in [target].flags of
Project.toml — where the build will see it too, which is the point.
Shared keys, and their limits
| Key | Shape | Limit |
|---|---|---|
backend |
"clang-format@22.1.8" |
64 characters; name alone pins no version |
preset |
"molto" or "none" |
kernel and gnu are refused by name |
exclude |
List of globs | 16 patterns, 128 characters each |
rules |
Rule to off/warn/error |
32 rules, names of 64 characters |
style |
The eight keys above | Widths between 1 and 512 |
A backend pin is looked for inside the version pickup reports, because that is a sentence
(clang-format version 22.1.8) and not a number. Molto verifies the pin; it never installs — that
is pickup’s half of the split.
exclude patterns are POSIX fnmatch without FNM_PATHNAME, matched against the path relative to
the project root. A star crosses a slash, so vendor/* matches vendor/a/b.c. A pattern ending in
/** is tried a second time with that suffix removed, so it matches the directory itself too.
The generated configuration is composed into a 4096-byte buffer. A linter.json that overflows it
is an error — the translated check list is too long — not a file quietly cut in half.
Every error, and what it means
All of them exit 2, and all of them name what is wrong. Nothing in either file is ignored.
| Message | What happened |
|---|---|
format.json: is not valid JSON |
A trailing comma, a missing brace |
format.json: must hold an object |
The document is a list or a scalar |
format.json: unknown key 'styl' |
A typo, at the top level or in style |
format.json: unknown value for 'attached' |
Not one of the four brace styles, or the two alignments |
format.json: expected a string for 'backend' |
A number or a list where text belongs |
format.json: expected true or false for 'use_tabs' |
"yes" is not a boolean |
format.json: expected a column count for 'line_width' |
Not a number, or outside 1–512 |
format.json: expected a list for 'exclude' |
A single string is still not a list |
format.json: too many exclude patterns |
More than 16 |
linter.json: too many rules |
More than 32 |
linter.json: severity must be "off", "warn" or "error", not 'warning' |
Close, but not one of the three |
linter.json: rule 'x' is not supported by clang-tidy (no equivalent check) |
Not a name from the rule table |
format.json: preset is not implemented yet; use "molto" or "none": kernel |
kernel or gnu |
format.json: backend 'uncrustify' is not supported by molto (only clang-format) |
A second backend is not written yet |
format.json: backend is pinned to '…' but this machine has '…' |
The installed version differs |
What --format json produces
[
{
"file": "src/foo.c",
"line": 12,
"column": 5,
"severity": "warning",
"message": "…",
"rule": "unused_variable"
}
]
severity is note, warning or error. Paths are relative to the project root. Every entry
carries all six fields; an empty rule is "", never absent.
The caret lines are not in it. A compiler interleaves source snippets and In file included from headers, which carry no file and no line of their own. They are kept in the text form, where
they are read next to the line they point at, and dropped from JSON, where nothing could act on
them.
rule is Molto’s spelling: cut at the comma clang-tidy appends, with a -W or -Werror= prefix
dropped and hyphens turned into underscores. -Wunused-variable reads as unused_variable and
bugprone-assignment-in-if-condition as bugprone_assignment_in_if_condition. To act on one —
NOLINT, -Wno-… — turn the underscores back into hyphens.
A pass that says more than 65536 bytes about one file is cut off there, and the cut is reported as
a note on that file. It is never silent.
Where the state lives
| Path | What is in it |
|---|---|
.bin/style/clang-format.yaml |
The translated format.json, rewritten every fmt |
.bin/style/clang-tidy.yaml |
The translated linter.json, rewritten every lint |
.bin/lint/<source>.d |
Which headers each source read, written by -MMD |
.bin/wsdb |
The recorded results, and the formatter and linter pickup resolved |
molto clean --all removes all of it. Neither command writes compile_commands.json — a build
does — so an external clang-tidy reading that database is reading what molto build last left
there.
What invalidates a recorded result
| Command | Fingerprint | Watched files |
|---|---|---|
fmt |
Formatter version + the generated clang-format.yaml |
The file itself |
lint |
Compiler version + the whole compiler command line + linter version + the generated clang-tidy.yaml + the whole linter command line + [env] |
The source and every header its depfile named |
The mode is not part of the fmt fingerprint: what is recorded is that a file is in its final form
under this style, and that is the same fact --check asks about, --diff asks about and a write
has just made true. One entry serves all three.
Nothing is recorded for a run that was killed by a signal, exited 127, or had its output cut off: a
failure should be retried, not replayed. lint additionally refuses to record when the source
changed while it was being analysed, which would otherwise pin stale diagnostics under a current
signature and never invalidate them.
Environment
| Variable | Effect |
|---|---|
MOLTO_CLANG_FORMAT |
Use this formatter; skips pickup and is not cached |
MOLTO_CLANG_TIDY |
Use this linter; same |
MOLTO_PICKUP |
Where pickup is, when it is not on the PATH |
NO_COLOR |
Drops the colour from --format rich |
An overridden tool reports no version, so the recorded results cannot notice that the binary
changed underneath them. After swapping one, run with --refresh-analysis once.