diff --git a/docs.go b/docs.go index 710445f..d81f553 100644 --- a/docs.go +++ b/docs.go @@ -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. diff --git a/docs_test.go b/docs_test.go index d072400..aa5074c 100644 --- a/docs_test.go +++ b/docs_test.go @@ -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() + } + }) } diff --git a/generate/generate.go b/generate/generate.go index 5c8e5bf..f88620f 100644 --- a/generate/generate.go +++ b/generate/generate.go @@ -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 } diff --git a/internal/tests/src/testpackage/test.go b/internal/tests/src/testpackage/test.go index e9d8f50..8c65390 100644 --- a/internal/tests/src/testpackage/test.go +++ b/internal/tests/src/testpackage/test.go @@ -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 diff --git a/readme.md b/readme.md index 1be9df0..f50b464 100644 --- a/readme.md +++ b/readme.md @@ -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 ```