Dev & Code 21 h agoZu Lesezeichen hinzufügen

Unter der Haube von `go vet`, `staticcheck` und Co.: Das Go-Team pflegt einen offiziellen Framework, der es ermöglicht, einen Linter in wenigen Dutzend Zeilen zu schreiben.
Sie erben ein Go-Projekt mit 200.000 Zeilen. Der Code-Review lässt seit sechs Monaten ein Muster durchgehen, das zu Goroutine-Leaks führt: ein context.WithTimeout, das ohne defer cancel() erstellt wird. Niemand sieht es beim Lesen. go vet meldet es nicht. Das Hinzufügen einer Regel im hausgemachten Linter würde in einer anderen Sprache etwa eine Woche dauern.
In Go schreibt man einen Analyzer an einem Abend. Weil das Team der Sprache seit langem ein quasi diskretes, aber dennoch fundamentales Paket pflegt: golang.org/x/tools/go/analysis.
Das Paket go/analysis (« Go Analysis Framework ») ist nicht neu - es wird auf pkg.go.dev/golang.org/x/tools/go/analysis veröffentlicht und dokumentiert - aber es taucht regelmäßig auf Hacker News auf (letzte Erwähnung am 2026-07-26), weil viele Go-Entwickler es immer noch nicht kennen. Dabei ist es die Infrastruktur, auf der das statische Analyse-Ökosystem der Sprache läuft: go vet stützt sich darauf, staticcheck (Dominik Honnef) verwendet es, gopls (der offizielle Language Server) verbindet seine Diagnosen damit, und die überwiegende Mehrheit der Linter der modernen CI - golangci-lint an der Spitze - sind nur Sammlungen von Analyzer, die gestapelt werden.
Die Wette des Frameworks ist einfach: ein Analysator = eine Struct. Diese Struct erklärt ihren Namen, ihre Dokumentation, ihre Abhängigkeiten zu anderen Analysatoren und eine Funktion Run, die ein Objekt *analysis.Pass (der geparste Code, der Type-Checker, die Dateien, die Kommentare) erhält und ihre Diagnosen über pass.Report meldet. Das ist alles.
Drei Ideen tragen die Architektur:
Modularität durch Komposition. Ein Analysator kann Requires: []*analysis.Analyzer{inspect.Analyzer} erklären: Das Framework löst den Abhängigkeitsgraphen und injiziert das Ergebnis der vorab erforderlichen Analysatoren in das Pass.ResultOf. Ergebnis: Ihr Geschäftsanalysator parst die Dateien nicht neu, er verbraucht das Ergebnis von inspect (das den typisierten AST-Walk einmal für die gesamte Kette durchführt).
Ein einziger « Treiber », mehrere Bootstrappings. Sie schreiben Ihren Analyzer. singlechecker.Main(myAnalyzer) macht daraus ein autonomes CLI-Binary, multichecker.Main(a1, a2, a3) macht daraus ein Multi-Check-Binary, unitchecker.Main(...) verbindet es mit go vet -vettool=.... Kein Klebstoff-Code: Der gleiche Analyzer-Code läuft lokal, in der CI, über gopls in der IDE und in golangci-lint.
Automatische typisierte Korrektur. Eine Diagnose kann SuggestedFix enthalten: Textuelle Ersetzungen, die auf Positionen token.Pos verankert sind. gopls wendet sie mit einem Klick im Editor an. Dadurch kann staticcheck beispielsweise vorschlagen, « errors.Is(err, sql.ErrNoRows) durch eine idiomatischere Prüfung zu ersetzen », und die IDE führt die Substitution ordnungsgemäß durch.
Um eine Größenordnung zu geben: singlechecker - die Funktion, die einen *Analyzer in ein vollständiges Binary umwandelt - besteht aus einer Datei. Das minimale Gerüst eines veröffentlichbaren Go-Linters umfasst etwa 40 Zeilen, einschließlich Dokumentation und Tests. Vergleichen Sie dies mit dem Schreiben eines ESLint-Plugins von Grund auf: Es ist nicht die gleiche Größenordnung.
Drei Anwendungsfälle, die wir in echten Projekten sehen:
net/http direkt zu importieren, man muss unser Tracing-Wrapper verwenden. » Dreißig Zeilen, ein Analyzer, der die *ast.ImportSpec inspiziert, ein Test mit analysistest.Run auf einer Fixture-Datei, und die Regel wird am nächsten Tag in die CI eingebunden.Analyzer mit SuggestedFix verwandelt die Operation in go vet -vettool=./monfix -fix ./.... Kein Mensch in der Schleife.ctrlflow (Control Flow Graph), buildssa (SSA-Darstellung des Go-Codes) - auf denen nicht triviale Dataflow-Analysen durchgeführt werden können. Das Erkennen von nicht geschlossenen sql.Rows-Lecks erfolgt beispielsweise ernsthaft auf der Grundlage der SSA, nicht des rohen AST.Zwei blinde Flecken, die man im Hinterkopf behalten sollte:
Analyzer ein Paket auf einmal. Inter-Paket-Analysen (ganzes Programm) erfordern zusätzliche Arbeit - es ist möglich mit den « Fakten » (analysis.Fact), die es einem Analysator ermöglichen, Fakten über die Symbole eines Pakets zu exportieren, damit diese Fakten wiederverwendet werden, wenn ein anderes Paket sie importiert, aber es ist eine Stufe komplexer zu schreiben.Analyzer in go vet per Laufzeitkonfiguration ein: man kompiliert ihn in ein Binary. Das ist eine Entscheidung (Go mag keine dynamischen Plugins), kein Vergessen.go/analysis ist die offizielle API für alles, was mit statischer Analyse in Go zu tun hat, vom hausgemachten Linter bis zur automatischen Code-Migration.Run(*Pass). Der Rest (CLI, Integration von go vet, Integration von gopls, CI) wird Ihnen geschenkt.sed schreiben, ist dieser Abend gut investiert.Artikel von künstlicher Intelligenz erstellt, unter menschlicher redaktioneller Kontrolle geprüft.