t.Errorf("anchorID(%q) = %q; want %q", in, got, want)
}
}
+
+func TestFuncs(t *testing.T) {
+ fset := token.NewFileSet()
+ file, err := parser.ParseFile(fset, "funcs.go", strings.NewReader(funcsTestFile), parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ doc, err := NewFromFiles(fset, []*ast.File{file}, "importPath", Mode(0))
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ for _, f := range doc.Funcs {
+ f.Decl = nil
+ }
+ for _, ty := range doc.Types {
+ for _, f := range ty.Funcs {
+ f.Decl = nil
+ }
+ for _, m := range ty.Methods {
+ m.Decl = nil
+ }
+ }
+
+ compareFuncs := func(t *testing.T, msg string, got, want *Func) {
+ // ignore Decl and Examples
+ got.Decl = nil
+ got.Examples = nil
+ if !(got.Doc == want.Doc &&
+ got.Name == want.Name &&
+ got.Recv == want.Recv &&
+ got.Orig == want.Orig &&
+ got.Level == want.Level) {
+ t.Errorf("%s:\ngot %+v\nwant %+v", msg, got, want)
+ }
+ }
+
+ compareSlices(t, "Funcs", doc.Funcs, funcsPackage.Funcs, compareFuncs)
+ compareSlices(t, "Types", doc.Types, funcsPackage.Types, func(t *testing.T, msg string, got, want *Type) {
+ if got.Name != want.Name {
+ t.Errorf("%s.Name: got %q, want %q", msg, got.Name, want.Name)
+ } else {
+ compareSlices(t, got.Name+".Funcs", got.Funcs, want.Funcs, compareFuncs)
+ compareSlices(t, got.Name+".Methods", got.Methods, want.Methods, compareFuncs)
+ }
+ })
+}
+
+func compareSlices[E any](t *testing.T, name string, got, want []E, compareElem func(*testing.T, string, E, E)) {
+ if len(got) != len(want) {
+ t.Errorf("%s: got %d, want %d", name, len(got), len(want))
+ }
+ for i := 0; i < len(got) && i < len(want); i++ {
+ compareElem(t, fmt.Sprintf("%s[%d]", name, i), got[i], want[i])
+ }
+}
+
+const funcsTestFile = `
+package funcs
+
+func F() {}
+
+type S1 struct {
+ S2 // embedded, exported
+ s3 // embedded, unexported
+}
+
+func NewS1() S1 {return S1{} }
+func NewS1p() *S1 { return &S1{} }
+
+func (S1) M1() {}
+func (r S1) M2() {}
+func(S1) m3() {} // unexported not shown
+func (*S1) P1() {} // pointer receiver
+
+type S2 int
+func (S2) M3() {} // shown on S2
+
+type s3 int
+func (s3) M4() {} // shown on S1
+
+type G1[T any] struct {
+ *s3
+}
+
+func NewG1[T any]() G1[T] { return G1[T]{} }
+
+func (G1[T]) MG1() {}
+func (*G1[U]) MG2() {}
+
+type G2[T, U any] struct {}
+
+func NewG2[T, U any]() G2[T, U] { return G2[T, U]{} }
+
+func (G2[T, U]) MG3() {}
+func (*G2[A, B]) MG4() {}
+
+
+`
+
+var funcsPackage = &Package{
+ Funcs: []*Func{{Name: "F"}},
+ Types: []*Type{
+ {
+ Name: "G1",
+ Funcs: []*Func{{Name: "NewG1"}},
+ Methods: []*Func{
+ {Name: "M4", Recv: "G1", // TODO: synthesize a param for G1?
+ Orig: "s3", Level: 1},
+ {Name: "MG1", Recv: "G1[T]", Orig: "G1[T]", Level: 0},
+ {Name: "MG2", Recv: "*G1[U]", Orig: "*G1[U]", Level: 0},
+ },
+ },
+ {
+ Name: "G2",
+ Funcs: []*Func{{Name: "NewG2"}},
+ Methods: []*Func{
+ {Name: "MG3", Recv: "G2[T, U]", Orig: "G2[T, U]", Level: 0},
+ {Name: "MG4", Recv: "*G2[A, B]", Orig: "*G2[A, B]", Level: 0},
+ },
+ },
+ {
+ Name: "S1",
+ Funcs: []*Func{{Name: "NewS1"}, {Name: "NewS1p"}},
+ Methods: []*Func{
+ {Name: "M1", Recv: "S1", Orig: "S1", Level: 0},
+ {Name: "M2", Recv: "S1", Orig: "S1", Level: 0},
+ {Name: "M4", Recv: "S1", Orig: "s3", Level: 1},
+ {Name: "P1", Recv: "*S1", Orig: "*S1", Level: 0},
+ },
+ },
+ {
+ Name: "S2",
+ Methods: []*Func{
+ {Name: "M3", Recv: "S2", Orig: "S2", Level: 0},
+ },
+ },
+ },
+}
package doc
import (
+ "fmt"
"go/ast"
"go/token"
"internal/lazyregexp"
"sort"
"strconv"
+ "strings"
)
// ----------------------------------------------------------------------------
//
type methodSet map[string]*Func
-// recvString returns a string representation of recv of the
-// form "T", "*T", or "BADRECV" (if not a proper receiver type).
+// recvString returns a string representation of recv of the form "T", "*T",
+// "T[A, ...]", "*T[A, ...]" or "BADRECV" (if not a proper receiver type).
//
func recvString(recv ast.Expr) string {
switch t := recv.(type) {
return t.Name
case *ast.StarExpr:
return "*" + recvString(t.X)
+ case *ast.IndexExpr:
+ // Generic type with one parameter.
+ return fmt.Sprintf("%s[%s]", recvString(t.X), recvParam(t.Index))
+ case *ast.IndexListExpr:
+ // Generic type with multiple parameters.
+ if len(t.Indices) > 0 {
+ var b strings.Builder
+ b.WriteString(recvString(t.X))
+ b.WriteByte('[')
+ b.WriteString(recvParam(t.Indices[0]))
+ for _, e := range t.Indices[1:] {
+ b.WriteString(", ")
+ b.WriteString(recvParam(e))
+ }
+ b.WriteByte(']')
+ return b.String()
+ }
}
return "BADRECV"
}
+func recvParam(p ast.Expr) string {
+ if id, ok := p.(*ast.Ident); ok {
+ return id.Name
+ }
+ return "BADPARAM"
+}
+
// set creates the corresponding Func for f and adds it to mset.
// If there are multiple f's with the same name, set keeps the first
// one with documentation; conflicts are ignored. The boolean