gopls/internal/golang: provide available version info in stdlib hover
Version is only available for the types of Var, Func, Const & Type.
For golang/go#67159
Change-Id: I77f95ccb6027914440ec7a2ea5338318c0f88e60
Reviewed-on: https://go-review.googlesource.com/c/tools/+/594875
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/gopls/internal/golang/hover.go b/gopls/internal/golang/hover.go
index 1291f64..4818f81 100644
--- a/gopls/internal/golang/hover.go
+++ b/gopls/internal/golang/hover.go
@@ -40,6 +40,7 @@
"golang.org/x/tools/gopls/internal/util/typesutil"
"golang.org/x/tools/internal/aliases"
"golang.org/x/tools/internal/event"
+ "golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/tokeninternal"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
@@ -77,6 +78,10 @@
// For example, the "Node" part of "pkg.go.dev/go/ast#Node".
LinkAnchor string `json:"linkAnchor"`
+ // stdVersion is the Go release version at which this symbol became available.
+ // It is nil for non-std library.
+ stdVersion *stdlib.Version
+
// New fields go below, and are unexported. The existing
// exported fields are underspecified and have already
// constrained our movements too much. A detailed JSON
@@ -595,6 +600,11 @@
linkPath = strings.Replace(linkPath, mod.Path, mod.Path+"@"+mod.Version, 1)
}
+ var version *stdlib.Version
+ if symbol := StdSymbolOf(obj); symbol != nil {
+ version = &symbol.Version
+ }
+
return *hoverRange, &hoverJSON{
Synopsis: doc.Synopsis(docText),
FullDocumentation: docText,
@@ -606,6 +616,7 @@
typeDecl: typeDecl,
methods: methods,
promotedFields: fields,
+ stdVersion: version,
}, nil
}
@@ -1166,11 +1177,15 @@
formatDoc(h, options),
maybeMarkdown(h.promotedFields),
maybeMarkdown(h.methods),
+ fmt.Sprintf("Added in %v", h.stdVersion),
formatLink(h, options, pkgURL),
}
if h.typeDecl != "" {
parts[0] = "" // type: suppress redundant Signature
}
+ if h.stdVersion == nil || *h.stdVersion == stdlib.Version(0) {
+ parts[5] = "" // suppress stdlib version if not applicable or initial version 1.0
+ }
parts = slices.Remove(parts, "")
var b strings.Builder
@@ -1191,6 +1206,29 @@
}
}
+// StdSymbolOf returns the std lib symbol information of the given obj.
+// It returns nil if the input obj is not an exported standard library symbol.
+func StdSymbolOf(obj types.Object) *stdlib.Symbol {
+ if !obj.Exported() {
+ return nil
+ }
+
+ if isPackageLevel(obj) {
+ // TODO(hxjiang): This is binary searchable.
+ for _, s := range stdlib.PackageSymbols[obj.Pkg().Path()] {
+ if s.Kind == stdlib.Method || s.Kind == stdlib.Field {
+ continue
+ }
+ if s.Name == obj.Name() {
+ return &s
+ }
+ }
+ }
+
+ // TODO(hxjiang): handle exported fields and methods of package level types.
+ return nil
+}
+
// If pkgURL is non-nil, it should be used to generate doc links.
func formatLink(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) string {
if options.LinksInHover == false || h.LinkPath == "" {
diff --git a/gopls/internal/test/integration/misc/hover_test.go b/gopls/internal/test/integration/misc/hover_test.go
index 3526a93..21233d0 100644
--- a/gopls/internal/test/integration/misc/hover_test.go
+++ b/gopls/internal/test/integration/misc/hover_test.go
@@ -660,3 +660,52 @@
}
})
}
+
+func TestHoverStdlibWithAvailableVersion(t *testing.T) {
+ const src = `
+-- stdlib.go --
+package stdlib
+
+import "fmt"
+import "context"
+import "crypto"
+
+func _() {
+ var ctx context.Context
+ ctx = context.Background()
+ if ctx.Err(); e == context.Canceled {
+ fmt.Println("Canceled")
+ fmt.Printf("%v", crypto.SHA512_224)
+ }
+ _ := fmt.Appendf(make([]byte, 100), "world, %d", 23)
+}
+`
+
+ testcases := []struct {
+ symbolRE string // regexp matching symbol to hover over
+ shouldContain bool
+ targetString string
+ }{
+ {"Println", false, "go1.0"}, // package-level func
+ {"Appendf", true, "go1.19"}, // package-level func
+ {"Background", true, "go1.7"}, // package-level func
+ {"Canceled", true, "go1.7"}, // package-level var
+ {"Context", true, "go1.7"}, // package-level type
+ {"SHA512_224", true, "go1.5"}, // package-level const
+ // TODO(hxjiang): add test for symbol type Method.
+ // TODO(hxjiang): add test for symbol type Field.
+ }
+
+ Run(t, src, func(t *testing.T, env *Env) {
+ env.OpenFile("stdlib.go")
+ for _, tc := range testcases {
+ content, _ := env.Hover(env.RegexpSearch("stdlib.go", tc.symbolRE))
+ if tc.shouldContain && !strings.Contains(content.Value, tc.targetString) {
+ t.Errorf("Hover(%q) should contain string %s", tc.symbolRE, tc.targetString)
+ }
+ if !tc.shouldContain && strings.Contains(content.Value, tc.targetString) {
+ t.Errorf("Hover(%q) should not contain string %s", tc.symbolRE, tc.targetString)
+ }
+ }
+ })
+}