The following method extracts all public method calls from the AST of a file. I need to find out the full package from the CallExpr, for example: ast.Inspect() is imported from "go/ast". I want to match the list of pkgsInclude strings with the imported package name:
func functionCalls(path string, node *ast.File, pkgsInclude []string) int {
fCalls := 0
ast.Inspect(node, func(n ast.Node) bool {
switch fCall := n.(type) {
case *ast.CallExpr:
if fun, ok := fCall.Fun.(*ast.SelectorExpr); ok {
if fun.Sel.IsExported() {
fCalls += 1
}
}
}
return true
})
return fCalls
}
To get fully qualified names, the code has to be type checked with the go/types package.
The go/types article by Alan Donovan goes into great detail on how to use the type checker properly, but here is the gist of it. I left a few type assertions in the Visit method for brevity. In production code you shouldn't assume specific node types.
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
)
// code to parse. It includes two variants of calling a package function.
var code = `package main
import (
foo "io/ioutil"
. "io/ioutil"
)
func main() {
foo.ReadFile("")
ReadFile("")
}
`
func main() {
fset := &token.FileSet{}
f, err := parser.ParseFile(fset, "", code, 0)
if err != nil {
log.Fatal(err)
}
// info.Uses allows to lookup import paths for identifiers.
info := &types.Info{
Uses: make(map[*ast.Ident]types.Object),
}
// Type check the parsed code using the default importer.
// Use golang.org/x/tools/go/loader to check a program
// consisting of multiple packages.
conf := types.Config{Importer: importer.Default()}
pkg, err := conf.Check("main", fset, []*ast.File{f}, info)
if err != nil {
log.Fatal(err)
}
// Do something with ast, info, and possibly pkg
var _ = pkg
ast.Walk(v{info}, f)
}
type v struct {
info *types.Info
}
func (v v) Visit(node ast.Node) (w ast.Visitor) {
switch node := node.(type) {
case *ast.CallExpr:
// Get some kind of *ast.Ident for the CallExpr that represents the
// package. Then we can look it up in v.info. Where exactly it sits in
// the ast depends on the form of the function call.
switch node := node.Fun.(type) {
case *ast.SelectorExpr: // foo.ReadFile
pkgID := node.X.(*ast.Ident)
fmt.Println(v.info.Uses[pkgID].(*types.PkgName).Imported().Path())
case *ast.Ident: // ReadFile
pkgID := node
fmt.Println(v.info.Uses[pkgID].Pkg().Path())
}
}
return v
}
// Output:
// io/ioutil
// io/ioutil
Related
I am using protobuf to generate a lot of struct, and I want to use gorm.io to Db.AutoMigrate all the struct in the generate package into the database. But the function func (*gorm.DB).AutoMigrate(dst ...interface{}) error can only accept variadic struct instance as its parameters. The following code can retrieved all the name of structs is string format.
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
// src is the input for which we want to inspect the AST.
src := `
package service
type Screen struct {
Width float64
Height float64
}
type Memory struct {
Unit float64
}
`
// Create the AST by parsing src.
fset := token.NewFileSet() // positions are relative to fset
f, err := parser.ParseFile(fset, "src.go", src, 0)
if err != nil {
panic(err)
}
for _, node := range f.Decls {
switch node.(type) {
case *ast.GenDecl:
genDecl := node.(*ast.GenDecl)
for _, spec := range genDecl.Specs {
switch spec.(type) {
case *ast.TypeSpec:
typeSpec := spec.(*ast.TypeSpec)
fmt.Printf("Struct: name=%s\n", typeSpec.Name.Name)
}
}
}
}
}
My Question is:
This code can got all the name of structs , but the gorm.io need initialize the struct DB.AutoMigrate(&User{}). How can I do create a empty struct that specified by the string of the name at the runtime. eg. the "Screen" ===> &Screen{}.
Is there a way to list out all functions that uses/returns a specific type?
For example: I'm interested to use the following function.
func ListenAndServe(addr string, handler Handler) error
How can I find out all functions (across all Go packages) that can return a Handler?
I'd write an analysis tool using the x/tools/go/analysis framework. Here's a rough sketch that you can run on any module (it uses go/packages underneath so it fully supports modules):
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/token"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/singlechecker"
)
var RtAnalysis = &analysis.Analyzer{
Name: "rtanalysis",
Doc: "finds functions by return type",
Run: run,
}
func main() {
singlechecker.Main(RtAnalysis)
}
func run(pass *analysis.Pass) (interface{}, error) {
for _, file := range pass.Files {
ast.Inspect(file, func(n ast.Node) bool {
if funcTy, ok := n.(*ast.FuncType); ok {
if funcTy.Results != nil {
for _, fl := range funcTy.Results.List {
if tv, ok := pass.TypesInfo.Types[fl.Type]; ok {
if tv.Type.String() == "net/http.Handler" {
ns := nodeString(funcTy, pass.Fset)
fmt.Printf("%s has return of type net/http.Handler\n", ns)
}
}
}
}
}
return true
})
}
return nil, nil
}
// nodeString formats a syntax tree in the style of gofmt.
func nodeString(n ast.Node, fset *token.FileSet) string {
var buf bytes.Buffer
format.Node(&buf, fset, n)
return buf.String()
}
I'm trying to get at the Docs and Comments of structs and struct fields, but I can't seem to be able to do so, they just turn up empty:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
src := `package test
// Hello
type A struct {
// Where
B int // Are you
}
`
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "", src, 0)
if err != nil {
panic(err)
}
ast.Inspect(f, func(n ast.Node) bool {
switch t := n.(type) {
case *ast.TypeSpec:
fmt.Println(t.Doc.Text())
case *ast.StructType:
for _, field := range t.Fields.List {
fmt.Println(field.Doc.Text())
fmt.Println(field.Comment.Text())
}
}
return true
})
}
yields three blank lines: https://play.golang.org/p/4Eh9gS-PUg
Saw the similar question Go parser not detecting Doc comments on struct type but when trying to run the accepted example it turns up all empty — so I'm wondering if something has changed since that version.
In order to get comments, you have to pass the parser.ParseComments flag in argument to parser.ParseFile():
parser.ParseFile(fset, "", src, parser.ParseComments)
All possible mode flags are documented here:
https://golang.org/pkg/go/parser/#Mode
Here is an example:
package main
type State int
const (
Created State = iota
Modified
Deleted
)
func main() {
// Some code here where I need the list
// of all available constants of this type.
}
The use case for this is to create a Finite State Machine (FSM). Being able to get all constants will help me in writing a test case that will ensure that every new value has a corresponding entry in the FSM map.
If your constants are all in an order, you can use this:
type T int
const (
TA T = iota
TB
TC
NumT
)
func AllTs() []T {
ts := make([]T, NumT)
for i := 0; i < int(NumT); i++ {
ts[i] = T(i)
}
return ts
}
You can also cache the output in e.g. init(). This will only work when all constants are initialised with iota in order. If you need something that works for all cases, use an explicit slice.
There is no way to do this at runtime, as the reflect package cannot be used for it. You could define a list:
const(
Created State = iota
Modified
Deleted
)
var allStates = []State{Created, Modified, Deleted}
You may go further and add in a string representation, or any number of other things.
You may be able to generate such a list from the source to make maintenance easier, but I generally don't think that saves enough time to be worth it. There are tools like stringer that can already do some of that.
Since you talking about a test-case I assume you have the type available as well as the file where the constants are defined in. I used for a similar problem the following approach (go playground):
package main
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"log"
"strconv"
"strings"
)
type InterestingType uint64
const const_go = `
package p
type InterestingType uint64
const (
A InterestingType = iota << 1
B
C
)
type UninterestingType int
const (
D UninterestingType = iota
E
)
`
func main() {
constantValues := []InterestingType{}
ConstantsOf("InterestingType", const_go, func(v string) {
value, err := strconv.ParseUint(v, 0, 64)
if err != nil {
log.Fatal(err)
}
constantValues = append(
constantValues, InterestingType(value))
})
fmt.Printf("%#v\n", constantValues)
}
func ConstantsOf(ctype string, file string, value func(string)) {
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "const.go", file, 0)
if err != nil {
log.Fatal(err)
}
// Obtain type information.
conf := types.Config{Importer: importer.Default()}
info := &types.Info{
Defs: make(map[*ast.Ident]types.Object),
}
_, err = conf.Check("p", fset, []*ast.File{f}, info)
if err != nil {
log.Fatal(err)
}
for _, d := range f.Decls {
for _, s := range d.(*ast.GenDecl).Specs {
v, ok := s.(*ast.ValueSpec)
if !ok {
continue
}
for _, name := range v.Names {
c := info.ObjectOf(name).(*types.Const)
if strings.HasSuffix(c.Type().String(), ctype) {
value(c.Val().ExactString())
}
}
}
}
}
package main
import (
"fmt"
)
type State int
const (
Created State = iota
Modified
Deleted
)
func (s State) Name() (name string) {
switch s {
case Created:
name = "created"
case Modified:
name = "modified"
case Deleted:
name = "deleted"
}
return
}
func main() {
states := States()
fmt.Println(states)
}
func States() (states []State) {
state := State(0)
for {
name := state.Name()
if name == "" {
break
}
states = append(states, state)
state++
}
return
}
I am trying to parse go source files that contain interfaces and find the interfaces defined methods / signatures. I am using ast to parse the file. I am able to get some high level declarations, such as *ast.GenDecl, but I am not able to get to the next level of determining if this type is an interface and what its methods are.
This is for a scaffolding type problem I am trying to solve where a user defines the interface for a service and a tool will build out the skeleton of the service
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"reflect"
)
func main() {
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "/tmp/tmp.go", `package service
type ServiceInterface interface {
Create(NewServiceRequest) (JsonResponse, error)
Delete(DelServiceRequest) (JsonResponse, error)
}`, 0)
for _, v := range f.Decls {
switch t := v.(type) {
case *ast.FuncDecl:
fmt.Println("func ", t.Name.Name)
case *ast.GenDecl:
switch x := t.Specs[0].(type) {
default:
fmt.Println(x, reflect.TypeOf(x))
}
default:
fmt.Printf("skipping %t\n", t)
}
}
}
Results in but I cant seem to find anything about the internals of interface declaration at all.
&{<nil> ServiceInterface 0x8202d8260 <nil>} *ast.TypeSpec
When working with the AST, I find it helpful to dump an example using the spew package:
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "/tmp/tmp.go", `package service ....`)
spew.Dump(f)
I find it easy to write the required code from the spew output.
Here's some code to get you started. It prints interface and method names:
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
)
func main() {
fset := token.NewFileSet()
f, _ := parser.ParseFile(fset, "/tmp/tmp.go", `package service
type ServiceInterface interface {
Create(NewServiceRequest) (JsonResponse, error)
Delete(DelServiceRequest) (JsonResponse, error)
}`, 0)
for _, x := range f.Decls {
if x, ok := x.(*ast.GenDecl); ok {
if x.Tok != token.TYPE {
continue
}
for _, x := range x.Specs {
if x, ok := x.(*ast.TypeSpec); ok {
iname := x.Name
if x, ok := x.Type.(*ast.InterfaceType); ok {
for _, x := range x.Methods.List {
if len(x.Names) == 0 {
continue
}
mname := x.Names[0].Name
fmt.Println("interface:", iname, "method:", mname)
}
}
}
}
}
}
}
http://play.golang.org/p/eNyB7O6FIc