[Go] ソースコードを静的解析してコードを自動生成する

# はじめに

コード以外の情報を元にして自動生成したことはあったのですが、コードを静的解析した内容をもとに自動生成するのは初めてだったので、調べた内容をまとめる記事を書きました。

今回は、特定のパッケージ配下にある構造体全てにメソッドを生やすコードを生成しようと思います。
※typeで型定義した構造体のみ生成対象とします

また、今回説明する内容を実装した[サンプルリポジトリ](https://github.com/cpp0302/method-generator-example)も作成しましたので、必要であればそちらも参照してください。

# コードを解析

## go/typesを使用して型情報を取得

goは標準でコードを静的解析するためのライブラリが整えられていますが、その中でも`go/types`は、型情報を取得するのに使えます。

以下は、パッケージ配下に存在する、typeで型定義した構造体の名前の一覧を取得する処理です

```go
structNames := []string{}

// 生成方法は後述
var pkg *types.Package

// パッケージ配下に存在する各要素の名前を取得
for _, name := range pkg.Scope().Names() {

	// typeで定義しているかどうかチェック
	obj, ok := pkg.Scope().Lookup(name).(*types.TypeName)
	if !ok {
		continue
	}

	// structかどうかチェック
	if _, ok := obj.Type().Underlying().(*types.Struct); !ok {
		continue
	}

	structNames = append(structNames, obj.Name())
}
```

## *types.Packageの生成

`*types.Package`を使用して型情報を取得できるとわかったところで、肝心の`*types.Package`の生成方法ですが、パッケージ解析を実施するためのライブラリである[x/tools/go/packages](https://pkg.go.dev/golang.org/x/tools/go/packages)から取得することができるので、こちら経由で取得します。

なお、同様の機能を持つライブラリとして[golang.org/x/tools/go/loader](https://pkg.go.dev/golang.org/x/tools/go/loader)がありますが、こちらはモジュールをサポートしていないため非推奨であり、代わりに`x/tools/go/packages`を使用するよう促されています。

例) bytesパッケージの型情報を取得する

```go
cfg := &packages.Config{
	// 型情報を取得するモードを指定する。必要であれば他のモードも追加して情報を取得可能
	Mode: packages.NeedTypes | packages.NeedTypesInfo,
}

// パッケージ情報をロード
pPkgs, _ := packages.Load(cfg, "bytes")

for _, pPkg := range pPkgs {
	var pkg *types.Package = pPkg.Types
	// 以降型情報の解析
}
```

これで`*types.Package`を取得できるので、後は前述したとおり型情報を解析していけばOKです。
今回は例として`bytes`パッケージを直接指定しましたが、他にも様々な指定方法があるようです。

# コードを生成

コードを解析して必要な情報が得られたら、コード生成です。
今回はテキストテンプレートを使用してコードを生成します。

```go

func main() {
	packageName := "pkg"
	structNames := []string{"A", "B"}

	templData := struct {
		PackageName string
		StructNames []string
	}{
		PackageName: packageName,
		StructNames: structNames,
	}

	var w bytes.Buffer
	tmpl := template.Must(template.New("mytemplate").Parse(templStr))
	tmpl.Execute(&w, templData)

	// 以下、ファイル出力など...
}

const templStr = `// Code generated by gen/genmethods.go; DO NOT EDIT.

package {{ .PackageName }}

import "fmt"

{{ range $structName := .StructNames }}
// PrintType 型情報を標準出力する
func (s {{ $structName }}) PrintType() {
	fmt.Printf("%T\n", s)
}
{{ end }}
`
```

実行したら以下のようなコードを生成できます。

```go
// Code generated by gen/genmethods.go; DO NOT EDIT.

package pkg

import "fmt"

// PrintType 型情報を標準出力する
func (s A) PrintType() {
	fmt.Printf("%T\n", s)
}

// PrintType 型情報を標準出力する
func (s B) PrintType() {
	fmt.Printf("%T\n", s)
}
```

ソースコードのフォーマット([format.Source](https://pkg.go.dev/go/format#Source))やパッケージのimportを実施する処理([imports.Process](https://pkg.go.dev/golang.org/x/tools/imports#Process))も用意されているので、必要であれば使いましょう。(サンプルリポジトリでは両方使っています)

# リンク集

* go/types関連
    * [静的解析を使った開発ツールの開発](https://www.slideshare.net/takuyaueda967/ss-86437682)
    * [go/types](https://pkg.go.dev/go/types)
    * [goでstructの定義を見てあれこれするのに、ast.Fileを直接見るよりtypes.Packageを参照したほうが楽(かもしれない)](https://pod.hatenablog.com/entry/2018/02/04/145615)
* /tools/go/packages関連
    * [go/packagesについて](https://ajalab.github.io/2018/11/03/go-packages.html)
    * [golang.org/x/tools/go/packages](https://pkg.go.dev/golang.org/x/tools/go/packages)
* コード生成関連
    * [go generate のベストプラクティス](https://qiita.com/yaegashi/items/d1fd9f7d0c75b2bb7446)