2
0

More than 3 years have passed since last update.

Go のオレオレ linter を golang.org/x/tools/go/analysis 使って作成する

Posted at

ゴール

  • x/tools/go/analysis package を使って、linter を実装できるようになる

背景

Go は言語本体の簡潔さ、強力さはもちろんのことですが、周辺のツール/環境が充実していることでも知られています。
linter についても、標準で go vet や、標準の linter の runnner として、golangci-lint というサードパーティのツールも利用されています。

さらに、Go ではそのような linter 、及び静的解析を実装するためのライブラリが準標準 (golang.org./x 以下)で公開されています。
今回は、そのライブラリを使ってユーザ独自の linter を作る方法を紹介します。

環境

  • go1.14.4
  • skeleton v1.2.1

gostaticanalysis/skeleton で linter のひな形を作る

go/analysis package を使った linter の作成は、以下のツールでスケルトンを作成することができます。

https://github.com/gostaticanalysis/skeleton

skeleton が出力するスケルトンの内容

以下のようにファイル一式が出力されます。

.
│  hogefuga.go
│  hogefuga_test.go
│
├─cmd
│  └─hogefuga
│          main.go
│          varinit.exe
│
├─plugin
│  └─hogefuga
│          main.go
│
└─testdata
    └─src
        └─a
                a.go

  • hogefuga.go/hogefuga_test.go へ、linter の実装とテストを記載する
  • cmd 以下は go vet 等から利用する実行体を作成するコマンド
  • plugin 以下は golangci-lint から利用するプラグインを作成するコマンド
  • testdata 以下へ hogefuga_test.go から参照されるテストデータを作成する

これらで、linter を作るための基本的な材料は全てそろっていて、
すぐに linter の作成を始めることができます。

独自 linter を実装する

というわけで、早速 linter を作っていきます。
今回は簡単なサンプルとして、識別子に hoge/fuga のような意味のない名前を与えている場合、
それを linter の指摘として出力するというものを作ってみます。

テストデータを作成する

まず、linter の指摘を受けるような Go のコードをテストデータとして用意します。

go/analysis package は、 go/analysis/analysistest package を使って、テストができます。

そのテスト package では、 // want "期待値" というコメントで、 その行にどんな linter の指摘を期待するかを記載してテストすることができます。

a.go
package a

import "fmt"

func a() {
    var hoge string = "hoge" // want "identifier is meaningless"

    fuga(hoge) // want "identifier is meaningless" "identifier is meaningless"
}

func fuga(s string) { // want "identifier is meaningless"
    fmt.Println(s)
}

なお、テストコード側は以下のようになっており、複数 package を試すなどの場合除いては、
スケルトンの出力したコードからの変更は不要です。

hogefuga_test.go
package varinit_test

import (
    "testing"

    "github.com/akif999/varinit"
    "golang.org/x/tools/go/analysis/analysistest"
)

// TestAnalyzer is a test for Analyzer.
func TestAnalyzer(t *testing.T) {
    testdata := analysistest.TestData()
    analysistest.Run(t, testdata, varinit.Analyzer, "a")
}

linter の処理を実装する

以下のように実装しました。
ユーザで書いているのは、ast node*ast.Ident (識別子) へ絞り込むフィルタを作成していることと、
そのフィルタで ast を走査して、各 *ast.Ident (識別子) の要素に対して、指摘を出力するかを判定します。

ちなみに、本記事では取り上げていませんが、指摘を出力するだけではなく suggest fix (= 修正案を提示) したり、
lint すると同時に修正したりということもできるようです。

hogefuga.go
package varinit

import (
    "go/ast"

    "golang.org/x/tools/go/analysis"
    "golang.org/x/tools/go/analysis/passes/inspect"
    "golang.org/x/tools/go/ast/inspector"
)

const doc = "varinit is ..."

// Analyzer is ...
var Analyzer = &analysis.Analyzer{
    Name: "varinit",
    Doc:  doc,
    Run:  run,
    Requires: []*analysis.Analyzer{
        inspect.Analyzer,
    },
}

func run(pass *analysis.Pass) (interface{}, error) {
    inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)

    nodeFilter := []ast.Node{
        (*ast.Ident)(nil), // nodefileter の定義
    }

    inspect.Preorder(nodeFilter, func(n ast.Node) {
        switch n := n.(type) {
        case *ast.Ident: // 識別子に対して処理を記載する
            if isHoge(n.Name) || isFuga(n.Name) { // hoge、fuga に識別子が該当するかを確認する
                pass.Reportf(n.Pos(), "identifier is meaningless") // 指摘ありの場合、指摘を出力する API をコールする
            }
        }
    })

    return nil, nil
}

func isHoge(s string) bool {
    return s == "hoge"
}

func isFuga(s string) bool {
    return s == "fuga"
}

独自 linter の動かし方

独自 linter は、 go vet から動かすことができます。
golangci-lint の plugin を作ることができるようになっていますが、
記事執筆時点では、Windows/amd64 環境では build ができませんでした。

ちなみに、 go vet 本体の linter も、ある時点で go/analysis package を使ってリライトされています。

C:\Users\aki01\work\src\github.com\akif999\hogefuga\cmd\hogefuga>go vet --vettool=hogefuga.exe ..\..\testdata\src\a
# _/C_/Users/aki01/work/src/github.com/akif999/hogefuga/testdata/src/a
..\..\testdata\src\a\a.go:6:6: identifier is meaningless
..\..\testdata\src\a\a.go:8:2: identifier is meaningless
..\..\testdata\src\a\a.go:8:7: identifier is meaningless
..\..\testdata\src\a\a.go:11:6: identifier is meaningless

まとめ

  • go/analysis package を使うと、他の linter と共通の I/F で linter をつくることができる
  • linter の作成/テストをライブラリが支援してくれるので作るのが簡単
  • gostaticanalysis/skeleton を使うとスケルトンを出力してくれて、更に作りやすい
  • 現場独自の linter を作って、自分たちのプロダクトの品質とスピードの向上に貢献しませんか??
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0