*ast.File からファイル名を取得したい
goのファイルのimportなどの情報を取得するのに、直接 go/* のpackageを使うよりも golang.org/x/tools/go/loader を使うのが手軽。
pkgname := "golang.org/x/tools/refactor/rename" c := loader.Config{} c.Import(pkgname) prog, _ := c.Load() // これでrename packageに関する情報がいい感じに取得できる pkginfo := prog.Package(pkgname)
(defaultではフルでastがparseされ、型チェックも行われる)一方でこのloader packageで取得した結果はpackage単位の情報になってしまうので、個々のast.Fileのファイル名と対応付けるのに面倒さを感じていた。
以下のようなPackageInfoという型の値が返ってくる。
// PackageInfo holds the ASTs and facts derived by the type-checker // for a single package. // // Not mutated once exposed via the API. // type PackageInfo struct { Pkg *types.Package Importable bool // true if 'import "Pkg.Path()"' would resolve to this TransitivelyErrorFree bool // true if Pkg and all its dependencies are free of errors Files []*ast.File // syntax trees for the package's files Errors []error // non-nil if the package had errors types.Info // type-checker deductions. dir string // package directory checker *types.Checker // transient type-checker state errorFunc func(error) }
*ast.File の値からそのASTの元のソースコードのファイル名を取得したい。ここで *ast.File のNameはpackage名なことに注意。
*token.File からファイル名は取得できる
*ast.File 自身がファイル名を持っていなくてもどこか別の場所にマッピングが保持されていれば取得できる。具体的には、*token.File からファイル名を取得できる。 *token.File はfsetから取れる(astなどをいじる時によく使う *token.FileSet のこと)。
pkginfo := prog.Package(pkgname)
fset := prog.Fset
// 先頭のファイル名
fset.File(pkginfo.Files[0].Pos()).Name()
// 末尾のファイル名
fset.File(pkginfo.Files[len(pkginfo.Files)-1].Pos()).Name()
動作する完全なgoのコードの例
動作する完全なgoのコードの例は以下。
package main import ( "fmt" "go/parser" "log" "os/user" "strings" "golang.org/x/tools/go/loader" ) func p(path string) { u, _ := user.Current() fmt.Println(strings.Replace(path, u.HomeDir, "~", 1)) } func main() { pkgname := "golang.org/x/tools/refactor/rename" c := loader.Config{ ParserMode: parser.PackageClauseOnly, } c.Import(pkgname) prog, err := c.Load() if err != nil { log.Fatal(err) } pkginfo := prog.Package(pkgname) fset := prog.Fset p(fset.File(pkginfo.Files[0].Pos()).Name()) p(fset.File(pkginfo.Files[len(pkginfo.Files)-1].Pos()).Name()) }
結果
~/go/src/golang.org/x/tools/refactor/rename/check.go ~/go/src/golang.org/x/tools/refactor/rename/util.go