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>
This commit is contained in:
Dylan Le 2022-04-18 13:59:34 -04:00
parent fa7afc95f2
commit 559469ae16
2 changed files with 97 additions and 12 deletions

View File

@ -5,6 +5,7 @@
package misc package misc
import ( import (
"fmt"
"strings" "strings"
"testing" "testing"
@ -140,3 +141,83 @@ package main
env.Editor.Hover(env.Ctx, "main.go", env.RegexpSearch("main.go", "foo")) 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)
}
})
}

View File

@ -510,21 +510,25 @@ func FindHoverContext(ctx context.Context, s Snapshot, pkg Package, obj types.Ob
} }
case *ast.ImportSpec: case *ast.ImportSpec:
// Try to find the package documentation for an imported package. // Try to find the package documentation for an imported package.
if pkgName, ok := obj.(*types.PkgName); ok { pkgPath, err := strconv.Unquote(node.Path.Value)
imp, err := pkg.GetImport(pkgName.Imported().Path()) if err != nil {
if err != nil { return nil, err
return nil, err }
} imp, err := pkg.GetImport(pkgPath)
// Assume that only one file will contain package documentation, if err != nil {
// so pick the first file that has a doc comment. return nil, err
for _, file := range imp.GetSyntax() { }
if file.Doc != nil { // Assume that only one file will contain package documentation,
info = &HoverContext{signatureSource: obj, Comment: file.Doc} // so pick the first file that has a doc comment.
break 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: case *ast.GenDecl:
switch obj := obj.(type) { switch obj := obj.(type) {
case *types.TypeName, *types.Var, *types.Const, *types.Func: case *types.TypeName, *types.Var, *types.Const, *types.Func: