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