go/analysisのSuggestedFixでコードを修正する
Goの既存コードを修正するツールを作る時、
- 既存コードをどう書き換えて
- 出力して
- テストするか
を考えなければいけないのが少し面倒だと思っていました。
が、golang.org/x/tools/go/analysis
のSuggestedFixを使えばすごく簡単にできてしまいます。
golang.org/x/tools/go/analysis
は staticcheckやgolangci-lintなどの静的解析ツールでよく使われているパッケージです。
例えば以下のような、関数の引数にcontext.Context
があるかどうかチェックするツールがあったとして、
func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ (*ast.FuncDecl)(nil), } inspect.Preorder(nodeFilter, func(n ast.Node) { decl := n.(*ast.FuncDecl) if decl.Type.Params.NumFields() > 0 { // NOTE: 第1引数のみを文字列でチェックしているので厳密ではない if types.ExprString(decl.Type.Params.List[0].Type) == "context.Context" { return } } pass.Reportf(decl.Pos(), "missing ctx in parameter") }) return nil, nil }
これを、もしチェックに引っ掛かったら引数にcontext.Context
を追加できるように変更してみます。
まずはpass.Reportf
をpass.Report
に変更し、直接Diagnosticを渡せる形にします。
pass.Report(analysis.Diagnostic{
Pos: decl.Pos(),
Message: "missing context in parameter",
})
そしてSuggestedFixes
としてコードを変更する場所(Pos
からEnd
)と書き換え後のコード(NewText
)を渡します。
pass.Report(analysis.Diagnostic{ Pos: decl.Pos(), Message: "missing context in parameter", SuggestedFixes: []analysis.SuggestedFix{{ Message: "add ctx to parameter", TextEdits: []analysis.TextEdit{{ Pos: decl.Pos(), End: decl.Type.Params.Closing + 1, NewText: b, }}, }}, })
書き換え後のコードは標準パッケージのformat.Nodeを使って作ります。
func newText(pass *analysis.Pass, decl *ast.FuncDecl) ([]byte, error) { // Godoc、戻り値、関数の中身は使わずにコードを整形する f := &ast.FuncDecl{ Recv: decl.Recv, Name: decl.Name, Type: &ast.FuncType{ Params: &ast.FieldList{ List: append([]*ast.Field{{ Names: []*ast.Ident{{Name: "ctx"}}, Type: &ast.SelectorExpr{ X: &ast.Ident{Name: "context"}, Sel: &ast.Ident{Name: "Context"}, }, }}, decl.Type.Params.List...), }, }, } var buf bytes.Buffer if err := format.Node(&buf, pass.Fset, f); err != nil { return nil, err } return buf.Bytes(), nil }
この書き換えを実際に適用するにはコマンドラインツールとして実行する時に-fix
フラグを付けるようにすればOKです。
なお、-fix
フラグはunitcheckerだと渡せないため、main.go
はsinglecheckerかmulticheckerを使う必要があります。
もしくは、gopls
にAnalyzer
として組み込むことでエディタと連携して使うことも可能です。
多少作り込みが甘くても、リファクタリングする時だけ以下に追加し、go install
して使ってみても良いかもしれません。
https://github.com/golang/tools/blob/gopls/v0.6.4/internal/lsp/source/options.go#L1108-L1150
vim + vim-lspは該当箇所で:LspCodeAction
を実行すると呼び出せます。
テストについてはanalysistest.Runをanalysistest.RunWithSuggestedFixesに変更すればgoldenファイルと比較してくれるようになります。