Dev & Código Jul 26, 2026Añadir a favoritos

Bajo el capó de `go vet`, `staticcheck` y compañía: el equipo de Go mantiene un *framework* oficial que permite escribir un *linter* en unas pocas decenas de líneas.
Hereda un proyecto Go de 200 000 líneas. La revisión de código permite, desde hace seis meses, un patrón que provoca fugas de goroutines: un context.WithTimeout creado sin defer cancel(). Nadie lo detecta al leer el código. go vet no lo señala. Añadir una regla en el linter interno llevaría, en otro lenguaje, una pequeña semana.
En Go, se escribe un Analyzer en una tarde. Porque el equipo del lenguaje mantiene desde hace tiempo un paquete casi discreto pero fundamental: golang.org/x/tools/go/analysis.
El paquete go/analysis (« Marco de Análisis de Go ») no es nuevo: está publicado y documentado en pkg.go.dev/golang.org/x/tools/go/analysis, pero sigue apareciendo en Hacker News (última mención el 2026-07-26) porque muchos desarrolladores de Go aún lo desconocen. Sin embargo, es la infraestructura sobre la que gira el ecosistema de análisis estático del lenguaje: go vet se basa en él, staticcheck (Dominik Honnef) lo utiliza, gopls (el servidor de lenguaje oficial) conecta sus diagnósticos, y la inmensa mayoría de los linters modernos en CI —encabezados por golangci-lint— no son más que colecciones de Analyzer apilados.
La apuesta del framework es simple: un analizador = una struct. Esta struct declara su nombre, su documentación, sus dependencias hacia otros analizadores, y una función Run que recibe un objeto *analysis.Pass (el código parseado, el verificador de tipos, los archivos, los comentarios) y reporta sus diagnósticos mediante pass.Report. Eso es todo.
Tres ideas sostienen la arquitectura:
Modularidad por composición. Un analizador puede declarar Requires: []*analysis.Analyzer{inspect.Analyzer}: el framework resuelve el grafo de dependencias e inyecta el resultado de los analizadores previos en Pass.ResultOf. Resultado: tu analizador de negocio no vuelve a parsear los archivos, consume el resultado de inspect (que, a su vez, recorre el AST tipado una sola vez para toda la cadena).
Un solo « driver », múltiples arranques. Escribes tu Analyzer. singlechecker.Main(myAnalyzer) lo convierte en un binario CLI autónomo, multichecker.Main(a1, a2, a3) lo convierte en un binario multi-chequeos, unitchecker.Main(...) lo conecta en go vet -vettool=.... Sin código de pegamento: el mismo código del Analyzer funciona localmente, en CI, en el IDE vía gopls, y en golangci-lint.
Corrección automática tipada. Un diagnóstico puede llevar SuggestedFix: reemplazos textuales anclados en posiciones token.Pos. gopls los aplica con un clic en el editor. Esto permite, por ejemplo, que staticcheck proponga «reemplazar errors.Is(err, sql.ErrNoRows) por una verificación más idiomática» y que el IDE realice la sustitución correctamente.
Para dar una idea de magnitud: singlechecker —la función que transforma un *Analyzer en un binario completo— cabe en un solo archivo. El esqueleto mínimo de un linter Go publicable es, a grandes rasgos, unas 40 líneas, incluyendo la documentación y las pruebas. Compárese con escribir un plugin de ESLint desde cero: no es la misma escala.
Tres usos que se ven en proyectos reales:
net/http, hay que pasar por nuestro wrapper de tracing.» Treinta líneas, un Analyzer que inspecciona los *ast.ImportSpec, una prueba con analysistest.Run sobre un archivo de fixture, y la regla está conectada en CI al día siguiente.Analyzer con SuggestedFix convierte la operación en go vet -vettool=./monfix -fix ./.... Sin intervención humana en el proceso.ctrlflow (grafo de control), buildssa (representación SSA del código Go)— sobre los que se pueden conectar análisis de flujo de datos no triviales. Detectar fugas de sql.Rows no cerradas, por ejemplo, se hace seriamente a partir de la SSA, no del AST bruto.Dos puntos ciegos a tener en cuenta:
Analyzer ve un paquete a la vez. Los análisis inter-packages (whole-program) requieren trabajo adicional: es posible con los «hechos» (analysis.Fact), que permiten a un analizador exportar hechos sobre los símbolos de un paquete para que sean reutilizados cuando otro paquete lo importa, pero es un nivel más complejo de escribir.Analyzer en go vet mediante configuración en tiempo de ejecución: se compila en un binario. Es una elección (Go no gusta de plugins dinámicos), no un olvido.go/analysis es la API oficial para todo lo relacionado con el análisis estático en Go, desde el linter interno hasta la migración automática de código.Run(*Pass). El resto (CLI, integración con go vet, integración con gopls, CI) te lo dan hecho.sed, esa tarde está bien invertida.Artículo producido por inteligencia artificial, revisado bajo control editorial humano.