Zenn
株式会社HRBrain
🚥

【Go】コーディング規則を簡単にlinterに落としこむ!go-ruleguardを使ってみる

に公開

はじめに

こんにちは、kenです。 2025年が始まりましたね! 今年はより一層発信に力を入れていきたいと思います。
さて、今回はgo-ruleguardというツールを使ってオリジナルのGoのlinterを作る方法をご紹介します。

オリジナルのlinterの作成は難しいイメージがありましたが、このツールを使えば驚くほど簡単に自前のlinterを作れることがわかったので、簡単なlinterを作成しつつその便利さを紹介できればと思います。

go-ruleguardとは

go-ruleguardは、Goのコードを解析し、ユーザーが定義したルールに違反しているコードがないかを検出する静的解析ツールです。
「ユーザーが定義したルール」というのがポイントで、これを使うことでプロジェクト固有のコーディング規約やベストプラクティスに違反している箇所を自動的に検出し、報告させることができます。

https://github.com/quasilyte/go-ruleguard

実際に使ってみる

インストール

まずはgo installruleguardをインストールします。

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をつけてくれました!

autofix.gif

テストを書いてみる

人間によるチェックを軽減するためのカスタムルールに誤りがあっては困るので、テストを書いてみます。

テストはgolang.org/x/tools/go/analysis/analysistestを使って書くことができます。
今回はテストの書き方が目的の記事ではないため詳しくは割愛しますが、テスト用のファイル(testdata/src/boolfunctionnaming/tests.go)を作り、それに対してanalysistest.Runでテストを実行するイメージです。検出されるメッセージの期待値はコメントに書いて定義します。

rules/example_test.go
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",
	)
}
rules/testdata/src/boolfunctionnaming/tests.go
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.ymlgolangci-lintの設定を書くことができるファイルです。
ここに記述を足すことで、自作のlinterをgolangci-lintに組み込むことができます。

この実装の際には下の公式ガイドが参考になりました。

https://www.quasilyte.dev/blog/post/ruleguard/#using-from-the-golangci-lint

どうやら以下の3点が満たされていれば良いみたいです。

  1. gocritic linter が有効になっていること
  2. ruleguard check が有効になっていること
  3. rulesパラメーターに自作のルールがセットされていること
.golangci.yml
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による静的解析が実行されます。

スクリーンショット 2024-12-30 18.47.21.png

自作のカスタムルールの警告も無事に表示されました!

さいごに

今回はgo-ruleguardを使って簡単なlinterを作成することで、その使い方を紹介してみました。
人間によるReviewも大切ですが、形式的なチェックはレビュワー側・レビュイー側ともに負担のかかる部分だと思うので、これをうまく活用することで仕組み化していきたいですね。
今回使用したコードは下のリポジトリにあげています。

https://github.com/ken-hashimoto/TechBlogSamples/tree/main/go-ruleguard-sample

間違いなどあればコメントにてご指摘いただけますと幸いです。最後まで読んでいただきありがとうございました!

株式会社HRBrain
株式会社HRBrain

Discussion

ログインするとコメントできます