skip to Main Content

Is there a way to search (aka "grep") some Go code, and show all functions/methods which return a struct called "FooBar"?

A command-line tool would be fine, or a way to do this in vscode.

In vscode, if I use "Go to References" I see the methods of this struct, too (which I don’t want to see)

Update: I know how to use grep via the vscode terminal. But using grep or rg for this task is error-prone. I am looking for an AST-based solution.

2

Answers


  1. You can try to use callgraph and grep combination to parse go code and find required returning value in functions signature.

    Install::

    go install golang.org/x/tools/cmd/callgraph@latest
    

    Example:

    ✳️ project structure

    .
    ├── main.go
    ├── go.mod
    ├── service
    │   └── service.go
    └── types
        └── type.go
    

    ✳️ service.go

    package service
    
    import "github.com/some/project/types"
    
    func ReturnX() types.X {
        return types.X{}
    }
    
    type Service struct {
    }
    
    func (Service) ReturnX() types.X {
        return types.X{}
    }
    
    func (Service) ReturnPtrX() *types.X {
        return &types.X{}
    }
    

    ✳️ type.go

    package types
    
    type X struct {
    }
    
    

    ✳️ main.go

    package main
    
    import (
        "github.com/some/project/service"
    )
    
    func main() {
        A()
        s := service.Service{}
    
        s.ReturnX()
        s.ReturnPtrX()
    }
    
    func A() string {
        service.ReturnX()
        return ""
    }
    
    
    % callgraph -format '{{.Caller}}--{{.Dynamic}}-{{.Line}}:{{.Column}}-->{{.Callee}} - {{.Callee.Signature.Results}}}' main.go | grep types.X
    command-line-arguments.A--static-16:17-->github.com/some/project/service.ReturnX - (github.com/some/project/types.X)}
    command-line-arguments.main--static-11:11-->(github.com/some/project/service.Service).ReturnX - (github.com/some/project/types.X)}
    command-line-arguments.main--static-12:14-->(github.com/some/project/service.Service).ReturnPtrX - (*github.com/some/project/types.X)}
    
    

    to configure format look at the source. For example .Callee is ssa.Function type -> you can use text/template syntax to reach it value ({{.Callee.Signature.Results}}})

    Login or Signup to reply.
  2. Summary:

    • Loads the active directory.
    • Skips any node that is not in the path:
      Package > File > FuncDecl
    • Traverses the AST with DFS.
    • Prints the names of found functions.
    • Can work with variations of FooBar, such as *FooBar, []FooBar, map[FooBar]any
    
    func recursivelySearchForIdent(node ast.Node, identName string) bool {
        matchFound := false
        ast.Inspect(node, func(n ast.Node) bool {
            if n == nil {
                return false
            }
            if n, ok := n.(*ast.Ident); ok {
                if n.Name == identName {
                    matchFound = true
                }
            }
            return true
        })
        return matchFound
    }
    
    func main() {
        packageName := "my_package"
        structName := "FooBar"
    
        pkgs, err := parser.ParseDir(token.NewFileSet(), ".", nil, parser.AllErrors)
        if err != nil {
            log.Println(errors.Wrap(err, "failed to load directory"))
        }
    
        if _, ok := pkgs[packageName]; !ok {
            log.Fatalln("failed to load package")
        }
        pkg := pkgs[packageName]
    
        selectedFuncDecls := []*ast.FuncDecl{}
        ast.Inspect(pkg, func(n ast.Node) bool {
            if n == nil {
                return false
            }
    
            switch n := n.(type) {
            case *ast.Package, *ast.File:
                return true
            case *ast.FuncDecl:
                if n.Type.Results != nil {
                    if recursivelySearchForIdent(n.Type.Results, structName) {
                        selectedFuncDecls = append(selectedFuncDecls, n)
                    }
                }
            }
    
            return false
        })
        for _, funcDecl := range selectedFuncDecls {
            fmt.Println(funcDecl.Name)
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search