これは Go3 Advent Calendar 2019 の 3 日目の記事です。
今年 2 月にリリースされた Go 1.12 において、 公式の静的解析ツールである go vet が golang.org/x/tools/go/analysis を使う形に書き直されました。
Analyzer
golang.org/x/tools/go/analysis
は Analyzer (静的解析モジュール) を作るためのパッケージで、 go vet
が行う様々な処理は個別の Analyzer
として分離されこのパッケージ配下に格納されています。これらは外部からも参照できるようになっており、サードパーティの Analyzer
から参照することもできます。
Diagnostic
Analyzer
は、解析して見つかった問題を Diagnostic (診断) として報告します。 Diagnostic
には Pos
(コードの開始位置) と Message
(メッセージ) が含まれます。
type Diagnostic struct { Pos token.Pos Message string ... }
例えば Printf()
の書式識別子と引数の型が合っていないと go vet
は診断を出します。
$ cat main.go package main import "fmt" func main() { fmt.Printf("%s", 1) }
$ go vet . ./main.go:6:2: Printf format %s has arg 1 of wrong type int
このように問題があったコードの位置 (./main.go:6:2
) とメッセージ (Printf format %s has arg 1 of wrong type int
) を出すことでユーザーに修正を促す仕組みになっています。
Analyzer
は checker というパッケージを通して実行可能ファイルにすることができます。 checker には singlechecker と multichecker 、そして unitchecker の 3 種類があり、 go vet
は unitchecker
を使って作られています。
SuggestedFix
今年の 6 月、この golang.org/x/tools/go/analysis
に SuggestedFix (修正案) という機能が入りました。これにより、今までのように解析して見つかった問題を報告するだけでなく、その修正案を提示できるようになりました。 SuggestedFix
には Message
(メッセージ) と複数の TextEdit
(テキスト編集) が含まれます。
type SuggestedFix struct { Message string TextEdits []TextEdit }
TextEdit
は Pos
(コードの開始位置) と End
(コードの終了位置) 、そして NewText
(新しいテキスト) から構成されます。
type TextEdit struct { Pos token.Pos End token.Pos NewText []byte }
そして SuggestedFix
は Diagnostic
に複数紐づけることができます。つまり 1 つの診断に対して複数の修正案を提示できるということですね。
type Diagnostic struct { Pos token.Pos Message string SuggestedFixes []SuggestedFix ... }
SuggestedFix
に複数の TextEdit
を含めているのは修正案に柔軟性を持たせるためでしょう。例えば「関数に不要な引数が含まれる」という診断に対する修正案には、関数そのものに対する編集だけでなくその呼び出し元に対する編集も含めることができます。
func add(a, b, c int) int { // ^^^ ここを編集する (1 つ目の TextEdit) return a + b } func main() { fmt.Println(add(1, 2, 0)) // ^^^ ここも編集する (2 つ目の TextEdit) }
SuggestedFix
のサンプルとして、引数が 1 つの fmt.Printf()
を fmt.Print()
に修正する Analyzer
を作ってみました。
-fix
を付けて実行することで SuggestedFix
を適用し、コードを自動修正することができます。
$ cat main.go package main import "fmt" func main() { fmt.Printf("Hello, World") }
$ fmtprintf -fix .
./main.go:6:2: fmt.Printf call which have one argument can be replaced with fmt.Print
$ cat main.go package main import "fmt" func main() { fmt.Print("Hello, World") }
それから今年リリースされた Goa v3 へ移行するため v1 から v3 へのアップグレードツールを作っているのですが、こちらも SuggestedFix
を使う Analyzer
として実装しています。
SuggestedFix の適用
SuggestedFix
の適用方法は現状 2 通り用意されているようです。
1 つは前述したサンプルの例で出た -fix
を使う方法で、 singlechecker
と multichecker
はこのフラグが渡されると SuggestedFix
を自動で適用する仕組みになっています。 go vet
に使われている unitchecker
はこのフラグに対応しておらず、現状だと SuggestedFix
の適用はできないようでした。
もう 1 つは gopls を使う方法です。 SuggestedFix
は既に gopls
に統合されているようですが、私が使っている vim-go はまだ該当のメソッド (textDocument/codeAction
) に対応していないようで今回は試せませんでした。
gopls
自体もまだ開発途中のようですが、近い将来に Analyzer
が提示する修正案を様々なエディタから適用できるようになるかもしれません。