【Go】コーディング規則を簡単にlinterに落としこむ!go-ruleguardを使ってみる
はじめに
こんにちは、kenです。 2025年が始まりましたね! 今年はより一層発信に力を入れていきたいと思います。
さて、今回はgo-ruleguard
というツールを使ってオリジナルのGoのlinterを作る方法をご紹介します。
オリジナルのlinterの作成は難しいイメージがありましたが、このツールを使えば驚くほど簡単に自前のlinterを作れることがわかったので、簡単なlinterを作成しつつその便利さを紹介できればと思います。
go-ruleguard
とは
go-ruleguard
は、Goのコードを解析し、ユーザーが定義したルールに違反しているコードがないかを検出する静的解析ツールです。
「ユーザーが定義したルール」というのがポイントで、これを使うことでプロジェクト固有のコーディング規約やベストプラクティスに違反している箇所を自動的に検出し、報告させることができます。
実際に使ってみる
インストール
まずはgo install
でruleguard
をインストールします。
go install github.com/quasilyte/go-ruleguard/cmd/ruleguard@latest
インストール完了後、ruleguard
コマンドが使用可能になっているかは下のコマンドで確認してください。
ruleguard -h
linterを作ってみる
次に、go-ruleguard
を使ってlinterを作成します。
今回は 「bool値のみを返す関数の関数名がIsから始まっているか」 を検証するlinterを作ってみます。
ルールの定義
まず、このカスタムルールを定義したファイルを作成します。
//go:build ruleguard
// +build ruleguard
package rules
import "github.com/quasilyte/go-ruleguard/dsl"
func boolFunctionNaming(m dsl.Matcher) {
m.Match(`func $name($*params) bool { $*body }`).
Where(!m["name"].Text.Matches(`^(Is).*`)).
Report("bool function name should start with 'Is'")
}
非常にシンプルなコードですね!
linterといえばASTでごちゃごちゃ書くイメージがあったんですが、こんなに簡潔にかけるんですね。
チェックの実行
わざとこのルールに違反したコードを書いておきます。
package main
func IsNegative(x int) bool {
return x < 0
}
func Zero(x int) bool {
return x == 0
}
Zero
がbool値のみを返す関数であるにも関わらず、Is
の接頭辞がついてないですね。
このコードに対して、ruleguard
を実行してみます。
すると期待通り、ルール違反が検出されました。
❯ ruleguard -fix -rules=./rules/example.go ./...
/Users/ken/Desktop/programming/TechBlogSamples/go-ruleguard-sample/main.go:7:1: boolFunctionNaming: bool function name should start with 'Is' (example.go:9)
あっという間にlinterが作れてしまいました。とても簡単ですね!!
autofixの機能も入れてみる
公式のexampleを読んでいると、autofixの機能も作れるみたいなので試してみました。
//go:build ruleguard
// +build ruleguard
package rules
import "github.com/quasilyte/go-ruleguard/dsl"
func boolFunctionNaming(m dsl.Matcher) {
m.Match(`func $name($*params) bool { $*body }`).
Where(!m["name"].Text.Matches(`^(Is).*`)).
Report("bool function name should start with 'Is'").
Suggest(`func Is$name $params bool { $body }`) // この部分を追加
}
fixオプションをつけて実行してみます。
ruleguard -fix -rules=./rules/example.go ./...
めでたく接頭辞にIs
をつけてくれました!
テストを書いてみる
人間によるチェックを軽減するためのカスタムルールに誤りがあっては困るので、テストを書いてみます。
テストはgolang.org/x/tools/go/analysis/analysistest
を使って書くことができます。
今回はテストの書き方が目的の記事ではないため詳しくは割愛しますが、テスト用のファイル(testdata/src/boolfunctionnaming/tests.go
)を作り、それに対してanalysistest.Run
でテストを実行するイメージです。検出されるメッセージの期待値はコメントに書いて定義します。
package rules
import (
"testing"
"github.com/quasilyte/go-ruleguard/analyzer"
"golang.org/x/tools/go/analysis/analysistest"
)
func TestRules(t *testing.T) {
if err := analyzer.Analyzer.Flags.Set("rules", "example.go"); err != nil {
t.Fatalf("set rules flag: %v", err)
}
analysistest.Run(
t,
analysistest.TestData(),
analyzer.Analyzer,
"boolfunctionnaming",
)
}
package boolfuncnametest
func IsPositive(x int) bool {
return x > 0
}
func Zero(x int) bool { // want "bool function name should start with 'Is'"
return x == 0
}
テストの書き方も簡単で助かりますね!
golangci-lintに組み込んでみる
次に、今回作ったboolFunctionNaming
のカスタムルールをgolangci-lint
に組み込んでみます。こうすることで、他のlinterと一緒に自作のlinterを実行させることができます。
golangci-lint
のインストール
言わずもがなですが、これからローカルでgolangci-lint
を実行するのでインストールが必要です。
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
.golangci.ymlの設定
.golangci.yml
はgolangci-lint
の設定を書くことができるファイルです。
ここに記述を足すことで、自作のlinterをgolangci-lint
に組み込むことができます。
この実装の際には下の公式ガイドが参考になりました。
どうやら以下の3点が満たされていれば良いみたいです。
-
gocritic
linter が有効になっていること -
ruleguard
check が有効になっていること -
rules
パラメーターに自作のルールがセットされていること
linters:
enable:
- gocritic # gocriticを有効化することでruleguardも使用可能に
linters-settings:
gocritic:
enabled-checks:
- ruleguard # ruleguardチェックを有効にする
settings:
ruleguard:
rules: "rules/example.go" # カスタムルールファイルのパス
実行
golangci-lint
を実行し、問題点が検出されることを確認します。
❯ golangci-lint run ./...
main.go:7:1: ruleguard: bool function name should start with 'Is' (gocritic)
func Zero(x int) bool { return x == 0 }
できました!
VSCodeの警告に表示させる
最後にVSCodeの「問題」の欄に警告を表示させるようにします。
(この部分はgo-ruleguard
というよりgolangci
についての説明ですね。)
プロジェクトフォルダに.vscode/settings.json
を作成し、下記のように記載します。
{
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--config=${workspaceFolder}/.golangci.yml", "--fast"]
}
すると先程作った.golangci,yml
の設定を使ってgolangci-lint
による静的解析が実行されます。
自作のカスタムルールの警告も無事に表示されました!
さいごに
今回はgo-ruleguard
を使って簡単なlinterを作成することで、その使い方を紹介してみました。
人間によるReviewも大切ですが、形式的なチェックはレビュワー側・レビュイー側ともに負担のかかる部分だと思うので、これをうまく活用することで仕組み化していきたいですね。
今回使用したコードは下のリポジトリにあげています。
間違いなどあればコメントにてご指摘いただけますと幸いです。最後まで読んでいただきありがとうございました!
Discussion