Prashanth Ellina
Prashanth Ellina

Reputation: 374

Usage of go/parser across packages

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

  1. Extracts "MyObject" from the line example_package.GetItStarted(&MyObject{}) from inside the main() function.
  2. Looks for methods on MyObject that have atleast one argument with the first one being of type *package_example.FirstArg. It finds DoMyThing (and ignored DoMyAnotherThing).
  3. Identifies the member base and peeks inside (by opening the example_package).
  4. Applies the same process to find methods as above and finds DoSomething
  5. Using the collected information, it prints the required output.

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

Answers (1)

Thundercat
Thundercat

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

Related Questions