field docs lookup

This commit is contained in:
Arpad Ryszka 2025-08-17 18:33:25 +02:00
parent 5c8853f7ae
commit 43a0e6ca33
5 changed files with 107 additions and 60 deletions

117
docs.go
View File

@ -81,12 +81,17 @@ func splitDocs(d string) (string, string) {
func functionParams(d string) []string { func functionParams(d string) []string {
_, p := splitDocs(d) _, p := splitDocs(d)
if p == "" { if len(p) <= len("func()") {
return nil return nil
} }
p = p[len("func(") : len(p)-1] p = p[len("func(") : len(p)-1]
return strings.Split(p, ", ") pp := strings.Split(p, ",")
for i := range pp {
pp[i] = strings.TrimSpace(pp[i])
}
return pp
} }
// Register is used by the code generated by the docreflect/generate package or the docreflect generate command to register // Register is used by the code generated by the docreflect/generate package or the docreflect generate command to register
@ -127,18 +132,60 @@ func FunctionParams(v reflect.Value) []string {
func Type(t reflect.Type) string { func Type(t reflect.Type) string {
t = unpack(t) 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) { func structField(visited map[reflect.Type]bool, t reflect.Type, fieldPath []string) (reflect.Type, string, bool) {
for i := 0; i < st.NumField(); i++ { t = unpack(t)
f := st.Field(i) if t.Kind() != reflect.Struct {
if f.Name == name { return nil, "", false
return f, true }
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if !f.Anonymous {
continue
}
ft := unpack(f.Type)
if visited[ft] {
continue
}
if visited == nil {
visited = make(map[reflect.Type]bool)
}
visited[ft] = true
if t, s, ok := structField(visited, ft, fieldPath); ok {
return t, s, true
} }
} }
return reflect.StructField{}, false name := fieldPath[0]
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.Name != name {
continue
}
if len(fieldPath) == 1 {
return t, name, true
}
tt, s, ok := structField(nil, unpack(f.Type), fieldPath[1:])
if !ok {
return nil, "", false
}
if tt.Name() == "" {
return t, fmt.Sprintf("%s.%s", name, s), true
}
return tt, s, true
}
return nil, "", false
} }
// Field returns the docuemntation for a struct field. // Field returns the docuemntation for a struct field.
@ -147,56 +194,12 @@ func Field(t reflect.Type, fieldPath ...string) string {
return "" return ""
} }
t = unpack(t) t, s, ok := structField(nil, t, fieldPath)
if t.Kind() != reflect.Struct { if !ok {
return "" return ""
} }
f, found := structField(t, fieldPath[0]) return Docs(strings.Join([]string{t.PkgPath(), t.Name(), s}, "."))
if !found {
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.
@ -207,7 +210,7 @@ func Method(t reflect.Type, name string) string {
} }
p := fmt.Sprintf("%s.%s.%s", t.PkgPath(), t.Name(), name) p := fmt.Sprintf("%s.%s.%s", t.PkgPath(), t.Name(), name)
return docs(p) return Docs(p)
} }
// MethodParams returns the list of the parameter names of a type method. // MethodParams returns the list of the parameter names of a type method.

View File

@ -105,6 +105,15 @@ func Test(t *testing.T) {
} }
}) })
t.Run("field in inline pointer struct type", func(t *testing.T) {
s := &testpackage.ExportedType{}
typ := reflect.TypeOf(s)
d := docreflect.Field(typ, "Qux", "Quux")
if !strings.Contains(d, "Quux is a number") {
t.Fatal(d)
}
})
t.Run("field in another type", func(t *testing.T) { t.Run("field in another type", func(t *testing.T) {
s := &testpackage.ExportedType{} s := &testpackage.ExportedType{}
typ := reflect.TypeOf(s) typ := reflect.TypeOf(s)
@ -114,6 +123,15 @@ func Test(t *testing.T) {
} }
}) })
t.Run("field in an anon struct field", func(t *testing.T) {
s := &testpackage.ExportedType3{}
typ := reflect.TypeOf(s)
d := docreflect.Field(typ, "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)
@ -131,4 +149,21 @@ func Test(t *testing.T) {
t.Fatal() t.Fatal()
} }
}) })
t.Run("method as function value", func(t *testing.T) {
s := testpackage.ExportedType{}
d := docreflect.Function(reflect.ValueOf(s.Method))
if !strings.Contains(d, "Method is a method of ExportedType") {
t.Fatal()
}
p := docreflect.FunctionParams(reflect.ValueOf(s.Method))
if len(p) != 3 {
t.Fatal()
}
if p[0] != "p1" || p[1] != "p2" || p[2] != "p3" {
t.Fatal()
}
})
} }

View File

@ -299,7 +299,7 @@ func funcDocs(f *doc.Func) string {
} }
func findFieldDocs(str *ast.StructType, fieldPath []string) (string, bool) { func findFieldDocs(str *ast.StructType, fieldPath []string) (string, bool) {
if len(fieldPath) == 0 || str.Fields == nil { if str.Fields == nil {
return "", false return "", false
} }

View File

@ -42,11 +42,16 @@ type Foo = ExportedType
type Bar ExportedType type Bar ExportedType
type ExportedType2 struct{ type ExportedType2 struct{
*ExportedType3
// Foo is a field in ExportedType2 // Foo is a field in ExportedType2
Foo int Foo int
} }
type ExportedType3 struct {
ExportedType2
}
type ( type (
// A is a number // A is a number

View File

@ -15,6 +15,10 @@ code, will have the docs accessible via the top level docreflect package methods
- the package documentation can be fetched using `docreflect.Docs("absolute/import/path/of/package")` - the package documentation can be fetched using `docreflect.Docs("absolute/import/path/of/package")`
- when passing in the import path of only the selected symbols, the rest of the package level symbols will be ignroed - when passing in the import path of only the selected symbols, the rest of the package level symbols will be ignroed
**Gotchas:**
- type aliases and type definitions based on named types are not resolved
- package imports are not resolved, necessary packages need to included in the generate arguments
Library documentation: https://godocs.io/code.squareroundforest.org/arpio/docreflect Library documentation: https://godocs.io/code.squareroundforest.org/arpio/docreflect
To insall the docreflect command, run: To insall the docreflect command, run:
@ -26,5 +30,5 @@ make install
Usage of the docreflect command: Usage of the docreflect command:
``` ```
docreflect generate --package-name mypackage coderepos.org/jdoe/mypackage > docreflect.go docreflect generate --package-name mypackage coderepos.org/jdoe/mypackage coderepos.org/jdoe/otherpackage > docreflect.go
``` ```