fix struct field docs
This commit is contained in:
parent
0fcdf50ce5
commit
af83b13d7c
78
docs.go
78
docs.go
@ -13,11 +13,24 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type pointer[T any] interface {
|
||||||
|
Kind() reflect.Kind
|
||||||
|
Elem() T
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
registry = make(map[string]string)
|
registry = make(map[string]string)
|
||||||
funcParamsExp = regexp.MustCompile("^func[(]([a-zA-Z_][a-zA-Z0-9_]+(, [a-zA-Z_][a-zA-Z0-9_]+)*)?[)]$")
|
funcParamsExp = regexp.MustCompile("^func[(]([a-zA-Z_][a-zA-Z0-9_]+(, [a-zA-Z_][a-zA-Z0-9_]+)*)?[)]$")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func unpack[T pointer[T]](p T) T {
|
||||||
|
if p.Kind() != reflect.Pointer {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
return unpack(p.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
func docs(p string) string {
|
func docs(p string) string {
|
||||||
return registry[p]
|
return registry[p]
|
||||||
}
|
}
|
||||||
@ -91,6 +104,7 @@ func Docs(gopath string) string {
|
|||||||
|
|
||||||
// Function returns the documentation for a package level function.
|
// Function returns the documentation for a package level function.
|
||||||
func Function(v reflect.Value) string {
|
func Function(v reflect.Value) string {
|
||||||
|
v = unpack(v)
|
||||||
if v.Kind() != reflect.Func {
|
if v.Kind() != reflect.Func {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -100,6 +114,7 @@ func Function(v reflect.Value) string {
|
|||||||
|
|
||||||
// FunctionParams returns the list of the parameter names of a package level function.
|
// FunctionParams returns the list of the parameter names of a package level function.
|
||||||
func FunctionParams(v reflect.Value) []string {
|
func FunctionParams(v reflect.Value) []string {
|
||||||
|
v = unpack(v)
|
||||||
if v.Kind() != reflect.Func {
|
if v.Kind() != reflect.Func {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -110,27 +125,83 @@ func FunctionParams(v reflect.Value) []string {
|
|||||||
|
|
||||||
// Type returns the docuemntation for a package level type.
|
// Type returns the docuemntation for a package level type.
|
||||||
func Type(t reflect.Type) string {
|
func Type(t reflect.Type) string {
|
||||||
|
t = unpack(t)
|
||||||
p := fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
p := fmt.Sprintf("%s.%s", t.PkgPath(), t.Name())
|
||||||
return docs(p)
|
return docs(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func structField(st reflect.Type, name string) (reflect.StructField, bool) {
|
||||||
|
for i := 0; i < st.NumField(); i++ {
|
||||||
|
f := st.Field(i)
|
||||||
|
if f.Name == name {
|
||||||
|
return f, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return reflect.StructField{}, false
|
||||||
|
}
|
||||||
|
|
||||||
// Field returns the docuemntation for a struct field.
|
// Field returns the docuemntation for a struct field.
|
||||||
func Field(t reflect.Type, fieldPath ...string) string {
|
func Field(t reflect.Type, fieldPath ...string) string {
|
||||||
if len(fieldPath) == 0 {
|
if len(fieldPath) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t = unpack(t)
|
||||||
if t.Kind() != reflect.Struct {
|
if t.Kind() != reflect.Struct {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
p := strings.Join(append([]string{t.PkgPath(), t.Name()}, fieldPath...), ".")
|
f, found := structField(t, fieldPath[0])
|
||||||
println(p)
|
if !found {
|
||||||
return docs(p)
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fieldPath) == 1 {
|
||||||
|
println("returning docs", strings.Join([]string{t.PkgPath(), t.Name(), fieldPath[0]}, "."))
|
||||||
|
return docs(strings.Join([]string{t.PkgPath(), t.Name(), fieldPath[0]}, "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
ft := unpack(f.Type)
|
||||||
|
if ft.Kind() != reflect.Struct {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if ft.Name() != "" {
|
||||||
|
return Field(ft, fieldPath[1:]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
st := ft
|
||||||
|
path := fieldPath[1:]
|
||||||
|
for {
|
||||||
|
f, found = structField(st, path[0])
|
||||||
|
if !found {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(path) == 1 {
|
||||||
|
return docs(strings.Join(append([]string{t.PkgPath(), t.Name()}, fieldPath...), "."))
|
||||||
|
}
|
||||||
|
|
||||||
|
path = path[1:]
|
||||||
|
if len(path) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
st = unpack(f.Type)
|
||||||
|
if st.Kind() != reflect.Struct {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if st.Name() != "" {
|
||||||
|
return Field(st, path...)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method returns the documentation for a type method.
|
// Method returns the documentation for a type method.
|
||||||
func Method(t reflect.Type, name string) string {
|
func Method(t reflect.Type, name string) string {
|
||||||
|
t = unpack(t)
|
||||||
if t.Kind() != reflect.Struct {
|
if t.Kind() != reflect.Struct {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -141,6 +212,7 @@ func Method(t reflect.Type, name string) string {
|
|||||||
|
|
||||||
// MethodParams returns the list of the parameter names of a type method.
|
// MethodParams returns the list of the parameter names of a type method.
|
||||||
func MethodParams(t reflect.Type, name string) []string {
|
func MethodParams(t reflect.Type, name string) []string {
|
||||||
|
t = unpack(t)
|
||||||
if t.Kind() != reflect.Struct {
|
if t.Kind() != reflect.Struct {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
18
docs_test.go
18
docs_test.go
@ -96,6 +96,24 @@ func Test(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("field in inline struct type", func(t *testing.T) {
|
||||||
|
s := &testpackage.ExportedType{}
|
||||||
|
typ := reflect.TypeOf(s)
|
||||||
|
d := docreflect.Field(typ, "Bar", "Baz")
|
||||||
|
if !strings.Contains(d, "Baz is another field") {
|
||||||
|
t.Fatal(d)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("field in another type", func(t *testing.T) {
|
||||||
|
s := &testpackage.ExportedType{}
|
||||||
|
typ := reflect.TypeOf(s)
|
||||||
|
d := docreflect.Field(typ, "Baz", "Foo")
|
||||||
|
if !strings.Contains(d, "Foo is a field in ExportedType2") {
|
||||||
|
t.Fatal(d)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("method", func(t *testing.T) {
|
t.Run("method", func(t *testing.T) {
|
||||||
s := testpackage.ExportedType{}
|
s := testpackage.ExportedType{}
|
||||||
typ := reflect.TypeOf(s)
|
typ := reflect.TypeOf(s)
|
||||||
|
@ -320,7 +320,7 @@ func symbolDocs(pkg *doc.Package, gopath string) (string, bool) {
|
|||||||
|
|
||||||
fieldPath := symbol[1:]
|
fieldPath := symbol[1:]
|
||||||
for len(fieldPath) > 0 {
|
for len(fieldPath) > 0 {
|
||||||
var found bool
|
var foundField bool
|
||||||
if str.Fields != nil {
|
if str.Fields != nil {
|
||||||
for _, f := range str.Fields.List {
|
for _, f := range str.Fields.List {
|
||||||
for _, fn := range f.Names {
|
for _, fn := range f.Names {
|
||||||
@ -329,6 +329,7 @@ func symbolDocs(pkg *doc.Package, gopath string) (string, bool) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foundField = true
|
||||||
if len(fieldPath) == 1 {
|
if len(fieldPath) == 1 {
|
||||||
if f.Doc == nil {
|
if f.Doc == nil {
|
||||||
return "", true
|
return "", true
|
||||||
@ -339,19 +340,22 @@ func symbolDocs(pkg *doc.Package, gopath string) (string, bool) {
|
|||||||
|
|
||||||
fstr, ok := f.Type.(*ast.StructType)
|
fstr, ok := f.Type.(*ast.StructType)
|
||||||
if !ok {
|
if !ok {
|
||||||
break
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
found = true
|
|
||||||
str = fstr
|
str = fstr
|
||||||
fieldPath = fieldPath[1:]
|
fieldPath = fieldPath[1:]
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if foundField {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !foundField {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -390,6 +394,27 @@ func valueDocs(packagePath string, v []*doc.Value) map[string]string {
|
|||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func takeFieldDocs(docs map[string]string, packagePath string, prefix []string, f *ast.Field) {
|
||||||
|
for _, fn := range f.Names {
|
||||||
|
if fn == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
docs[symbolPath(packagePath, append(prefix, fn.Name)...)] = f.Doc.Text()
|
||||||
|
if fst, ok := f.Type.(*ast.StructType); ok {
|
||||||
|
if fst.Fields != nil {
|
||||||
|
for _, fi := range fst.Fields.List {
|
||||||
|
if fi == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
takeFieldDocs(docs, packagePath, append(prefix, fn.Name), fi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func packageDocs(pkg *doc.Package) map[string]string {
|
func packageDocs(pkg *doc.Package) map[string]string {
|
||||||
d := make(map[string]string)
|
d := make(map[string]string)
|
||||||
d[pkg.ImportPath] = pkg.Doc
|
d[pkg.ImportPath] = pkg.Doc
|
||||||
@ -404,17 +429,11 @@ func packageDocs(pkg *doc.Package) map[string]string {
|
|||||||
if ts, ok := t.Decl.Specs[0].(*ast.TypeSpec); ok {
|
if ts, ok := t.Decl.Specs[0].(*ast.TypeSpec); ok {
|
||||||
if str, ok := ts.Type.(*ast.StructType); ok && str.Fields != nil {
|
if str, ok := ts.Type.(*ast.StructType); ok && str.Fields != nil {
|
||||||
for _, f := range str.Fields.List {
|
for _, f := range str.Fields.List {
|
||||||
if f == nil || f.Doc == nil {
|
if f == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, fn := range f.Names {
|
takeFieldDocs(d, pkg.ImportPath, []string{t.Name}, f)
|
||||||
if fn == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d[symbolPath(pkg.ImportPath, t.Name, fn.Name)] = f.Doc.Text()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -534,3 +553,9 @@ func GenerateRegistry(w io.Writer, outputPackageName string, gopath ...string) e
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// - type foo = bar
|
||||||
|
// - type foo bar
|
||||||
|
// - type (...)
|
||||||
|
// - pointers in struct fields
|
||||||
|
@ -63,9 +63,9 @@ func testGenerate(check map[string]string, errstr string, o options, gopath ...s
|
|||||||
if failed {
|
if failed {
|
||||||
t.Log("failed to get the right documentation")
|
t.Log("failed to get the right documentation")
|
||||||
t.Log("expected matches:")
|
t.Log("expected matches:")
|
||||||
t.Log(notation.Sprint(check))
|
t.Log(notation.Sprintw(check))
|
||||||
t.Log("documetation got:")
|
t.Log("documetation got:")
|
||||||
t.Log(notation.Sprint(d))
|
t.Log(notation.Sprintw(d))
|
||||||
t.Fatal()
|
t.Fatal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,7 +121,8 @@ func TestGenerate(t *testing.T) {
|
|||||||
testGenerate(
|
testGenerate(
|
||||||
check(
|
check(
|
||||||
packagePath, "Package testpackage is a test package",
|
packagePath, "Package testpackage is a test package",
|
||||||
fmt.Sprintf("%s.%s", packagePath, "ExportedType"), "ExportedType has docs",
|
fmt.Sprintf("%s.ExportedType", packagePath), "ExportedType has docs",
|
||||||
|
fmt.Sprintf("%s.ExportedType.Bar.Baz", packagePath), "Baz is another field",
|
||||||
),
|
),
|
||||||
"",
|
"",
|
||||||
o,
|
o,
|
||||||
|
@ -19,11 +19,18 @@ type ExportedType struct {
|
|||||||
// Baz is another field
|
// Baz is another field
|
||||||
Baz int
|
Baz int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Baz is a field of type *ExportedType2
|
||||||
|
Baz *ExportedType2
|
||||||
}
|
}
|
||||||
|
|
||||||
type Foo = ExportedType
|
type Foo = ExportedType
|
||||||
|
|
||||||
type ExportedType2 struct{}
|
type ExportedType2 struct{
|
||||||
|
|
||||||
|
// Foo is a field in ExportedType2
|
||||||
|
Foo int
|
||||||
|
}
|
||||||
|
|
||||||
// C1 is a const
|
// C1 is a const
|
||||||
const C1 = 42
|
const C1 = 42
|
||||||
|
Loading…
Reference in New Issue
Block a user