Skip to content

Commit 3db1ddb

Browse files
committed
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 <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 008ed2c commit 3db1ddb

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

gopls/internal/golang/hover.go

+38
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import (
4040
"golang.org/x/tools/gopls/internal/util/typesutil"
4141
"golang.org/x/tools/internal/aliases"
4242
"golang.org/x/tools/internal/event"
43+
"golang.org/x/tools/internal/stdlib"
4344
"golang.org/x/tools/internal/tokeninternal"
4445
"golang.org/x/tools/internal/typeparams"
4546
"golang.org/x/tools/internal/typesinternal"
@@ -77,6 +78,10 @@ type hoverJSON struct {
7778
// For example, the "Node" part of "pkg.go.dev/go/ast#Node".
7879
LinkAnchor string `json:"linkAnchor"`
7980

81+
// stdVersion is the Go release version at which this symbol became available.
82+
// It is nil for non-std library.
83+
stdVersion *stdlib.Version
84+
8085
// New fields go below, and are unexported. The existing
8186
// exported fields are underspecified and have already
8287
// constrained our movements too much. A detailed JSON
@@ -595,6 +600,11 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
595600
linkPath = strings.Replace(linkPath, mod.Path, mod.Path+"@"+mod.Version, 1)
596601
}
597602

603+
var version *stdlib.Version
604+
if symbol := StdSymbolOf(obj); symbol != nil {
605+
version = &symbol.Version
606+
}
607+
598608
return *hoverRange, &hoverJSON{
599609
Synopsis: doc.Synopsis(docText),
600610
FullDocumentation: docText,
@@ -606,6 +616,7 @@ func hover(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, pp pro
606616
typeDecl: typeDecl,
607617
methods: methods,
608618
promotedFields: fields,
619+
stdVersion: version,
609620
}, nil
610621
}
611622

@@ -1166,11 +1177,15 @@ func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path Packa
11661177
formatDoc(h, options),
11671178
maybeMarkdown(h.promotedFields),
11681179
maybeMarkdown(h.methods),
1180+
fmt.Sprintf("Added in %v", h.stdVersion),
11691181
formatLink(h, options, pkgURL),
11701182
}
11711183
if h.typeDecl != "" {
11721184
parts[0] = "" // type: suppress redundant Signature
11731185
}
1186+
if h.stdVersion == nil || *h.stdVersion == stdlib.Version(0) {
1187+
parts[5] = "" // suppress stdlib version if not applicable or initial version 1.0
1188+
}
11741189
parts = slices.Remove(parts, "")
11751190

11761191
var b strings.Builder
@@ -1191,6 +1206,29 @@ func formatHover(h *hoverJSON, options *settings.Options, pkgURL func(path Packa
11911206
}
11921207
}
11931208

1209+
// StdSymbolOf returns the std lib symbol information of the given obj.
1210+
// It returns nil if the input obj is not an exported standard library symbol.
1211+
func StdSymbolOf(obj types.Object) *stdlib.Symbol {
1212+
if !obj.Exported() {
1213+
return nil
1214+
}
1215+
1216+
if isPackageLevel(obj) {
1217+
// TODO(hxjiang): This is binary searchable.
1218+
for _, s := range stdlib.PackageSymbols[obj.Pkg().Path()] {
1219+
if s.Kind == stdlib.Method || s.Kind == stdlib.Field {
1220+
continue
1221+
}
1222+
if s.Name == obj.Name() {
1223+
return &s
1224+
}
1225+
}
1226+
}
1227+
1228+
// TODO(hxjiang): handle exported fields and methods of package level types.
1229+
return nil
1230+
}
1231+
11941232
// If pkgURL is non-nil, it should be used to generate doc links.
11951233
func formatLink(h *hoverJSON, options *settings.Options, pkgURL func(path PackagePath, fragment string) protocol.URI) string {
11961234
if options.LinksInHover == false || h.LinkPath == "" {

gopls/internal/test/integration/misc/hover_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -660,3 +660,52 @@ func (e) Error() string
660660
}
661661
})
662662
}
663+
664+
func TestHoverStdlibWithAvailableVersion(t *testing.T) {
665+
const src = `
666+
-- stdlib.go --
667+
package stdlib
668+
669+
import "fmt"
670+
import "context"
671+
import "crypto"
672+
673+
func _() {
674+
var ctx context.Context
675+
ctx = context.Background()
676+
if ctx.Err(); e == context.Canceled {
677+
fmt.Println("Canceled")
678+
fmt.Printf("%v", crypto.SHA512_224)
679+
}
680+
_ := fmt.Appendf(make([]byte, 100), "world, %d", 23)
681+
}
682+
`
683+
684+
testcases := []struct {
685+
symbolRE string // regexp matching symbol to hover over
686+
shouldContain bool
687+
targetString string
688+
}{
689+
{"Println", false, "go1.0"}, // package-level func
690+
{"Appendf", true, "go1.19"}, // package-level func
691+
{"Background", true, "go1.7"}, // package-level func
692+
{"Canceled", true, "go1.7"}, // package-level var
693+
{"Context", true, "go1.7"}, // package-level type
694+
{"SHA512_224", true, "go1.5"}, // package-level const
695+
// TODO(hxjiang): add test for symbol type Method.
696+
// TODO(hxjiang): add test for symbol type Field.
697+
}
698+
699+
Run(t, src, func(t *testing.T, env *Env) {
700+
env.OpenFile("stdlib.go")
701+
for _, tc := range testcases {
702+
content, _ := env.Hover(env.RegexpSearch("stdlib.go", tc.symbolRE))
703+
if tc.shouldContain && !strings.Contains(content.Value, tc.targetString) {
704+
t.Errorf("Hover(%q) should contain string %s", tc.symbolRE, tc.targetString)
705+
}
706+
if !tc.shouldContain && strings.Contains(content.Value, tc.targetString) {
707+
t.Errorf("Hover(%q) should not contain string %s", tc.symbolRE, tc.targetString)
708+
}
709+
}
710+
})
711+
}

0 commit comments

Comments
 (0)