Reputation: 374
I have used go/parser to parse a golang file and examine it's AST. I have a specific problem for which I want to use go/parser but I hit a roadblock.
Consider that the following files are present in GOPATH/src
$GOPATH/src/
example.go
example_package/
example_package.go
The following are the contents of the files above
example.go
package main
import (
"example_package"
)
type MyObject struct {
base *example_package.BaseObject
}
func DoMyThing(arg *example_package.FirstArg) {
arg.Write(10)
}
func DoMyAnotherThing() {
}
func main() {
example_package.GetItStarted(&MyObject{})
}
example_package.go
package example_package
func GetItStarted(obj interface{}) {
}
type FirstArg interface {
Read() int
Write(x int)
}
type BaseObject struct {
}
func (p *BaseObject) DoSomething(arg *FirstArg, a int) {
arg.Write(arg.Read() + a)
}
My intention is to write a go program called gen_structure
that is used like this
$ gen_structure example.go
The output would be
> MyObject
- DoMyThing(arg)
- base
- DoSomething(arg, a)
What did gen_structure do?
It parses example.go and
example_package.GetItStarted(&MyObject{})
from inside the main() function.MyObject
that have atleast one argument with the first one being of type *package_example.FirstArg
. It finds DoMyThing
(and ignored DoMyAnotherThing
).base
and peeks inside (by opening the example_package
).DoSomething
I understand I can parse a single file or a bunch of files in the same directory using the functionality within go/parser
. However, I am unable to figure out how to resolve symbols across packages (In this case, example_package
).
How do I do this?
Upvotes: 3
Views: 2368
Reputation: 121049
Call ast.NewPackage to resolve a package names. You will need to supply an importer that returns an *ast.Object for the given import path. If all you want to do is resolve the name to a path, the importer can simply return an *ast.Object with the Kind set to ast.Pkg and the Name set to name of the package. Most of the heavy lifting in the importer can be done with the go/build package. If want to resolve do the AST for the target package, you will need to parse the package and return the ast.Object for the package. To prevent loading the same package multiple times, use the map argument to the importer as a cache of previously loaded packages.
Here's some untested code for finding the resolved package path from the *ast.SelectorExpr
se
:
if x, _ := se.X.(*ast.Ident); x != nil {
if obj := x.Obj; obj != nil && obj.Kind == ast.Pkg {
if spec, _ := obj.Decl.(*ast.ImportSpec); spec != nil {
if path, err := strconv.Unquote(spec.Path.Value); err == nil {
// path is resolved path for selector expression se.
}
}
}
}
The go/types package can also be used to get this information and more. I recommend using go/types instead of using go/ast directly.
Upvotes: 5