Como um linter corrige código sem quebrá-lo?
Apontar ativo == verdadeiro é fácil. Reescrever o arquivo, preservar comentários e combinar dezenas de sugestões com segurança é outra engenharia.
se ativo == verdadeiro {
mostre(total)
}ativo == verdadeiro
──────┬──────────
simplifique para ativoVOCABULÁRIO VISUAL
As peças que entram em cena neste capítulo.
O terceiro pacote continua tokenizando, construindo AST, resolvendo nomes, acompanhando escopos e produzindo todas as regras do 0.2. Agora alguns diagnósticos podem carregar uma edição textual. Essa separação evita o erro de reduzir a versão final a uma busca por == verdadeiro.
Diagnostic
├─ regra, mensagem e severidade
├─ span principal
├─ notas e spans relacionados
└─ edit opcional
├─ intervalo em bytes
├─ substituição
└─ aplicabilidadeUm diagnóstico sem correção continua valioso. Código inalcançável pode envolver intenção humana; o Crivo explica e não apaga. Comparação booleana redundante possui uma transformação pequena e comprovável no modelo da Pulso.
| Original | Correção |
|---|---|
x == verdadeiro | x |
x != falso | x |
x == falso | !(x) |
x != verdadeiro | !(x) |
Os parênteses da negação preservam a precedência quando x é uma expressão composta. O span vem da AST; não procuramos a primeira ocorrência textual, que poderia estar num comentário ou numa string.
- O Crivo analisa o conteúdo original e coleta diagnósticos.
- Seleciona apenas edições marcadas como seguras.
- Ordena intervalos e recusa sobreposição.
- Valida limites e fronteiras UTF-8.
- Aplica do maior offset para o menor.
- Analisa o resultado novamente antes de gravar.
- Escreve em arquivo temporário e substitui o destino.
Aplicar de trás para frente impede que uma edição no começo desloque os offsets ainda pendentes. Reanalisar garante que a correção não produziu sintaxe inválida. A troca por arquivo temporário reduz o risco de deixar conteúdo parcial após falha.
$ cargo test
$ cargo run -- check exemplos/inicio.pulso
$ cargo run -- check exemplos/inicio.pulso --format json
$ cargo run -- fix exemplos/inicio.pulso
# instalação opcional
$ cargo install --path .
$ crivo check exemplos/inicio.pulsocheck nunca escreve. fix altera apenas edições seguras. JSON contém arquivo, regra, severidade, mensagem, linha, coluna e intervalos para máquinas. Saída humana mostra a linha e um marcador visual. Ambos nascem dos mesmos diagnósticos.
| Teste | O que prova |
|---|---|
| lexer UTF-8 | spans recortam texto válido |
| parser | programas-fixture viram a AST esperada |
| snapshots | mensagens e posições não mudam por acidente |
| regressão 0.1/0.2 | regras anteriores continuam presentes |
| não executa | chamadas Pulso permanecem apenas como nós |
| fix idempotente | executar duas vezes produz o mesmo arquivo |
| conflito | edições sobrepostas são recusadas |
| CLI | stdout, stderr e exit codes seguem o contrato |
Clippy, ESLint e Ruff possuem ecossistemas, analisadores e anos de compatibilidade muito maiores. O Crivo 1.0 é funcional no subconjunto documentado da Pulso; ele não promete análise de tipos completa, macros ou módulos distribuídos.
| Resultado | O Crivo promete | Exemplo |
|---|---|---|
| Diagnóstico | explicar uma evidência | parâmetro nunca usado |
| Sugestão | mostrar uma possível mudança | reorganizar retorno complexo |
| Correção segura | preservar o comportamento dentro do modelo | x == verdadeiro → x |
“Possivelmente insegura” exige uma opção explícita. “Informativa” nunca modifica arquivo. Essa classificação pertence à regra, não à interface.
se ativo /* liberado pelo operador */ == verdadeiro {
iniciar();
}A AST pode representar apenas a comparação. Se imprimirmos a árvore novamente, o comentário e os espaços podem desaparecer. Por isso uma correção é uma edição textual:
pub struct TextEdit {
pub span: Span, // bytes [start, end)
pub replacement: String,
pub applicability: Applicability,
pub rule: RuleId,
}ANTES · bytes 0 9 38 | ativo /* comentário */ == verdadeiro | |-------- preservar -------|--- trocar ---| EDIÇÃO: [27, 40) → "" DEPOIS | ativo /* comentário */ |
Se uma edição no início encurta o texto, todas as posições posteriores mudam. Ordenar por start decrescente resolve: a primeira alteração acontece mais à direita e não desloca nenhum intervalo ainda pendente.
edits.sort_by_key(|edit| Reverse(edit.span.start));
for edit in edits {
source.replace_range(edit.span.start..edit.span.end, &edit.replacement);
}Antes disso, validamos limites UTF-8 e conferimos se o trecho esperado ainda ocupa o span. Se o arquivo mudou depois da análise, o Crivo recusa a correção e pede novo check.
fonte: se (ativo == verdadeiro) { ... }
regra A: remover "== verdadeiro" [10, 23)
regra B: remover parênteses [3, 25)
└──── intervalos se sobrepõem ────┘
política: aplicar A; manter B como sugestão e relatar conflitoOrdenamos por arquivo, início, fim e prioridade estável. Edições idênticas são deduplicadas. Inserções no mesmo offset só podem coexistir quando a regra declara uma ordem. Sobreposições restantes formam um grupo de conflito e não são aplicadas automaticamente.
$ crivo check src/
src/pedido.pulso:8:12 aviso[boolean-comparison]
1 aviso · nenhuma alteração
$ crivo fix src/
1 correção segura aplicada · 0 conflitos
$ crivo check src/ --format json
{"rule":"unused-parameter","severity":"warning",
"file":"src/pedido.pulso","start":42,"end":47}check nunca grava. fix aplica somente Safe; --allow-unsafe amplia conscientemente. O formato texto privilegia leitura; JSON preserva spans, regra, severidade, mensagens e edições para editor e CI.
# crivo.toml
[rules]
unused-variable = "warn"
shadowed-variable = "deny"
no-effect-expression = "off"
[analysis]
exclude = ["vendor/**", "gerado/**"]
max-warnings = 0O registro conhece todas as regras; a configuração decide quais instanciar e pode trocar severidade sem recompilar. Nomes inválidos geram erro com sugestão, evitando que um typo desligue proteção em silêncio.
Data de modificação é rápida, mas pode enganar. O Crivo calcula uma chave com hash do conteúdo, versão do analisador e configuração efetiva. Se qualquer parte muda, a entrada deixa de valer.
CacheKey = hash(
CRIVO_VERSION,
config.normalized(),
source.bytes(),
)O benchmark usa um projeto Pulso de 50 arquivos, aquecimento explícito e 30 repetições. Na máquina descrita no README, a mediana cai de 41 ms numa análise fria para 6 ms com 48 arquivos reaproveitados. Isso não prevê desempenho em repositórios reais; demonstra apenas onde o cache evita trabalho.
EDITOR ── documento aberto ─┐
▼
GIT HOOK ─ arquivos staged → CRIVO CORE → diagnósticos + edits
▲
CI ─── checkout completo ──┘
CLI texto · CLI JSON · adaptador LSPUm servidor LSP converteria spans do Crivo em diagnósticos e code actions. O hook executaria crivo check antes do commit. A CI falharia conforme max-warnings. Construir esses sistemas por inteiro desviaria da pergunta; o pacote inclui exemplos mínimos de configuração e o contrato JSON.
fonte Pulso → lexer + parser compartilhados → AST com spans → símbolos + escopos → CFG + análises → regras configuradas → diagnósticos + sugestões → conflitos resolvidos → edits seguros em ordem reversa → arquivo gravado atomicamente
Testes unitários cobrem spans, regras e conflitos; snapshots protegem a apresentação dos diagnósticos; testes de integração executam check, fix e JSON; propriedades aleatórias verificam que edits aceitos nunca se sobrepõem e sempre respeitam limites UTF-8.
Começamos com um sublinhado no editor. Para explicá-lo, preservamos posições na AST, separamos regras, resolvemos nomes, reconstruímos escopos, desenhamos caminhos e transformamos sugestões em edições verificáveis. O Crivo 1.0 é pequeno o bastante para caber na cabeça e completo o bastante para participar de um fluxo real.
Um linter aprende a examinar código porque transforma texto em estrutura, estrutura em relações e relações em evidências. Corrigir com segurança exige voltar ao texto com intervalos precisos, política explícita e recusa diante da dúvida.
As convenções de integração seguem a especificação oficial do LSP 3.17; o modelo de aplicabilidade foi inspirado na documentação do Clippy. Ferramentas industriais lidam com linguagens, macros, builds e ecossistemas muito maiores. O Crivo é um laboratório funcional, não uma promessa de substituição.
PROJETO COMPLETO · ESTADO EXATO DESTE CAPÍTULO
Baixe e execute o Crivo 1.0.
O pacote é independente, inclui o código-fonte em Rust, exemplos Pulso, testes e README. Ele contém somente o que foi construído até este artigo; as versões anteriores permanecem disponíveis em seus próprios endereços.