Skip to content

Commit cb134f5

Browse files
committed
gopls/internal/golang: RenderPkgDoc: elide parameters 4+ in index
This CL causes the fourth and subsequent parameters of functions and methods in the index section to be elided and replaced by "...", following pkg.go.dev. Also, unnamed parameters in the navigation selector are treated as blanks (_). Added a test of both features. Change-Id: I542eac61a5ab8b93e5ac02b80d3562a111a0514b Reviewed-on: https://go-review.googlesource.com/c/tools/+/578675 Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent c5094cc commit cb134f5

File tree

2 files changed

+125
-15
lines changed

2 files changed

+125
-15
lines changed

gopls/internal/golang/pkgdoc.go

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ package golang
2525
// - modify JS httpGET function to give a transient visual indication
2626
// when clicking a source link that the editor is being navigated
2727
// (in case it doesn't raise itself, like VS Code).
28+
// - move this into a new package, golang/pkgdoc, and then
29+
// split out the various helpers without fear of polluting
30+
// the golang package namespace.
2831

2932
import (
3033
"bytes"
@@ -263,7 +266,11 @@ window.onload = () => {
263266
if i > 0 {
264267
buf.WriteString(", ")
265268
}
266-
buf.WriteString(sig.Params().At(i).Name())
269+
name := sig.Params().At(i).Name()
270+
if name == "" {
271+
name = "_"
272+
}
273+
buf.WriteString(name)
267274
}
268275
buf.WriteByte(')')
269276
label = buf.String()
@@ -439,12 +446,62 @@ window.onload = () => {
439446
return escape(buf.String())
440447
}
441448

442-
// pkgRelative qualifies types by package name alone
443-
pkgRelative := func(other *types.Package) string {
444-
if pkg.Types() == other {
445-
return "" // same package; unqualified
449+
// fnString is like fn.String() except that it:
450+
// - shows the receiver name;
451+
// - uses space "(T) M()" not dot "(T).M()" after receiver;
452+
// - doesn't bother with the special case for interface receivers
453+
// since it is unreachable for the methods in go/doc.
454+
// - elides parameters after the first three: f(a, b, c, ...).
455+
fnString := func(fn *types.Func) string {
456+
// pkgRelative qualifies types by package name alone
457+
pkgRelative := func(other *types.Package) string {
458+
if pkg.Types() == other {
459+
return "" // same package; unqualified
460+
}
461+
return other.Name()
462+
}
463+
464+
sig := fn.Type().(*types.Signature)
465+
466+
// Emit "func (recv T) F".
467+
var buf bytes.Buffer
468+
buf.WriteString("func ")
469+
if recv := sig.Recv(); recv != nil {
470+
buf.WriteByte('(')
471+
if recv.Name() != "" {
472+
buf.WriteString(recv.Name())
473+
buf.WriteByte(' ')
474+
}
475+
types.WriteType(&buf, recv.Type(), pkgRelative)
476+
buf.WriteByte(')')
477+
buf.WriteByte(' ') // (ObjectString uses a '.' here)
478+
} else if pkg := fn.Pkg(); pkg != nil {
479+
if s := pkgRelative(pkg); s != "" {
480+
buf.WriteString(s)
481+
buf.WriteByte('.')
482+
}
446483
}
447-
return other.Name()
484+
buf.WriteString(fn.Name())
485+
486+
// Emit signature.
487+
//
488+
// Elide parameters after the third one.
489+
// WriteSignature is too complex to fork, so we replace
490+
// parameters 4+ with "invalid type", format,
491+
// then post-process the string.
492+
if sig.Params().Len() > 3 {
493+
sig = types.NewSignatureType(
494+
sig.Recv(),
495+
typesSeqToSlice[*types.TypeParam](sig.RecvTypeParams()),
496+
typesSeqToSlice[*types.TypeParam](sig.TypeParams()),
497+
types.NewTuple(append(
498+
typesSeqToSlice[*types.Var](sig.Params())[:3],
499+
types.NewVar(0, nil, "", types.Typ[types.Invalid]))...),
500+
sig.Results(),
501+
sig.Variadic())
502+
}
503+
types.WriteSignature(&buf, sig, pkgRelative)
504+
return strings.ReplaceAll(buf.String(), ", invalid type)", ", ...)")
448505
}
449506

450507
fmt.Fprintf(&buf, "<main>\n")
@@ -473,10 +530,8 @@ window.onload = () => {
473530
}
474531
for _, fn := range docpkg.Funcs {
475532
obj := scope.Lookup(fn.Name).(*types.Func)
476-
// TODO(adonovan): show only 3 parameters; elide 4+. Ditto for methods.
477533
fmt.Fprintf(&buf, "<li><a href='#%s'>%s</a></li>\n",
478-
obj.Name(),
479-
escape(types.ObjectString(obj, pkgRelative)))
534+
obj.Name(), escape(fnString(obj)))
480535
}
481536
for _, doctype := range docpkg.Types {
482537
tname := scope.Lookup(doctype.Name).(*types.TypeName)
@@ -490,19 +545,15 @@ window.onload = () => {
490545
for _, docfn := range doctype.Funcs {
491546
obj := scope.Lookup(docfn.Name).(*types.Func)
492547
fmt.Fprintf(&buf, "<li><a href='#%s'>%s</a></li>\n",
493-
docfn.Name,
494-
escape(types.ObjectString(obj, pkgRelative)))
548+
docfn.Name, escape(fnString(obj)))
495549
}
496550
// methods
497551
for _, docmethod := range doctype.Methods {
498552
method, _, _ := types.LookupFieldOrMethod(tname.Type(), true, tname.Pkg(), docmethod.Name)
499-
// TODO(adonovan): style: change the . into a space in
500-
// ObjectString's "func (T).M()", and hide unexported
501-
// embedded types.
502553
fmt.Fprintf(&buf, "<li><a href='#%s.%s'>%s</a></li>\n",
503554
doctype.Name,
504555
docmethod.Name,
505-
escape(types.ObjectString(method, pkgRelative)))
556+
escape(fnString(method.(*types.Func))))
506557
}
507558
fmt.Fprintf(&buf, "</ul>\n")
508559
}
@@ -613,6 +664,22 @@ window.onload = () => {
613664
return buf.Bytes(), nil
614665
}
615666

667+
// typesSeq abstracts various go/types sequence types:
668+
// MethodSet, Tuple, TypeParamList, TypeList.
669+
// TODO(adonovan): replace with go1.23 iterators.
670+
type typesSeq[T any] interface {
671+
Len() int
672+
At(int) T
673+
}
674+
675+
func typesSeqToSlice[T any](seq typesSeq[T]) []T {
676+
slice := make([]T, seq.Len())
677+
for i := range slice {
678+
slice[i] = seq.At(i)
679+
}
680+
return slice
681+
}
682+
616683
// (partly taken from pkgsite's typography.css)
617684
const pkgDocStyle = `
618685
body {

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

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,49 @@ func (tπ) mπ() {}
140140
})
141141
}
142142

143+
// TestRenderNavigation tests that the symbol selector and index of
144+
// symbols are well formed.
145+
func TestRenderNavigation(t *testing.T) {
146+
const files = `
147+
-- go.mod --
148+
module example.com
149+
150+
-- a/a.go --
151+
package a
152+
153+
func Func1(int, string, bool, []string) (int, error)
154+
func Func2(x, y int, a, b string) (int, error)
155+
156+
type Type struct {}
157+
func (t Type) Method() {}
158+
func (p *Type) PtrMethod() {}
159+
160+
func Constructor() Type
161+
`
162+
Run(t, files, func(t *testing.T, env *Env) {
163+
uri1 := viewPkgDoc(t, env, "a/a.go")
164+
doc := get(t, uri1)
165+
166+
q := regexp.QuoteMeta
167+
168+
// selector
169+
checkMatch(t, true, doc, q(`<option label='Func1(_, _, _, _)' value='#Func1'/>`))
170+
checkMatch(t, true, doc, q(`<option label='Func2(x, y, a, b)' value='#Func2'/>`))
171+
checkMatch(t, true, doc, q(`<option label='Type' value='#Type'/>`))
172+
checkMatch(t, true, doc, q(`<option label='Constructor()' value='#Constructor'/>`))
173+
checkMatch(t, true, doc, q(`<option label='(t) Method()' value='#Type.Method'/>`))
174+
checkMatch(t, true, doc, q(`<option label='(p) PtrMethod()' value='#Type.PtrMethod'/>`))
175+
176+
// index
177+
checkMatch(t, true, doc, q(`<li><a href='#Func1'>func Func1(int, string, bool, ...) (int, error)</a></li>`))
178+
checkMatch(t, true, doc, q(`<li><a href='#Func2'>func Func2(x int, y int, a string, ...) (int, error)</a></li>`))
179+
checkMatch(t, true, doc, q(`<li><a href='#Type'>type Type</a></li>`))
180+
checkMatch(t, true, doc, q(`<li><a href='#Constructor'>func Constructor() Type</a></li>`))
181+
checkMatch(t, true, doc, q(`<li><a href='#Type.Method'>func (t Type) Method()</a></li>`))
182+
checkMatch(t, true, doc, q(`<li><a href='#Type.PtrMethod'>func (p *Type) PtrMethod()</a></li>`))
183+
})
184+
}
185+
143186
// viewPkgDoc invokes the "View package documention" code action in
144187
// the specified file. It returns the URI of the document, or fails
145188
// the test.

0 commit comments

Comments
 (0)