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 {
_, p := splitDocs(d)
if p == "" {
if len(p) <= len("func()") {
return nil
}
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
@ -127,18 +132,60 @@ func FunctionParams(v reflect.Value) []string {
func Type(t reflect.Type) string {
t = unpack(t)
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
func structField(visited map[reflect.Type]bool, t reflect.Type, fieldPath []string) (reflect.Type, string, bool) {
t = unpack(t)
if t.Kind() != reflect.Struct {
return nil, "", false
}
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.
@ -147,56 +194,12 @@ func Field(t reflect.Type, fieldPath ...string) string {
return ""
}
t = unpack(t)
if t.Kind() != reflect.Struct {
t, s, ok := structField(nil, t, fieldPath)
if !ok {
return ""
}
f, found := structField(t, fieldPath[0])
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...)
}
}
return Docs(strings.Join([]string{t.PkgPath(), t.Name(), s}, "."))
}
// 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)
return docs(p)
return Docs(p)
}
// 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) {
s := &testpackage.ExportedType{}
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) {
s := testpackage.ExportedType{}
typ := reflect.TypeOf(s)
@ -131,4 +149,21 @@ func Test(t *testing.T) {
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

@ -128,7 +128,7 @@ func splitGopath(p string) (string, string) {
func packagePaths(p []string) []string {
var (
pp []string
m map[string]bool
m map[string]bool
)
for _, pi := range p {
@ -299,7 +299,7 @@ func funcDocs(f *doc.Func) string {
}
func findFieldDocs(str *ast.StructType, fieldPath []string) (string, bool) {
if len(fieldPath) == 0 || str.Fields == nil {
if str.Fields == nil {
return "", false
}

View File

@ -42,11 +42,16 @@ type Foo = ExportedType
type Bar ExportedType
type ExportedType2 struct{
*ExportedType3
// Foo is a field in ExportedType2
Foo int
}
type ExportedType3 struct {
ExportedType2
}
type (
// 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")`
- 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
To insall the docreflect command, run:
@ -26,5 +30,5 @@ make install
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
```