如何以编程方式检索声明给定类型的包路径?

时间:2019-12-18 13:41:16

标签: go gofmt goimports

我正在寻找一种方法来检索本地安装的软件包,这些软件包包含给定类型的声明和默认软件包名称。

即:

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

我可以尝试检索所有已安装的软件包,并在每个AST中进行查找声明,但是如果有解决方案可以更有效地执行此操作,同时还为我想使用的go模块提供支持。 / p>

这样做的原因是为了改进代码生成工具。想法是让用户提供类型的名称,并让该工具识别最有可能的候选者,就像goimports添加缺失的导入一样。

2 个答案:

答案 0 :(得分:1)

您可以使用reflect.TypeOf(any).PkgPath()来获取某种类型的包装路径。但是,我们需要传递具有所需类型的对象(而不是您想要的字符串)。

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

答案 1 :(得分:0)

在程序下方列出了给定查询uses和给定go包的definitionstype

使用program loader package

以编程方式加载go程序非常简单明了
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())
            }
        }
        // -
    }
}

然后您以这种方式运行

$ 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

您可能需要改进匹配器引擎以使其更适合。