En qué se traduce el estilo
Cada flag de molto fmt y molto lint, el comando exacto que ejecuta cada uno, y la configuración de clang-format y clang-tidy que Molto escribe a partir de format.json y linter.json.
Formato y análisis es el argumento: Molto es dueño de un vocabulario y lo
renderiza como el del backend. Esta página es el recibo — cada flag, cada clave y los bytes exactos
que acaban en .bin/style/.
molto fmt
Escribir es lo que hace por defecto. --check y --diff son dos maneras de preguntar qué haría sin
hacerlo, y pasar las dos es un error de uso: dos respuestas a una sola pregunta.
| Flag | Corto | Qué hace |
|---|---|---|
--check |
-c |
Dice qué cambiaría y no escribe nada; sale con 1 si hay algo |
--diff |
-d |
Imprime el diff unificado en stdout y no escribe nada; sale con 1 si hay algo |
--refresh-tools |
Vuelve a preguntarle a pickup qué formateador tiene esta máquina | |
--refresh-analysis |
Formatea todos los archivos otra vez en vez de saltarse los que no cambiaron | |
--jobs <n> |
-j |
Formatea como mucho n archivos a la vez; por defecto, todos los núcleos |
No hay --profile ni --format. Un perfil decide qué defines están en vigor, y un formateador
nunca ve un define; la salida es un diff o una cuenta, y ninguna de las dos se negocia.
molto lint
| Flag | Corto | Qué hace |
|---|---|---|
--profile <nombre> |
-p |
debug (por defecto), release, bench, custom |
--format <fmt> |
-f |
rich, text, json. Sin valor por defecto — mira abajo |
--refresh-toolchain |
Resuelve el compilador otra vez en vez de reutilizar el cacheado | |
--refresh-tools |
Vuelve a preguntarle a pickup qué linter tiene esta máquina | |
--refresh-analysis |
Analiza todos los archivos otra vez en vez de reproducir lo que no cambió | |
--jobs <n> |
-j |
Analiza como mucho n archivos a la vez; por defecto, todos los núcleos |
-j acepta un número entre 1 y 1024. Cualquier otra cosa —cero, un negativo, una palabra, un número
con sufijo— se rechaza citando el valor, y sale con 4. Un build al que le pides dos workers y se
queda con la máquina entera en silencio es peor que uno que se para.
El parser acepta --opt valor, --opt=valor, -o valor y flags booleanos sueltos, y --help
siempre está.
Qué lee cada comando
| Comando | Directorios | Extensiones |
|---|---|---|
fmt |
src/, include/ |
.c .cpp .cc .h .hpp .hh |
lint |
src/ |
.c .cpp .cc |
fmt incluye las cabeceras porque una cabecera no es una unidad de traducción, pero sí es código.
lint no analiza cabeceras por su cuenta: una cabecera se analiza como parte de cada fuente que la
incluye, y la pasada del compilador registra cuáles leyó, así que cambiar cualquiera de ellas
reanaliza la fuente.
Después los dos restan lo que casa con exclude. Un directorio que no está no es un fallo: un
proyecto sin src/ no tiene nada que hacer y sale con 0.
Streams y códigos de salida
| Comando | stdout | stderr |
|---|---|---|
molto fmt |
Lo que dijera el formateador | N files formatted |
molto fmt --check |
Lo que dijera el formateador | N files would change |
molto fmt --diff |
El diff unificado | N files would change |
molto lint |
Los diagnósticos | Checking (debug), 2 errors, 7 warnings |
molto lint -f json |
El documento JSON | Nada |
Así que molto fmt --diff > parche produce un parche y nada más, y molto lint --format json deja
stdout parseable: la cabecera y el recuento se suprimen, no se mueven a otro sitio.
| Código | Cuándo |
|---|---|
| 0 | Nada cambiaría; ningún diagnóstico de severidad error |
| 1 | fmt --check/--diff encontró un archivo; lint produjo un error; una herramienta no arrancó |
| 2 | format.json o linter.json inválidos; no hay Project.toml por encima |
| 3 | fmt no encontró formateador en esta máquina; lint no encontró toolchain para [target] |
| 4 | Mal uso: --check con --diff, un perfil desconocido, un --format desconocido, un -j inválido |
Un warning se reporta y aun así tiene éxito. Sólo fmt sale con 3 por una herramienta que
falta: que no haya linter no es un error, porque la pasada del compilador es lo que molto lint
promete sin instalar nada.
--format no tiene valor por defecto a propósito
Ausente no es un text implícito. Ausente significa «lo que le convenga al flujo»: rich en una
terminal, text a través de una tubería. NO_COLOR le quita el color a rich, pero no los marcos.
Escribe el flag cuando la salida tenga que ser la misma vaya donde vaya.
Qué se ejecuta de verdad
molto fmt, una vez por archivo:
clang-format --style=file:.bin/style/clang-format.yaml -i src/foo.c # escribir
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 es lo que hace que --dry-run salga distinto de cero exactamente cuando el archivo
cambiaría. -i sale con cero pase lo que pase, así que al escribir se vuelve a leer el archivo
después para saber si cambió. En modo diff el stdout del formateador es el archivo formateado, así
que no puede mezclarse nada más ahí y el diff lo calcula Molto, con tres líneas de contexto.
molto lint, dos veces por fuente:
gcc -fsyntax-only -fdiagnostics-color=never src/foo.c \
-MMD -MF .bin/lint/src/foo.c.d \
-Wall -Wextra -Wpedantic <std, defines, includes y flags de [target] y del perfil> \
-I<raíz>/src
clang-tidy --config-file=.bin/style/clang-tidy.yaml --quiet src/foo.c -- \
<los mismos argumentos de compilación> -I<raíz>/src
Ahí hay tres decisiones deliberadas. -O y -g no se pasan: una pasada de sólo sintaxis se para
antes de optimizar, así que -O2 anunciaría -Wmaybe-uninitialized y compañía sin poder
entregarlos. Los warnings del preset van primero, porque en gcc y clang gana el último flag, y
un -Wall del preset añadido después de tu -Wno-unused reactivaría justo lo que apagaste. Y los
defines sí pasan, porque un #ifdef decide qué llega siquiera a compilarse: analizar con el perfil
equivocado revisa código que el build nunca ve. Las dos pasadas corren bajo el [env] del proyecto.
format.json, clave por clave
Se escriben diez líneas en cada ejecución, exista el archivo o no:
# 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
| Clave | Opción de clang-format | 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 en vez de Always: la clave canónica promete indentar con tabuladores, y Always
los metería también dentro de la alineación. ReflowComments se emite como booleano y no como el
enum Always/Never que introdujo clang-format 20, porque clang-format 19 rechaza el archivo
entero por ese enum — y una configuración rechazada deja a molto fmt diciendo que no formateó
nada, en vez de que no pudo.
Standard sale del manifiesto
No hay clave para el lenguaje. Un .h no lo lleva en la extensión y clang-format no tiene modo C:
lo parsea todo como C++ y Standard sólo elige el dialecto.
[target].cpp_std |
Standard |
|---|---|
| Ausente, un proyecto en C | 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 |
| Cualquier otra cosa | Latest |
c++03 es la gracia del asunto para C: es el único dialecto donde requires, concept y
co_await son identificadores normales, que es lo que son en C.
Todo aquello para lo que Molto no tiene palabra
BasedOnStyle: LLVM es la base, y las diez claves se apilan encima. Eso es la mayor parte de
clang-format: LLVM decide tu indentación de continuación, tu alineación, tu regla de funciones
cortas. Siete claves es con lo que abrió la RFC-0005, no el techo.
preset en format.json hoy no cambia nada. Se lee y se valida —molto y none se aceptan,
kernel y gnu se rechazan por su nombre— pero la base es BasedOnStyle: LLVM en cualquier caso.
Donde sí tiene dientes es en linter.json.
linter.json, regla por regla
# Generated by molto from linter.json. Do not edit.
Checks: '-*,clang-diagnostic-*,bugprone-*,readability-magic-numbers,-modernize-*'
WarningsAsErrors: 'bugprone-*'
La lista se compone siempre en el mismo orden:
-*primero. clang-tidy compone lo que se le da encima de su propio valor por defecto, que esclang-analyzer-*. Limpiarlo es lo que hace que el archivo generado diga exactamente lo que dicelinter.json— y lo que impide que otro clang-tidy, cuyo valor por defecto es suyo y puede cambiar, active un conjunto distinto en la máquina siguiente.clang-diagnostic-*,bugprone-*cuandopresetesmolto, que es lo de por defecto.noneno añade nada.- Cada regla, en el orden en que la escribiste.
warnyerrorañaden los checks de la regla, saltándose los que el preset ya activó.errorlos añade además aWarningsAsErrors.offañade cada check positivo negado y descarta los que ya venían negados: negar la lista entera sólo negaría su primer elemento.
| Regla | Checks de clang-tidy |
|---|---|
bugprone |
bugprone-* |
performance |
performance-* |
portability |
portability-* |
modernize |
modernize-* |
readability |
readability-* |
dataflow |
clang-analyzer-core.*, clang-analyzer-unix.* menos unix.Stream, clang-analyzer-valist.*, clang-analyzer-deadcode.* |
security |
clang-analyzer-security.* menos 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 |
El analizador separa sus familias con un punto, no con el guion que usa el resto de clang-tidy:
clang-analyzer-security-* no casa con absolutamente nada. unix.Stream se resta porque lee
while((n = fread(...)) > 0) como una lectura más allá del final del archivo, y el check del Anexo K
se resta porque pide memcpy_s y snprintf_s, que son opcionales en C11 y no están en glibc:
saltaría en cada llamada a la librería estándar y enterraría todo lo demás.
Las dos que tienen nombre propio —swappable_parameters y spurious_wakeup— son checks de
bugprone que describen C mal. El primero señala dos parámetros adyacentes del mismo tipo, que es
como está escrita la mayoría de funciones en C; el segundo no ve un bucle de reintento que está una
función por encima de la espera. Un proyecto debería poder rechazarlas sin renunciar a los noventa y
pico checks que las rodean.
preset en linter.json arma también al compilador
Es la única clave que llega a la pasada del compilador. molto le añade -Wall -Wextra -Wpedantic;
none deja esa pasada con tus [target].flags y nada más. Poner "preset": "none" apaga por tanto
las dos mitades a la vez, que no suele ser lo que quiere quien lo escribe.
El mapa de severidades no silencia al compilador
rules se traduce a la configuración de clang-tidy y a ningún otro sitio. "unused": "off" impide
que clang-tidy repita el aviso; no impide que gcc levante -Wunused-variable bajo el -Wall del
preset. Para callar al compilador, pon -Wno-unused en [target].flags de
Project.toml, donde el build también lo verá, que es de lo que se trata.
Claves compartidas y sus límites
| Clave | Forma | Límite |
|---|---|---|
backend |
"clang-format@22.1.8" |
64 caracteres; un nombre solo no fija versión |
preset |
"molto" o "none" |
kernel y gnu se rechazan por su nombre |
exclude |
Lista de globs | 16 patrones, de 128 caracteres cada uno |
rules |
Regla a off/warn/error |
32 reglas, con nombres de 64 caracteres |
style |
Las ocho claves de arriba | Anchos entre 1 y 512 |
Una fijación de backend se busca dentro de la versión que reporta pickup, porque eso es una
frase (clang-format version 22.1.8) y no un número. Molto verifica la fijación; nunca instala —
esa es la mitad de pickup.
Los patrones de exclude son fnmatch de POSIX sin FNM_PATHNAME, contrastados con la ruta
relativa a la raíz del proyecto. Un asterisco cruza barras, así que vendor/* casa con
vendor/a/b.c. Un patrón que acaba en /** se prueba una segunda vez sin ese sufijo, así que casa
también con el propio directorio.
La configuración generada se compone en un búfer de 4096 bytes. Un linter.json que lo desborde es
un error —the translated check list is too long— no un archivo cortado por la mitad en silencio.
Cada error, y qué significa
Todos salen con 2 y todos nombran qué está mal. Nada de ninguno de los dos archivos se ignora.
| Mensaje | Qué pasó |
|---|---|
format.json: is not valid JSON |
Una coma de más, una llave que falta |
format.json: must hold an object |
El documento es una lista o un escalar |
format.json: unknown key 'styl' |
Una errata, arriba o dentro de style |
format.json: unknown value for 'attached' |
No es uno de los cuatro estilos de llave, ni de las dos alineaciones |
format.json: expected a string for 'backend' |
Un número o una lista donde va texto |
format.json: expected true or false for 'use_tabs' |
"yes" no es un booleano |
format.json: expected a column count for 'line_width' |
No es un número, o queda fuera de 1–512 |
format.json: expected a list for 'exclude' |
Una sola cadena sigue sin ser una lista |
format.json: too many exclude patterns |
Más de 16 |
linter.json: too many rules |
Más de 32 |
linter.json: severity must be "off", "warn" or "error", not 'warning' |
Cerca, pero no es ninguna de las tres |
linter.json: rule 'x' is not supported by clang-tidy (no equivalent check) |
No es un nombre de la tabla de reglas |
format.json: preset is not implemented yet; use "molto" or "none": kernel |
kernel o gnu |
format.json: backend 'uncrustify' is not supported by molto (only clang-format) |
Todavía no hay un segundo backend escrito |
format.json: backend is pinned to '…' but this machine has '…' |
La versión instalada no coincide |
Qué produce --format json
[
{
"file": "src/foo.c",
"line": 12,
"column": 5,
"severity": "warning",
"message": "…",
"rule": "unused_variable"
}
]
severity es note, warning o error. Las rutas son relativas a la raíz del proyecto. Cada
entrada lleva los seis campos; un rule vacío es "", nunca ausente.
Las líneas del cursor no están ahí. Un compilador intercala fragmentos de código y cabeceras
tipo In file included from, que no llevan archivo ni línea propios. Se conservan en la forma de
texto, donde se leen junto a la línea que señalan, y se descartan del JSON, donde nada podría actuar
sobre ellas.
rule está en la ortografía de Molto: cortado en la coma que añade clang-tidy, sin el prefijo -W
ni -Werror= y con los guiones convertidos en guiones bajos. -Wunused-variable se lee
unused_variable y bugprone-assignment-in-if-condition se lee
bugprone_assignment_in_if_condition. Para actuar sobre una —NOLINT, -Wno-…— vuelve a convertir
los guiones bajos en guiones.
Una pasada que diga más de 65536 bytes sobre un mismo archivo se corta ahí, y el corte se reporta
como un note en ese archivo. Nunca es silencioso.
Dónde vive el estado
| Ruta | Qué contiene |
|---|---|
.bin/style/clang-format.yaml |
El format.json traducido, reescrito en cada fmt |
.bin/style/clang-tidy.yaml |
El linter.json traducido, reescrito en cada lint |
.bin/lint/<fuente>.d |
Qué cabeceras leyó cada fuente, escrito por -MMD |
.bin/wsdb |
Los resultados registrados, y el formateador y el linter que resolvió pickup |
molto clean --all borra todo eso. Ninguno de los dos comandos escribe compile_commands.json —eso
lo hace un build—, así que un clang-tidy externo que lea esa base de datos está leyendo lo que
dejó el último molto build.
Qué invalida un resultado registrado
| Comando | Huella | Archivos vigilados |
|---|---|---|
fmt |
Versión del formateador + el clang-format.yaml generado |
El propio archivo |
lint |
Versión del compilador + la línea de comando entera del compilador + versión del linter + el clang-tidy.yaml generado + la línea de comando entera del linter + [env] |
La fuente y cada cabecera que nombró su depfile |
El modo no forma parte de la huella de fmt: lo que se registra es que un archivo está en su forma
final bajo este estilo, y ese es el mismo hecho por el que pregunta --check, por el que pregunta
--diff y que una escritura acaba de hacer cierto. Una entrada sirve para los tres.
No se registra nada de una ejecución que murió por una señal, salió con 127 o vio su salida cortada:
un fallo hay que reintentarlo, no reproducirlo. lint además se niega a registrar cuando la fuente
cambió mientras se la analizaba, algo que si no fijaría diagnósticos rancios bajo una firma actual
que nunca los invalidaría.
Entorno
| Variable | Efecto |
|---|---|
MOLTO_CLANG_FORMAT |
Usa este formateador; se salta pickup y no se cachea |
MOLTO_CLANG_TIDY |
Usa este linter; igual |
MOLTO_PICKUP |
Dónde está pickup cuando no está en el PATH |
NO_COLOR |
Le quita el color a --format rich |
Una herramienta forzada por entorno no reporta versión, así que los resultados registrados no pueden
notar que el binario cambió debajo de ellos. Después de cambiar una, ejecuta una vez con
--refresh-analysis.