Reputation: 2524
I am looking for a way to retrieve the package(s) installed locally which contain the declaration for a given type and the default package name.
ie:
// FindPackagesForType returns the list of possible packages for a given type
func FindPackagesForType(typeName string) []string {
return []string {} // TODO: implement
}
func TestFindPackagesForType(t *testing.T) {
assert.Contains(t, FindPackagesForType("io.Reader"), "io")
assert.Contains(
t,
FindPackagesForType("types.Timestamp"),
"github.com/gogo/protobuf/types",
)
assert.Contains(
t,
FindPackagesForType("types.ContainerCreateConfig"),
"github.com/docker/docker/api/types",
)
}
I could try to retrieve all packages installed, and go through the AST in each looking for the declaration but if there is a solution which could do this more efficiently while also providing support for go modules I would like to use that.
The reason for this is to improve a code generation tool. The idea is to let the user provide the name of a type and let the tool identify the most likely candidate the same way goimports adds missing imports.
Upvotes: 2
Views: 2368
Reputation:
below program lists uses
and definitions
of a given query type
and given go package.
It is simple and straightforward to programmatically load a go program using the program loader package
package main
import (
"flag"
"fmt"
"strings"
"golang.org/x/tools/go/loader"
)
func main() {
var query string
var uses bool
var defs bool
flag.StringVar(&query, "query", "", "the fully qualified type path")
flag.BoolVar(&uses, "uses", true, "capture uses")
flag.BoolVar(&defs, "definitions", true, "capture definitions")
flag.Parse()
if query == "" {
panic("query must not be empty")
}
var queryPkg string
queryType := query
if i := strings.LastIndex(query, "."); i > -1 {
queryPkg = query[:i]
queryType = query[i+1:]
}
var conf loader.Config
_, err := conf.FromArgs(flag.Args(), false)
if err != nil {
panic(err)
}
prog, err := conf.Load()
if err != nil {
panic(err)
}
for pkgType, pkgInfo := range prog.AllPackages {
if queryPkg != "" {
if !strings.HasPrefix(pkgType.Path(), queryPkg) {
continue
}
}
if defs {
for typeInfo, ident := range pkgInfo.Defs {
if !strings.HasPrefix(typeInfo.Name, queryType) {
continue
}
f := prog.Fset.File(ident.Pos())
fpos := f.Position(ident.Pos())
fmt.Printf("def: %v %v.%v\n", fpos, pkgType.Path(), typeInfo.Name)
}
}
if uses {
for ident, oInfo := range pkgInfo.Uses {
if !strings.Contains(oInfo.Type().String(), queryType) {
continue
}
f := prog.Fset.File(ident.Pos())
fpos := f.Position(ident.Pos())
fmt.Printf("use: %v %v\n", fpos, oInfo.Type().String())
}
}
// -
}
}
then you run it like this
$ go run main.go -query="io.Reader" io
def: /home/mh-cbon/.gvm/gos/go1.12.7/src/io/io.go:170:6 io.ReaderFrom
def: /home/mh-cbon/.gvm/gos/go1.12.7/src/io/io.go:77:6 io.Reader
def: /home/mh-cbon/.gvm/gos/go1.12.7/src/io/io.go:211:6 io.ReaderAt
use: /home/mh-cbon/.gvm/gos/go1.12.7/src/io/multi.go:20:13 []io.Reader
use: /home/mh-cbon/.gvm/gos/go1.12.7/src/io/multi.go:21:16 *io.multiReader
# a ton of output...
[mh-cbon@Host-001 ploader] $ go run main.go -query="Config" io
[mh-cbon@Host-001 ploader] $ go run main.go -query="io.Reader" -uses=false io
def: /home/mh-cbon/.gvm/gos/go1.12.7/src/io/io.go:170:6 io.ReaderFrom
def: /home/mh-cbon/.gvm/gos/go1.12.7/src/io/io.go:211:6 io.ReaderAt
def: /home/mh-cbon/.gvm/gos/go1.12.7/src/io/io.go:77:6 io.Reader
you probably got to improve the matcher engine to make it moresuitable.
Upvotes: 0
Reputation: 11502
You can use reflect.TypeOf(any).PkgPath()
to get the package path of certain type. However we need to pass an object with desired type (not string like you wanted).
package main
import (
"bytes"
"fmt"
"reflect"
"gopkg.in/mgo.v2/bson"
)
func main() {
var a bytes.Buffer
fmt.Println(FindPackagesForType(a)) // output: bytes
var b bson.M
fmt.Println(FindPackagesForType(b)) // output: gopkg.in/mgo.v2/bson
}
func FindPackagesForType(any interface{}) string {
return reflect.TypeOf(any).PkgPath()
}
Upvotes: 2