Slide 1

Slide 1 text

analysis パッケージの仕組みの上で Multi linter with configを実現する ⼩⼭健⼀郎 / Tailor Inc. 2025.9.27 Go Conference 2025

Slide 2

Slide 2 text

少し実⽤的で⼩さなOSSを書くのが趣味 ● GitHub: @k1LoW ● X: @k1LoW ● SWE at Tailor Inc. / Fukuoka.go ⼩⼭健⼀郎 ⾃⼰紹介 2 Ken'ichiro Oyama https://findy-code.io/media/articles/codesidechat-k1LoW \ github.com/yuin/goldmarkを応援できます!/ \ 毎⽇「応援」ボタン押してください!/

Slide 3

Slide 3 text

https://careers.tailor.tech/ 全方位で採用中です! Your ERP, your way. 業務に最適な ERPを、思いのままに。 Tailorは、モノリシック(単一構造)な従来型ERPに代わる柔軟な モジュール型アーキテクチャを提供し、企業が自社に最適な業 務システムをAPIで自由に構成できる「コンポーザブルERP」を 実現します。 3

Slide 4

Slide 4 text

Motivation 4

Slide 5

Slide 5 text

● セッションタイトルは「analysis パッケージの仕組みの上でMulti linter with configを実現する」 ● Multi linter with config ○ 単⼀の設定ファイルを読んでその内容を各Linterの解析ロジックに反映させる機能 ■ つまり Golangci-lint や RuboCop のような機能 Multi linter with config Motivation 5

Slide 6

Slide 6 text

● Golangci-lint is a fast linters runner for Go ○ It runs linters in parallel, uses caching, supports YAML configuration, integrates with all major IDEs, and includes over a hundred linters. ○ https://golangci-lint.run/ ● ビルトインされた複数のLinterを同時に実⾏し、結果をまとめて出⼒してくれる ● 設定は1つのファイル( .golanci.yml etc. )に統合されており、それぞれのLinterの調整が可能。 Golangci-lint Motivation 6

Slide 7

Slide 7 text

● コーディングスタイルに特化した統合型Linter。 ○ https://github.com/k1LoW/gostyle ■ gostyle IS NOT Go Style ( https://google.github.io/styleguide/go/ ) ■ いくつか存在するコーディングスタイルの記述からリントルール(analysis.Analyzer)を 作成(現在17ルールある) ● Effective Go (https://go.dev/doc/effective_go) ● Go Style (https://google.github.io/styleguide/go/) ● Go Code Review Comments (https://go.dev/wiki/CodeReviewComments) ○ エラーにリントルールの根拠となるコーディングスタイルのURLを含めることでコンセンサスを取り やすくしている gostyle Motivation 7

Slide 8

Slide 8 text

● Go公式の、Goのソースコードを静的解析する ツール‧コマンド ○ https://pkg.go.dev/cmd/vet ● 組み込みのAnalyzer以外に、引数で任意のvet toolを渡すことができる Motivation 8 go vet

Slide 9

Slide 9 text

● go vet の vet toolとして使⽤可能 ● 設定ファイルはGitルートに .gostyle.yml もしくは .gostyle.yaml というファイル名で設置するか、 -gostyle.config に絶対パスでパスを渡す How to use gostyle ( As a vet tool ) Motivation 9

Slide 10

Slide 10 text

● gostyleの場合 ○ コーディングスタイルはチームやプロジェクトによって異なるので、それぞれのリントルールを設定 によって調整できるようしたい ● vet toolを作る時は⼤抵の場合、⽬の前のたった1つのユースケースに特化したものを作る時。 ● しかし、それが隣のユースケースで使われるようになった時、その新しいユースケースに合わせるための 調整弁が必要。 ● このとき"Multi linter with config"が必要になってくる なぜMulti linter with configが必要か Motivation 10

Slide 11

Slide 11 text

Multi linter with config using analysis package 11

Slide 12

Slide 12 text

go vet -vettool=`which mylint` [packages] で実⾏できる mylint を作成する 1. &analysis.Analyzer{} を必要な分作成する a. https://pkg.go.dev/golang.org/x/tools/go/analysis#Analyzer 2. unitchecker.Main() に作成した各&analysis.Analyzer{}を渡す go vet のカスタム vet tool の作成と使⽤⽅法 Multi linter with config using analysis package 12

Slide 13

Slide 13 text

Multi linter with config using analysis package 13 main() of gostyle v0.10.0

Slide 14

Slide 14 text

● Name string ... Analyzerの名前。go vet 経由のフラグ名にも使われる ● Doc string ... Analyzerの説明 ● URL string ... URL ● Flags flag.FlagSet ... Analyzerが持つフラグ ● Run func(*Pass) (any, error) ... Analyzerの解析処理のメイン部 ● RunDespiteErrors bool ... 対象コードがパースエラーになっていても実⾏するかどうかのフラグ ● Requires []*Analyzer ... このAnalyzerの前処理として実⾏が必要なAnalyzerのリスト ● ResultType reflect.Type ... Run の返り値の型。他のAnalyzerにResultとして渡せる ● FactTypes []Fact ... 使⽤するFactの型。他のAnalyzerにFactとして渡せる analysis.Analyzer の各フィールド概要 Multi linter with config using analysis package 14

Slide 15

Slide 15 text

● 様々な「前処理」Analyzerを指定することで Run func(*Pass) (any, error) での解析処理を効率よ く実施できる ○ golang.org/x/tools/go/analysis/passes/inspect.Analyzer ■ 対象のASTを深さ優先探索で⾛査できる inspector.Inspector.Preorder など、複数の便 利な関数を提供する ○ github.com/gostaticanalysis/comment/passes/commentmap.Analyzer ■ コード内のコメントを活⽤しやすくする ○ その他 golang.org/x/tools/go/analysis/passes/buildssa.Analyzer や golang.org/x/tools/go/analysis/passes/ctrlflow.Analyzer など Requires []*Analyzer
 Multi linter with config using analysis package 15

