internal/lsp: render package documentation when hovering over a package import

Update FindHoverContext to retrieve & render package documentation for a hovered package. Add a regtest for this hovering feature.

Updates golang/go#51848

Change-Id: If57396d59be9c4cf7e09b64e39832de6f996c7ca
Reviewed-on: https://go-review.googlesource.com/c/tools/+/400820
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Dylan Le <dungtuanle@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/regtest/misc/hover_test.go b/gopls/internal/regtest/misc/hover_test.go
index 04dc740..4701b07 100644
--- a/gopls/internal/regtest/misc/hover_test.go
+++ b/gopls/internal/regtest/misc/hover_test.go
@@ -5,6 +5,7 @@
 package misc
 
 import (
+	"fmt"
 	"strings"
 	"testing"
 
@@ -140,3 +141,83 @@
 		env.Editor.Hover(env.Ctx, "main.go", env.RegexpSearch("main.go", "foo"))
 	})
 }
+
+func TestHoverImport(t *testing.T) {
+	// For Go.13 and earlier versions, Go will try to download imported but missing packages. This behavior breaks the
+	// workspace as Go fails to download non-existent package "mod.com/lib4"
+	testenv.NeedsGo1Point(t, 14)
+	const packageDoc1 = "Package lib1 hover documentation"
+	const packageDoc2 = "Package lib2 hover documentation"
+	tests := []struct {
+		hoverPackage string
+		want         string
+	}{
+		{
+			"mod.com/lib1",
+			packageDoc1,
+		},
+		{
+			"mod.com/lib2",
+			packageDoc2,
+		},
+		{
+			"mod.com/lib3",
+			"",
+		},
+	}
+	source := fmt.Sprintf(`
+-- go.mod --
+module mod.com
+
+go 1.12
+-- lib1/a.go --
+// %s
+package lib1
+
+const C = 1
+
+-- lib1/b.go --
+package lib1
+
+const D = 1
+
+-- lib2/a.go --
+// %s
+package lib2
+
+const E = 1
+
+-- lib3/a.go --
+package lib3
+
+const F = 1
+
+-- main.go --
+package main
+
+import (
+	"mod.com/lib1"
+	"mod.com/lib2"
+	"mod.com/lib3"
+	"mod.com/lib4"
+)
+
+func main() {
+	println("Hello")
+}
+	`, packageDoc1, packageDoc2)
+	Run(t, source, func(t *testing.T, env *Env) {
+		env.OpenFile("main.go")
+		for _, test := range tests {
+			got, _ := env.Hover("main.go", env.RegexpSearch("main.go", test.hoverPackage))
+			if !strings.Contains(got.Value, test.want) {
+				t.Errorf("Hover: got:\n%q\nwant:\n%q", got.Value, test.want)
+			}
+		}
+
+		got, _ := env.Hover("main.go", env.RegexpSearch("main.go", "mod.com/lib4"))
+		if got != nil {
+			t.Errorf("Hover: got:\n%q\nwant:\n%v", got.Value, nil)
+		}
+	})
+}
diff --git a/internal/lsp/source/hover.go b/internal/lsp/source/hover.go
index 71a159c..34c80ab 100644
--- a/internal/lsp/source/hover.go
+++ b/internal/lsp/source/hover.go
@@ -510,21 +510,25 @@
 		}
 	case *ast.ImportSpec:
 		// Try to find the package documentation for an imported package.
-		if pkgName, ok := obj.(*types.PkgName); ok {
-			imp, err := pkg.GetImport(pkgName.Imported().Path())
-			if err != nil {
-				return nil, err
-			}
-			// Assume that only one file will contain package documentation,
-			// so pick the first file that has a doc comment.
-			for _, file := range imp.GetSyntax() {
-				if file.Doc != nil {
-					info = &HoverContext{signatureSource: obj, Comment: file.Doc}
-					break
+		pkgPath, err := strconv.Unquote(node.Path.Value)
+		if err != nil {
+			return nil, err
+		}
+		imp, err := pkg.GetImport(pkgPath)
+		if err != nil {
+			return nil, err
+		}
+		// Assume that only one file will contain package documentation,
+		// so pick the first file that has a doc comment.
+		for _, file := range imp.GetSyntax() {
+			if file.Doc != nil {
+				info = &HoverContext{Comment: file.Doc}
+				if file.Name != nil {
+					info.signatureSource = "package " + file.Name.Name
 				}
+				break
 			}
 		}
-		info = &HoverContext{signatureSource: node}
 	case *ast.GenDecl:
 		switch obj := obj.(type) {
 		case *types.TypeName, *types.Var, *types.Const, *types.Func: