Dev & Code 21 h agoブックマークに追加

`go vet`、`staticcheck` などの裏側:Go チームは、数十行でリンターを書くことができる公式のフレームワークを維持しています。
200,000行のGoプロジェクトを引き継ぎます。6ヶ月間、コードレビューで見逃されていたゴルーチンリークを引き起こすパターンがあります:defer cancel()なしで作成されたcontext.WithTimeoutです。誰も読んで気づきません。go vetはそれを報告しません。他の言語では、カスタムルールを追加するのに1週間かかります。
Goでは、1晩でAnalyzerを書くことができます。言語チームが長年、ほとんど目立たないが重要なパッケージを維持しているからです:golang.org/x/tools/go/analysisです。
go/analysis(「Go Analysis Framework」)パッケージは新しいものではありません - pkg.go.dev/golang.org/x/tools/go/analysisで公開され、ドキュメント化されています - ですが、Hacker Newsで定期的に取り上げられます(最後の取り上げは2026-07-26)。まだ多くのGo開発者がこれを知らないからです。しかし、これは言語の静的解析エコシステムのインフラです:go vetはこれに依存しており、staticcheck(Dominik Honnef)はこれを使用し、gopls(公式の言語サーバー)は診断をここに接続し、現代のCIのほとんどのリント(golangci-lintを筆頭に)は単にAnalyzerのコレクションです。
フレームワークのコンセプトはシンプルです:1つの解析器 = 1つのstruct。このstructは名前、ドキュメント、他の解析器への依存関係、および*analysis.Pass(解析されたコード、型チェッカー、ファイル、コメント)を受け取るRun関数を宣言します。そして、pass.Reportを通じて診断を報告します。それだけです。
3つのアイデアがアーキテクチャを支えています:
組み合わせによるモジュラリティ。 解析器はRequires: []*analysis.Analyzer{inspect.Analyzer}のように宣言できます:フレームワークは依存関係グラフを解決し、前提となる解析器の結果をPass.ResultOfに注入します。結果として、ビジネス解析器はファイルを再解析せず、inspect(これはタイプ付きASTを1回だけウォークする)の結果を消費します。
1つの「ドライバー」、複数のブートストラッピング。Analyzerを書きます。singlechecker.Main(myAnalyzer)はそれを独立したCLIバイナリにし、multichecker.Main(a1, a2, a3)はそれをマルチチェックバイナリにし、unitchecker.Main(...)はそれをgo vet -vettool=...に接続します。グルーコードはゼロ:同じAnalyzerコードはローカル、CI、IDE(gopls経由)、golangci-lintで実行されます。
タイプ付きの自動修正。 診断にはSuggestedFixを付けることができます:token.Posの位置に基づいたテキスト置換です。goplsはエディタで1クリックでこれを適用します。例えば、staticcheckが「errors.Is(err, sql.ErrNoRows)をよりイディオム的なチェックに置き換える」と提案できるのはこのためです。
規模感を示すために、singlechecker - *Analyzerを完全なバイナリに変換する関数 - は1つのファイルで構成されています。公開可能なGoリンターの最小限の骨格は約40行です。ドキュメントとテストを含めて。ESLintプラグインをゼロから書くのと比べると、スケールが全く違います。
実際のプロジェクトで見られる3つの用途:
企業内のルール。 「net/httpを直接インポートすることは禁止です。トレース用のラッパーを通してください。」 30行、*ast.ImportSpecを検査するAnalyzer、フィクスターファイルに対するanalysistest.Runのテスト、そしてルールはCIに接続されます。
大規模なコード移行。 大規模なリファクタリング(500ファイルで非推奨APIの名前変更):SuggestedFixを持つAnalyzerは操作をgo vet -vettool=./monfix -fix ./...に変えます。人間はループに入っていません。
カスタムデータフロー分析。 フレームワークはコンパニオンパッケージを提供します - ctrlflow(制御フローグラフ)、buildssa(GoコードのSSA表現) - これに非自明なデータフロー分析を接続できます。例えば、sql.Rowsの未閉じのリークを検出するには、SSAから真剣に行う必要があり、生のASTではありません。
2つの盲点を頭に入れておく必要があります:
パッケージ内解析。 デフォルトでは、Analyzerは1つのパッケージずつしか見ません。パッケージ間解析(全プログラム)には追加の作業が必要です - 「事実」(analysis.Fact)を使用すると可能ですが、これは別のパッケージがそのパッケージをインポートしたときに、解析器がパッケージのシンボルについての事実をエクスポートできるようにするもので、より複雑です。
動的プラグインのエコシステムはありません。go vetにAnalyzerをランタイム設定でプラグインすることはできません:バイナリにコンパイルします。これは選択(Goは動的プラグインが好きではない)であり、忘れではない。
go/analysisは、カスタムリントから自動コード移行まで、Goの静的解析に関する公式APIです。Run(*Pass)を持つstructです。残り(CLI、go vet統合、gopls統合、CI)は提供されます。sedで書いている場合、その夜はよく投資されています。本記事は人工知能により作成され、人間の編集管理のもとで校閲されています。