Ir al contenido
Molto

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:

  1. -* primero. clang-tidy compone lo que se le da encima de su propio valor por defecto, que es clang-analyzer-*. Limpiarlo es lo que hace que el archivo generado diga exactamente lo que dice linter.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.
  2. clang-diagnostic-*,bugprone-* cuando preset es molto, que es lo de por defecto. none no añade nada.
  3. Cada regla, en el orden en que la escribiste. warn y error añaden los checks de la regla, saltándose los que el preset ya activó. error los añade además a WarningsAsErrors. off añ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.