0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-03-05

はじめに

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

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

また、今回説明する内容を実装したサンプルリポジトリも作成しましたので、必要であればそちらも参照してください。

コードを解析

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

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

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

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から取得することができるので、こちら経由で取得します。

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

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

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パッケージを直接指定しましたが、他にも様々な指定方法があるようです。

コードを生成

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


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 }}
`

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

// 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)やパッケージのimportを実施する処理(imports.Process)も用意されているので、必要であれば使いましょう。(サンプルリポジトリでは両方使っています)

リンク集

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?