ADRIANOLAUREANO← Artigos
SÉRIE CRIVO · 03/03RUSTAUTOFIX

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.

ARTIGO 23 · CRIVO 1.0 · DA SUGESTÃO À CORREÇÃO

3modos de correção

3formatos de saída

1cache por conteúdo

CRIVO · O CÓDIGO VISTO POR DENTRO
FONTE PULSOse ativo == verdadeiro {
 mostre(total)
}
CRIVO 1.0estrutura · relações · intenção
DIAGNÓSTICOcondição redundanteativo == verdadeiro
──────┬──────────
simplifique para ativo

VOCABULÁRIO VISUAL

As peças que entram em cena neste capítulo.

01Ediçãointervalo substituído por outro texto
02Offsetposição numérica dentro do arquivo
03Fix seguromudança que preserva o comportamento
04Conflitoduas edições disputando bytes
05Cacheresultado reutilizado para conteúdo idêntico
06CIverificação automática antes da entrega
Os nomes técnicos aparecem depois do problema que resolvem — use este mapa para consultar, não para decorar.
00
O Crivo 1.0 preserva o analisador inteiro.
Autofix não substitui análise

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
      └─ aplicabilidade

Um 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.

00
A correção depende do operador e do literal.
Quatro formas, duas transformações
OriginalCorreção
x == verdadeirox
x != falsox
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.

00
Fix funciona como uma pequena transação.
Planejar, validar, aplicar e verificar
  1. O Crivo analisa o conteúdo original e coleta diagnósticos.
  2. Seleciona apenas edições marcadas como seguras.
  3. Ordena intervalos e recusa sobreposição.
  4. Valida limites e fronteiras UTF-8.
  5. Aplica do maior offset para o menor.
  6. Analisa o resultado novamente antes de gravar.
  7. 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.

00
Os comandos do artigo são os comandos do download.
Sem presumir instalação global
$ 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.pulso

check 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.

00
“Funciona” precisa de critérios observáveis.
A suíte do próprio Crivo
TesteO que prova
lexer UTF-8spans recortam texto válido
parserprogramas-fixture viram a AST esperada
snapshotsmensagens e posições não mudam por acidente
regressão 0.1/0.2regras anteriores continuam presentes
não executachamadas Pulso permanecem apenas como nós
fix idempotenteexecutar duas vezes produz o mesmo arquivo
conflitoedições sobrepostas são recusadas
CLIstdout, 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.

01
Diagnosticar, sugerir e corrigir são promessas diferentes.
O usuário precisa saber o risco
ResultadoO Crivo prometeExemplo
Diagnósticoexplicar uma evidênciaparâmetro nunca usado
Sugestãomostrar uma possível mudançareorganizar retorno complexo
Correção segurapreservar o comportamento dentro do modelox == verdadeirox

“Possivelmente insegura” exige uma opção explícita. “Informativa” nunca modifica arquivo. Essa classificação pertence à regra, não à interface.

02
Corrigir a AST não corrige o arquivo.
Comentários e formatação vivem no texto original
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 */ |
O span menor remove apenas a redundância; o resto permanece byte por byte.
03
Aplique do fim para o começo.
Como não invalidar offsets pendentes

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.

04
Duas correções podem estar certas e, juntas, serem impossíveis.
Sobreposição e conflito
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 conflito
O Crivo nunca escolhe silenciosamente entre edições concorrentes.

Ordenamos 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.

05
Uma ferramenta para humanos e para máquinas.
check, fix e formatos de saída
$ 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.

06
Política não deve morar no código da regra.
Configuração, severidades e exclusões
# crivo.toml
[rules]
unused-variable = "warn"
shadowed-variable = "deny"
no-effect-expression = "off"

[analysis]
exclude = ["vendor/**", "gerado/**"]
max-warnings = 0

O 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.

07
Arquivo igual não precisa ser analisado outra vez.
Cache baseado no conteúdo

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.

08
O mesmo núcleo cabe no editor, no Git e na CI.
Integração sem três analisadores diferentes
EDITOR ── documento aberto ─┐
                            ▼
GIT HOOK ─ arquivos staged → CRIVO CORE → diagnósticos + edits
                            ▲
CI ─── checkout completo ──┘

CLI texto  ·  CLI JSON  ·  adaptador LSP
A integração muda a entrada e a apresentação; regras, símbolos e fluxo permanecem iguais.

Um 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.

09
Do texto digitado à alteração segura.
O mapa final do Crivo 1.0
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
A gravação usa arquivo temporário no mesmo diretório e renomeação, evitando deixar metade do conteúdo em caso de falha.

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.

10
O Crivo não executa o programa. Ele constrói evidências.
O fechamento da série

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.

A resposta final

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.

Baixar Crivo 1.0 (.zip)Rust · Pulso · análise estática · testes

O que este artigo fez você pensar?

Dúvidas, experiências e contrapontos ajudam a próxima pessoa a enxergar o assunto por outro ângulo.

Todos passam por moderação. Ao enviar, você concorda com a política de privacidade.

Receba os próximos artigos.

Uma mensagem quando uma nova investigação estiver pronta. Só isso.