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

## ゴール

* `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 を作って、自分たちのプロダクトの品質とスピードの向上に貢献しませんか??