【Goland】 自作静的解析ツールをファイル保存時にフックさせる

この記事は[Wanoグループ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/wano-group) の3日目の記事になります。🎅🎁


## 何をするのか
Golandでは、 `gofmt` や `goimport` のような静的解析のコマンドは少しの設定でファイル保存時に実行することができます。
今回は自作の静的解析ツールを上と同じように保存時にフックさせてみようと思います。

プロジェクト特有のコマンドをCIで実行しているような場合、それをローカルでも通してからpushしたい！ みたいなケースで使えそうです


## 使うライブラリ
今回は、 `TODO` コメントが一つでもあったら怒られるというスパルタコマンドを **checom** という名前で作成 -> Golandに設定 してみます。
ちなみにTODOコメントに他意はありません

### 静的解析
コメントを抜き出すだけなら標準のライブラリで十分です。
* https://golang.org/pkg/go/parser/
* https://golang.org/pkg/go/token/

### CLIツール
[cobra](https://github.com/spf13/cobra) というCLIツール作成用のライブラリを使っていますが、必須ではないです。
* github.com/spf13/cobra


## 静的解析ツールの作成
静的解析は標準の `go` パッケージを使えば非常に簡単



```go:checom/main.go

package main

import (
	"fmt"
	"go/parser"
	"go/token"
	"os"
	"strings"

	"github.com/spf13/cobra"
)

func main() {
	if err := rootCmd.Execute(); err != nil {
		fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
		os.Exit(-1)
	}
}

func init() {
	cobra.OnInitialize()
}

var rootCmd = &cobra.Command{
	Use: "checom",
	Run: CommentChecker,
}

const ngWord = "TODO"

func CommentChecker(cmd *cobra.Command, args []string) {
	fset := token.NewFileSet()

	f, err := parser.ParseFile(fset, args[0], nil, parser.ParseComments)
	if err != nil {
		fmt.Println(err)
		return
	}

	var cnt int
	for _, cg := range f.Comments {
		t := cg.Text()
		if strings.Contains(strings.ToUpper(t), ngWord) {
			cnt++
			fmt.Printf("😡 \n%s\n", t)
		}
	}

	if cnt != 0 {
		fmt.Println("😡 DO NOW 😡")
		return
	}
	fmt.Println("You're hard worker 😆")
}


```

```go
f, err := parser.ParseFile(fset, args[0], nil, parser.ParseComments)
```
`arg[0]`にファイルのパスが入ってくる想定で、ファイルをパースしてコメントを引っこ抜いてます。

CLIツールはこれだけで、非常にシンプル

`go install` コマンドで `$GOPATH/bin/` にバイナリを設置します。

```bash
$ cd project/checom
$ go install
```
```bash
$ ll $GOPATH/bin/ | grep checom
-rwxr-xr-x  1 user  staff   4922168 11  4 17:03 checom
```

これで `$GOPATH/bin` にパスが通っていれば `checom /path/to/file` で実行できるようになりました。

## Golandの設定
Golandでは `File watcher` というファイル監視機構が用意されているのでそれを使います。
`Preference > Tools > File watcher > + > custom` から以下の設定します。
https://pleiades.io/help/go/new-watcher-dialog.html

<img width="993" alt="スクリーンショット 2019-11-04 18.08.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231923/2d899bd8-73b5-7777-0dd4-8844d44634a7.png">


<img width="757" alt="スクリーンショット 2019-11-04 17.09.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231923/53ebaba8-cf65-c089-3e42-bc68005df6a2.png">

**File type**

* どのファイル形式の保存・変更時にフックさせるかの設定です。今回は `Go` ですね。

**Program**

* ↑のバイナリの置き場所の指定です。
* `$GOPATH$` はGoland側に設定してあるマクロというかエイリアスなので、もし設定していなければ普通にバイナリまでのパスを書いても動きます。

**Argument**

* コマンドの引数を指定できます。
* 同様に `$FilePath$` はGoland側の設定で、保存・変更したファイルのパスになります。

**Show console**

* コマンドの実行結果をコンソールに出力する際の設定です。
* デフォルトでは `On error` ですが、今回は全て標準出力に出力するようにしてしまっていたので、 `Always` にしないと何も表示されません。


ということで、上記を設定することで、Goland上で `/path/to/project/hoge.go` を保存・変更した場合に

```bash
$GOPATH/bin/checom /path/to/project/hoge.go
```
が走るようになりました。

## 動作
<img width="1018" alt="スクリーンショット 2019-11-04 18.04.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231923/75314cee-66e9-b8b3-4fa0-405fdf06f1f2.png">

小文字でも見逃してくれません。

<img width="1189" alt="スクリーンショット 2019-11-04 17.57.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/231923/f846f0d1-bbfe-ebdf-27bb-81bca0c2c91f.png">

無事動きました

## まとめ

静的解析ツールを作るハードルがかなり低いので、プロジェクト内でもCIなどで必要なニーズが出てきたら簡単に導入できそう。
Golandは全体の2%くらいしか機能を使えてない気がするので、もっと活用していこうという気になりました。

---

Wanoでは積極的にエンジニア採用を行なっています！
まずはオンラインでVPoEとのカジュアル面談から。お好きな入り口からお気軽にお声がけください！
Wano Recruitページ　https://group.wano.co.jp/recruit/
QiitaJobs　https://jobs.qiita.com/employers/wano-inc/postings/1297
Wantedly　https://www.wantedly.com/companies/wano/projects
Findy　https://findy-code.io/companies/522