🎥

Goでtoken.Posから行数を読み取りたい人生だった

2021/08/12に公開

事の経緯

かなり薄い内容で申し訳ないです。
ファイルをWalkしたついでにファイル内のコメントとその行数を構造体に入れようとしたときに少し手こずったので記録しておきます。

困ったところ

hogehoge.go
...
type FilePos struct {
	Start int
	End int
}
type Fugafuga struct {
	FileName string
	FilePos
}
// filepath.Walkの第二引数にとる関数です
func exec(path string, info os.FileInfo, err error) error {
	if err != nil {
		return err
	}
	if info.IsDir() || filepath.Ext(path) != ".go" {
		return nil
	}
	...(中略)...
	// fsetの方がおしゃれ感あるなと思ったのですが、Golandに脅されてfSetにしました
	fSet := token.NewFileSet()
	pf, err := parser.ParseFile(fSet, path, nil, parser.ParseComments)
	if err != nil {
		return err
	}
	cMap := ast.NewCommentMap(fSet, pf, pf.Comments)
	for _, c := range cMap.Comments() {
		// Fugafuga構造体を埋める
		fugafuga := Fugafuga{
			FileName: path,
			FilePos: FilePos{
				Start: 0,
				End: 0, // ここに行数入れたい
			},
		}
	}
}

func hoge(fp string) error {
	return filepath.Walk(fp, exec)
}

...

こんな感じのコードを書いていました。
ファイルを見ていって、ファイル毎にコメントの情報を構造体に詰める、というようなものです。
入れたい情報はファイル名と、そのコメントのある行数です。

最初素直に

hogehoge.go
...
	for _, c := range cMap.Comments() {
		// Fugafuga構造体を埋める
		fugafuga := Fugafuga{
			FileName: path,
			FilePos: FilePos{
				Start: int(c.Pos()), // こんな感じかなー
				End: int(c.End()), // こんな感じかなー2
			},
		}
	}
...

こんな感じでc.Pos()をしたところ
なんか、かなり大きい数字になってる、、、
となってうまくいきませんでした。

解決方法

結果的には以下のようにすると解決することができました

hogehoge.go
...
	// fsetの方がおしゃれ感あるなと思ったのですが、Golandに脅されてfSetにしました
	fSet := token.NewFileSet()
	pf, err := parser.ParseFile(fSet, path, nil, parser.ParseComments)
	if err != nil {
		return err
	}
	cMap := ast.NewCommentMap(fSet, pf, pf.Comments)
	for _, c := range cMap.Comments() {
		// ここら辺に注目
		ff := fSet.File(c.Pos())
		lineStart := ff.Line(c.Pos())
		lineEnd := ff.Line(c.End())
		fugafuga := Fugafuga{
			FileName: path,
			FilePos: FilePos{
				Start: lineStart,
				End:   lineEnd,
			},
		}
	}
...

何をしたかというと
fSetとc.Pos()を生贄に
*token.File型のffを生成したのです
すると、なんということでしょう、*token.File型にはLineメソッドというものがありまして、これにc.Pos()やc.End()といったtoken.Posを食わせると、ファイル内の行数として返ってくるのです。

hogehoge.go
...
	for _, c := range cMap.Comments() {
		// *token.File召喚!
		ff := fSet.File(c.Pos())
		// *File.Line 求めていたものがここにあった
		line := ff.Line(c.Pos())
		endLine := ff.Line(c.End())
		...
...

なんだか他にも良い方法がありそうな気がしているのですが、見つかったら追記しようかなと思っています。
ちなみに俺環かもしれないので引き続き検証したいと思います

Discussion