Slide 16

Slide 16 text

Multi linter with config using analysis package 16 (再掲)main() of gostyle v0.10.0

Slide 17

Slide 17 text

● Name は gostyle に設定 ● Flags 経由で config フラグを⽤意し、設定ファイルのパスを取得 ○ go vet 経由だとフラグは Name の値と合わせて -gostyle.config になる ● Run 内で設定ファイルをパースして &Config{} に値を設定する。 ○ 指定がない場合はGitルートで指定のファイル名( .gostyle.yml or .gostyle.yaml )を読む ○ &Config{} には gostyle の全てのAnalyzerの設定が含まれている ■ 必要に応じてデフォルト値を設定する ● ResultType として設定 *Config を指定した上で、Run の返り値として返す config.Loader Multi linter with config using analysis package 17 前処理Analyzerとして設定ファイルを読む

Slide 18

Slide 18 text

Multi linter with config using analysis package 18 config.Loader と Analyzers と main() の依存関係

Slide 19

Slide 19 text

● &analysis.Analizer{} の Requires フィールドを活⽤ ● 全てのLinter⽤Analyzerの前処理(Requires)として設定ファイル読み込み⽤Analyzer config.Loader を⽤意 ● ResultType: reflect.TypeOf((*Config)(nil)) として、全てのLinter⽤Analyzerに設定を伝播さ せる How to implement "Multi linter with config" Multi linter with config using analysis package 19

Slide 20

Slide 20 text

20 End...?

Slide 21

Slide 21 text

21 More!!!

Slide 22

Slide 22 text

22 Run as a standalone CLI

Slide 23

Slide 23 text

● 先ほどまでの gostyle の実⾏には go vet が必要。 ○ 単独では実⾏できない。 ● golangci-lint run のように gostyle も gostyle run で静的解析ができて欲しい ● (かつ、 go vet 経由での実⾏機能も維持したい) gostyle run を実現する Run as a standalone CLI 23

Slide 24

Slide 24 text

● golang.org/x/tools/go/analysis/unitchecker.Main の代わりに golang.org/x/tools/go/analysis/multichecker.Main にAnalyzerを渡せば良い golang.org/x/tools/go/analysis/multichecker Run as a standalone CLI 24

Slide 25

Slide 25 text

● (再掲)go vet 経由での実⾏機能も維持したい ● main関数で分岐することで実現 ○ gostyle v0.25.1 ● cmd.Execute() でサブコマンドが run のとき に multichecker.Main を実⾏ Run as a standalone CLI 25 os.Args の値で分岐する "Run as a standalone CLI" and "Run as a vet tool"

Slide 26

Slide 26 text

Inline ignoring 26

Slide 27

Slide 27 text

● 設定ファイルでのLinterの調整は実現できた ● しかし⾏ごとでの調整はできない ● Golangci-lintのように //nolint:staticcheck のようなインラインコメントで各Linterの無効化ができ て欲しい ● //nostyle:all や //nostyle:nilslices みたいな細かい制御ができて欲しい //nostyle:[analyzer name] を実現する Inline ignoring 27

Slide 28

Slide 28 text

● Run は引数に pass *analysis.Pass を取る Run func(pass *Pass) (any, error)
 ● pass.Reportf や pass.ReportRangef で解析結果をレポートできる ○ 引数には ポジション(token.Pos) が必要 analysis.Analyzer での解析結果のレポート Inline ignoring 28 Run func(*Pass) (any, error)


Slide 29

Slide 29 text

● コメント(type comment.Maps []ast.CommentMap)を収集してくれるAnalyzerを持つ ○ github.com/gostaticanalysis/comment/passes/commentmap ○ Requiresに登録しておくだけで収集したコメントを取得可能 ○ コメント情報には token.Pos が保持されている ● commentmapを使うことで、 レポート時の token.Pos と コメント情報の token.Pos を使ってレポート ⾏に関係するコメントを抽出できる レポート対象の⾏のコメントを特定する Inline ignoring 29 github.com/gostaticanalysis/comment を使う

Slide 30

Slide 30 text

● github.com/gostaticanalysis/comment にはコメント操作ための関数がいくつか提供されている ● IgnorePos はそのうちの1つ ● //lint:ignore Check1[,Check2,...,CheckN] reason というstaticcheck styleのコメントを判定 可能 ● https://pkg.go.dev/github.com/gostaticanalysis/comment#Maps.IgnorePos By github.com/gostaticanalysis/comment Inline ignoring 30 func (maps Maps) IgnorePos(pos token.Pos, check string) bool


Slide 31

Slide 31 text

● レポート⽤のstruct reporter.Reporter を⽤意 ● 内部で //nostyle:[analyzer name] のコメント判定をしてから pass.Reportf を呼ぶ ○ Append -> Report
 gostyleでのInline ignoringの実装 Inline ignoring 31 github.com/k1LoW/gostyle/reporter.Reporter

Slide 32

Slide 32 text

まとめ 32

Slide 33

Slide 33 text

● analysis パッケージの仕組みの上での統合型Linterを作り⽅を紹介 ○ Multi linter with config ○ Run as a standalone CLI ○ Inline ignoring ● analysis パッケージで Golangci-lint や RuboCop のような機能が実現できる ● まずはカスタムvet toolから。そして統合型のLinterへ まとめ 33

Slide 34

Slide 34 text

34 Thank you!!!