x/tools/go/analysis/analysistest: support expectations for related information
要約
概要
golang.org/x/tools/go/analysis/analysistest パッケージの // want コメントを拡張する提案です。これにより、診断(Diagnostic)に付く「関連情報(analysis.RelatedInformation)」も、テストデータ側のコメントで期待値として書けるようになります。
ステータス変更
(新規) → active
2025年10月の起票後、@adonovan が支持を表明しました。そのうえで「相対行番号を使えるようにする」「End 位置の検証はやめる」という修正を提案し、提案者 @tcard が本文に反映しました。その後、@aclements が行番号の代わりに「アンカーコメント」で位置を指定する案を出しました。2026年10月14日には、この案を具体的な仕様に書き直したコメントを投稿し、同じ日に proposal review meeting の active 列に追加されました。設計の方向性がある程度固まったため、正式な審査に入ったと考えられます。
技術的背景
現状の問題点
現在の want コメントで期待値として書けるのは、診断メッセージと fact だけです。
x := f() // want "diag" "diag2" x:"fact1"
診断のカテゴリ、URL、修正候補(suggested fix)、関連情報はコメントでは検証できません。検証するには analysistest.Run が返す Result を自分で調べる必要があります。提案者はこの状態を「analysistest を使う意味が薄れ、analysis/checker を直接使うのと大差ない」と指摘しています。
もう一つ問題があります。lostcancel の "this return statement may be reached without using the %s var defined on line %d" のように、別の位置をメッセージ本文に行番号で埋め込んでいる analyzer があります。本来は RelatedInformation で表現すべき情報ですが、テストする手段がなかったことも、そうなっている理由の一つです。
提案された解決策
今回は範囲を「関連情報」に絞っています。議論は次のように進みました。
- 当初案:
want "diag" related 20:"extra"やrelated -3:"extra"のように、メッセージの後ろにrelated節を置き、ファイル名・行・列(絶対または相対)とメッセージの正規表現を並べる。End 位置は検証しない。 - @aclements の具体案(最新):
// anchor <name>という新しいコメントで、行に名前を付ける。名前は[a-zA-Z_][a-zA-Z0-9_]*にマッチする必要があり、コメントの探し方は// wantと同じ。wantコメントに@<anchor>:"<regexp>"という新しいフィールドを追加する。anchor は同じパッケージ内のどのファイルにあってもよく、regexp はRelatedInformation.Messageと照合される。@*:"..."と書くと位置は無視し、メッセージだけを照合する。関連情報がパッケージ外の位置を指す場合に必要になる。- 関連情報フィールドが1つでもある場合は、
Diagnostic.Relatedスライス全体と順序どおりに完全一致しなければならない。1つもない場合は、関連情報は無視される(既存テストとの互換性が保たれる)。
これによって何ができるようになるか
- 「変数宣言」と「問題が起きる箇所」のように2か所を結ぶ診断を、RelatedInformation で正しく報告し、その内容をテストで保証できます。
- メッセージに行番号を埋め込む書き方をやめられるため、テストデータを編集しても期待値の書き直しが起きにくくなります。
- singlechecker などを使い CI で動かす独自 analyzer の作者も、関連情報の出力を宣言的にテストできます。CI の出力には関連情報が常に含まれます。
コード例
// Before: Result を自分で調べる
results := analysistest.Run(t, dir, lostcancel.Analyzer, "a")
for _, r := range results {
for _, d := range r.Diagnostics {
if len(d.Related) != 1 || !strings.Contains(d.Related[0].Message, "return statement") {
t.Errorf("unexpected related info: %v", d.Related)
}
}
}
// After: testdata 内のコメントで宣言的に書く(@aclements 案に基づく)
func f(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx) // want "the cancel function is not used" @ret:"return statement reached without canceling context"
if cond {
return // anchor ret
}
cancel()
}
議論のハイライト
- 相対行番号の導入: @adonovan は、絶対行番号だと離れた場所への挿入でも期待値の振り直しが必要になると指摘し、
+N/-N形式を提案しました。これがPos "-" Endという区切りと衝突するため、End 位置の検証は省くことで合意しました(Diagnostic でも End は検証していないため)。 - クライアント対応の不足: @adonovan が lostcancel に RelatedInformation を試しに実装したところ、表示できたのは VS Code だけで、Emacs+eglot、Zed、Helix は表示しませんでした。これに対し提案者は、多くの analyzer は最終的に CI でも実行され、そこでは関連情報が必ず出力されるため重要だと反論し、@adonovan も納得しました。
- アンカー方式: @aclements は、行番号(相対でも)より壊れにくい方法として名前付きアンカーを提案し、提案者も賛成しました。最新の仕様はこの方式に基づいています。
- 照合ルールの変化: 当初案では「期待値にない関連情報があってもよい」としていました。最新案では、フィールドがあれば Related スライス全体と順序どおりに一致することを求める、より厳密なルールになっています。
- @adonovan は、細部で想定外の問題が出る可能性があるとして、まずプロトタイプ実装を作るよう提案者に勧めています。提案者は自分で実装したいと申し出ています。