Goのコードを静的解析するとき、自動生成コードをスキップするためのメモ。
TL;DR
- 自動生成のGoコードか判別したい
- Goの自動生成コードは定型文を入れるのがマナー
// Code generated 好きなコメント DO NOT EDIT
- https://golang.org/cmd/go/#hdr-Generate_Go_files_by_processing_source
- 上記定型文を探す正規表現は次のようになる
regexp.MustCompile("(?m)^// Code generated .* DO NOT EDIT\\.$")
var c = regexp.MustCompile("(?m)^// Code generated .* DO NOT EDIT\\.$")
func process(src []byte) bool {
return c.Match(src)
}
自動生成コードは無視したい
Goで静的解析ツールやコード修正ツールを作っていると、ディレクトリ再帰中にいくつかの存在を無視したくなる。
- testdataディレクトリに入っているもの
- ***_test.goファイル
- 自動生成されたファイル
先に挙げた2つはファイル名やパスを取得すれば対応できる。 最後の「自動生成されたファイル」に関して言うと一筋縄ではいかない。 どのようにファイルを見分けるかと言うと、ファイル内の文字列をパースする。
Goの自動生成されたコードの共通ルール
お行儀の良い大抵のコード自動生成ツールは「自動生成されたコードです」ということを示すコメントが入れられている。
https://golang.org/cmd/go/#hdr-Generate_Go_files_by_processing_source
To convey to humans and machine tools that code is generated, generated source should have a line that matches the following regular expression (in Go syntax):
// Code generated by MockGen. DO NOT EDIT.
なので、このコメント行を検出できれば自動生成されたコードなのかわかる。
正規表現を利用する
当然といえば当然で、正規表現を使えばよい。
var c = regexp.MustCompile("(?m)^// Code generated .* DO NOT EDIT\\.$")
func generated(src []byte) bool {
return c.Match(src)
}
正規表現の内容について
パっと見よくわからない(?m)
は複数行対応のフラグ。
https://github.com/google/re2/wiki/Syntax
(?flags)
: set flags within current group; non-capturingm
: multi-line mode:^
and$
match begin/end line in addition to begin/end text (default false)
これがないと、複数行文字列の中から該当表現を検出できない。
動作検証したテストコードは次のとおり。
https://play.golang.org/p/GMXVIXrqnLU
package main
import (
"regexp"
"testing"
)
func TestProcess(t *testing.T) {
t.Parallel()
tests := [...]struct {
name, src string
want bool
}{
{
name: "case1",
src: `// Code generated by MockGen. DO NOT EDIT.
package main
import (
"context"
"fmt"
"net/http"
)
func SampleFunc(ctx context.Context) {
}
`,
want: true,
},
{
name: "case2",
src: `
package main
import (
"context"
"fmt"
"net/http"
)
// Code generated by MockGen. DO NOT EDIT.
func SampleFunc(ctx context.Context) {
}
`,
want: true,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := generated([]byte(tt.src)); tt.want != got {
t.Fatalf("want %v, got %v, source: \n%s", tt.want, got, tt.src)
}
})
}
}
var c = regexp.MustCompile("(?m)^// Code generated .* DO NOT EDIT\\.$")
func generated(src []byte) bool {
return c.Match(src)
}
ファイルの途中にかかれていた場合も検出できている。
終わりに
厳密にやるならばASTを使ってgo/ast#File.Comments
をチェックしたほうが良さそうだが、このレベルで十分だろう。
(?m)
は知らない正規表現の記法だったのでそちらも学びになった。