Dev & Code Jul 26, 2026Adicionar aos favoritos

Sob o capô de `go vet`, `staticcheck` e afins: a equipe Go mantém um *framework* oficial que permite escrever um *linter* em poucas dezenas de linhas.
Você herda um projeto Go de 200.000 linhas. A revisão de código deixa passar, há seis meses, um pattern que provoca goroutine leaks: um context.WithTimeout criado sem defer cancel(). Ninguém o vê na leitura. O go vet não o sinaliza. Adicionar uma regra no linter caseiro levaria, em outra linguagem, uma pequena semana.
Em Go, você escreve um Analyzer em uma noite. Porque a equipe da linguagem mantém há muito tempo um pacote quase discreto e, ainda assim, fundamental: golang.org/x/tools/go/analysis.
O pacote go/analysis (« Go Analysis Framework ») não é novo — está publicado e documentado em pkg.go.dev/golang.org/x/tools/go/analysis — mas volta regularmente ao Hacker News (última aparição em 2026-07-26) porque muitos desenvolvedores Go ainda o desconhecem. Só que é a infraestrutura sobre a qual gira todo o ecossistema de análise estática da linguagem: o go vet se apoia nele, o staticcheck (Dominik Honnef) o usa, o gopls (o language server oficial) conecta seus diagnósticos a ele, e a imensa maioria dos linters modernos na CI — com o golangci-lint à frente — são apenas coleções de Analyzer empilhados.
A aposta do framework é simples: um analisador = uma struct. Essa struct declara seu nome, sua documentação, suas dependências em relação a outros analisadores, e uma função Run que recebe um objeto *analysis.Pass (o código parsed, o type-checker, os arquivos, os comentários) e sinaliza seus diagnósticos via pass.Report. Só isso.
Três ideias sustentam a arquitetura:
Modularidade por composição. Um analisador pode declarar Requires: []*analysis.Analyzer{inspect.Analyzer}: o framework resolve o grafo de dependências e injeta o resultado dos analisadores pré-requisitos no Pass.ResultOf. Resultado: seu analisador de negócio não re-parse os arquivos, ele consome o resultado do inspect (que, por sua vez, faz o walk no AST tipado uma única vez para toda a cadeia).
Um único « driver », múltiplos bootstrappings. Você escreve seu Analyzer. singlechecker.Main(myAnalyzer) o transforma em um binário CLI autônomo, multichecker.Main(a1, a2, a3) o transforma em um binário multi-checks, unitchecker.Main(...) o conecta ao go vet -vettool=.... Sem glue code: o mesmo código do Analyzer roda localmente, na CI, na IDE via gopls, e no golangci-lint.
Correção automática tipada. Um diagnóstico pode conter SuggestedFix: substituições textuais ancoradas em posições token.Pos. O gopls as aplica com um clique no editor. É isso que permite, por exemplo, ao staticcheck propor « substituir errors.Is(err, sql.ErrNoRows) por uma verificação mais idiomática » e à IDE fazer a substituição corretamente.
Para dar uma noção de proporção: singlechecker — a função que transforma um *Analyzer em um binário completo — cabe em um único arquivo. O esqueleto mínimo de um linter Go publicável é mais ou menos 40 linhas, incluindo documentação e testes. Compare com escrever um plugin ESLint do zero: não é a mesma escala.
Três usos que vemos em projetos reais:
net/http, deve-se passar pelo nosso wrapper de tracing. » Trinta linhas, um Analyzer que inspeciona os *ast.ImportSpec, um teste com analysistest.Run em um arquivo de fixture, e a regra é conectada na CI no dia seguinte.Analyzer com SuggestedFix transforma a operação em go vet -vettool=./meufix -fix ./.... Sem intervenção humana na cadeia.ctrlflow (control flow graph), buildssa (representação SSA do código Go) — nos quais é possível conectar análises de dataflow não triviais. Detectar leaks de sql.Rows não fechados, por exemplo, é feito de forma séria a partir da SSA, não do AST bruto.Dois pontos cegos para manter em mente:
Analyzer vê um package de cada vez. As análises inter-packages (whole-program) exigem trabalho adicional — é possível com « facts » (analysis.Fact), que permitem a um analisador exportar facts sobre os símbolos de um package para que esses facts sejam reutilizados quando outro package o importa, mas é um degrau mais complexo de se escrever.Analyzer ao go vet por configuração em tempo de execução: ele é compilado em um binário. É uma escolha (Go não gosta de plugins dinâmicos), não uma omissão.go/analysis é a API oficial para tudo o que diz respeito à análise estática em Go, do linter caseiro à migração automática de código.Run(*Pass). O resto (CLI, integração com go vet, integração com gopls, CI) é oferecido a você.sed, essa noite de estudo é bem investida.Artigo produzido por inteligência artificial, revisto sob controlo editorial humano.