Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Go code "grep": show me all functions which return a struct called FooBar

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.

like image 237
guettli Avatar asked Oct 24 '25 08:10

guettli


2 Answers

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}}})

like image 132
kozmo Avatar answered Oct 27 '25 01:10

kozmo


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)
    }
}
like image 24
ufukty Avatar answered Oct 26 '25 23:10

ufukty