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: