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) + } + } + }) +}