gopls/internal/golang: Definitions: support renaming imports in doc links This CL adds support for jumping to the definition of a doc link when the import is renamed. Before, the doc link had to use the local (renamed) name, which is unnatural; now, it can use either the local name or the package's declared name. + test Updates golang/go#61677 Change-Id: Ibbe18ab1527800c41900d42781677ad892b55cd4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/612045 Auto-Submit: Alan Donovan <adonovan@google.com> Reviewed-by: Robert Findley <rfindley@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Commit-Queue: Alan Donovan <adonovan@google.com>
diff --git a/gopls/internal/golang/comment.go b/gopls/internal/golang/comment.go index dc8c1c8..3a0d815 100644 --- a/gopls/internal/golang/comment.go +++ b/gopls/internal/golang/comment.go
@@ -111,7 +111,7 @@ for _, idx := range docLinkRegex.FindAllStringSubmatchIndex(text, -1) { // The [idx[2], idx[3]) identifies the first submatch, - // which is the reference name in the doc link. + // which is the reference name in the doc link (sans '*'). // e.g. The "[fmt.Println]" reference name is "fmt.Println". if !(idx[2] <= lineOffset && lineOffset < idx[3]) { continue @@ -126,7 +126,7 @@ name = name[:i] i = strings.LastIndexByte(name, '.') } - obj := lookupObjectByName(pkg, pgf, name) + obj := lookupDocLinkSymbol(pkg, pgf, name) if obj == nil { return nil, protocol.Range{}, errNoCommentReference } @@ -141,19 +141,42 @@ return nil, protocol.Range{}, errNoCommentReference } -func lookupObjectByName(pkg *cache.Package, pgf *parsego.File, name string) types.Object { +// lookupDocLinkSymbol returns the symbol denoted by a doc link such +// as "fmt.Println" or "bytes.Buffer.Write" in the specified file. +func lookupDocLinkSymbol(pkg *cache.Package, pgf *parsego.File, name string) types.Object { scope := pkg.Types().Scope() + + prefix, suffix, _ := strings.Cut(name, ".") + + // Try treating the prefix as a package name, + // allowing for non-renaming and renaming imports. fileScope := pkg.TypesInfo().Scopes[pgf.File] - pkgName, suffix, _ := strings.Cut(name, ".") - obj, ok := fileScope.Lookup(pkgName).(*types.PkgName) - if ok { - scope = obj.Imported().Scope() + pkgname, ok := fileScope.Lookup(prefix).(*types.PkgName) // ok => prefix is imported name + if !ok { + // Handle renaming import, e.g. + // [path.Join] after import pathpkg "path". + // (Should we look at all files of the package?) + for _, imp := range pgf.File.Imports { + pkgname2 := pkg.TypesInfo().PkgNameOf(imp) + if pkgname2 != nil && pkgname2.Imported().Name() == prefix { + pkgname = pkgname2 + break + } + } + } + if pkgname != nil { + scope = pkgname.Imported().Scope() if suffix == "" { - return obj + return pkgname // not really a valid doc link } name = suffix } + // TODO(adonovan): try searching the forward closure for packages + // that define the symbol but are not directly imported; + // see https://github.com/golang/go/issues/61677 + + // Type.Method? recv, method, ok := strings.Cut(name, ".") if ok { obj, ok := scope.Lookup(recv).(*types.TypeName) @@ -173,5 +196,6 @@ return nil } + // package-level symbol return scope.Lookup(name) }
diff --git a/gopls/internal/test/marker/testdata/definition/comment.txt b/gopls/internal/test/marker/testdata/definition/comment.txt index ac253b2..39c8607 100644 --- a/gopls/internal/test/marker/testdata/definition/comment.txt +++ b/gopls/internal/test/marker/testdata/definition/comment.txt
@@ -5,10 +5,16 @@ go 1.19 +-- path/path.go -- +package path + +func Join() //@loc(Join, "Join") + -- a.go -- package p import "strconv" //@loc(strconv, `"strconv"`) +import pathpkg "mod.com/path" const NumberBase = 10 //@loc(NumberBase, "NumberBase") @@ -19,3 +25,10 @@ i, _ := strconv.ParseInt(s, NumberBase, 64) return int(i) } + +// The declared and imported names of the package both work: +// [path.Join] //@ def("Join", Join) +// [pathpkg.Join] //@ def("Join", Join) +func _() { + pathpkg.Join() +}