internal/lsp/source: re-parse if needed when collecting identifier info

With the new ParseExported logic, we can lose some unexported fields on
exported structs. This can lead to misleading or malformatted hover
information.

Fix this by ensuring we always extract the Spec from a full parse. Since
this path is only hit via user-initiated requests (and should only be
hit ~once per request), it is preferable to do the parse on-demand
rather than parse via the cache and risk pinning the full AST for the
remaining duration of the session.

For golang/go#46158

Change-Id: Ib3eb61c3f75e16199eb492e3e129ba875bd8553e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/320550
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/gopls/internal/regtest/misc/hover_test.go b/gopls/internal/regtest/misc/hover_test.go
new file mode 100644
index 0000000..7a361f9
--- /dev/null
+++ b/gopls/internal/regtest/misc/hover_test.go
@@ -0,0 +1,58 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package misc
+
+import (
+	"strings"
+	"testing"
+
+	. "golang.org/x/tools/internal/lsp/regtest"
+)
+
+func TestHoverUnexported(t *testing.T) {
+	const proxy = `
+-- golang.org/x/structs@v1.0.0/go.mod --
+module golang.org/x/structs
+
+go 1.12
+
+-- golang.org/x/structs@v1.0.0/types.go --
+package structs
+
+type Mixed struct {
+	Exported   int
+	unexported string
+}
+`
+	const mod = `
+-- go.mod --
+module mod.com
+
+go 1.12
+
+require golang.org/x/structs v1.0.0
+-- go.sum --
+golang.org/x/structs v1.0.0 h1:oxD5q25qV458xBbXf5+QX+Johgg71KFtwuJzt145c9A=
+golang.org/x/structs v1.0.0/go.mod h1:47gkSIdo5AaQaWJS0upVORsxfEr1LL1MWv9dmYF3iq4=
+-- main.go --
+package main
+
+import "golang.org/x/structs"
+
+func main() {
+	var _ structs.Mixed
+}
+`
+	// TODO: use a nested workspace folder here.
+	WithOptions(
+		ProxyFiles(proxy),
+	).Run(t, mod, func(t *testing.T, env *Env) {
+		env.OpenFile("main.go")
+		got, _ := env.Hover("main.go", env.RegexpSearch("main.go", "Mixed"))
+		if !strings.Contains(got.Value, "unexported") {
+			t.Errorf("Hover: missing expected field 'unexported'. Got:\n%q", got.Value)
+		}
+	})
+}