Merge pull request #323 from lizkammer/bpdocs

Add docs for nested and embedded structs
This commit is contained in:
Liz Kammer 2020-10-05 13:15:33 -05:00 committed by GitHub
commit a10477e361
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 158 additions and 22 deletions

View file

@ -5,6 +5,7 @@ import (
"html/template"
"reflect"
"sort"
"strings"
"github.com/google/blueprint/proptools"
)
@ -58,6 +59,7 @@ type Property struct {
OtherTexts []template.HTML
Properties []Property
Default string
Anonymous bool
}
func AllPackages(pkgFiles map[string][]string, moduleTypeNameFactories map[string]reflect.Value,
@ -75,6 +77,7 @@ func AllPackages(pkgFiles map[string][]string, moduleTypeNameFactories map[strin
return nil, err
}
// Some pruning work
removeAnonymousProperties(mtInfo)
removeEmptyPropertyStructs(mtInfo)
collapseDuplicatePropertyStructs(mtInfo)
collapseNestedPropertyStructs(mtInfo)
@ -128,7 +131,9 @@ func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
}
ps.ExcludeByTag("blueprint", "mutated")
for nestedName, nestedValue := range nestedPropertyStructs(v) {
for _, nestedProperty := range nestedPropertyStructs(v) {
nestedName := nestedProperty.nestPoint
nestedValue := nestedProperty.value
nestedType := nestedValue.Type()
// Ignore property structs with unexported or unnamed types
@ -140,12 +145,27 @@ func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
return nil, err
}
nested.ExcludeByTag("blueprint", "mutated")
nestPoint := ps.GetByName(nestedName)
if nestPoint == nil {
return nil, fmt.Errorf("nesting point %q not found", nestedName)
if nestedName == "" {
ps.Nest(nested)
} else {
nestPoint := ps.GetByName(nestedName)
if nestPoint == nil {
return nil, fmt.Errorf("nesting point %q not found", nestedName)
}
nestPoint.Nest(nested)
}
nestPoint.Nest(nested)
if nestedProperty.anonymous {
if nestedName != "" {
nestedName += "."
}
nestedName += proptools.PropertyNameForField(nested.Name)
nestedProp := ps.GetByName(nestedName)
if nestedProp == nil {
return nil, fmt.Errorf("could not find nested property %q", nestedName)
}
nestedProp.SetAnonymous()
}
}
mt.PropertyStructs = append(mt.PropertyStructs, ps)
}
@ -153,10 +173,31 @@ func assembleModuleTypeInfo(r *Reader, name string, factory reflect.Value,
return mt, nil
}
func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value {
ret := make(map[string]reflect.Value)
type nestedProperty struct {
nestPoint string
value reflect.Value
anonymous bool
}
func nestedPropertyStructs(s reflect.Value) []nestedProperty {
ret := make([]nestedProperty, 0)
var walk func(structValue reflect.Value, prefix string)
walk = func(structValue reflect.Value, prefix string) {
var nestStruct func(field reflect.StructField, value reflect.Value, fieldName string)
nestStruct = func(field reflect.StructField, value reflect.Value, fieldName string) {
nestPoint := prefix
if field.Anonymous {
nestPoint = strings.TrimSuffix(nestPoint, ".")
} else {
nestPoint = nestPoint + proptools.PropertyNameForField(fieldName)
}
ret = append(ret, nestedProperty{nestPoint: nestPoint, value: value, anonymous: field.Anonymous})
if nestPoint != "" {
nestPoint += "."
}
walk(value, nestPoint)
}
typ := structValue.Type()
for i := 0; i < structValue.NumField(); i++ {
field := typ.Field(i)
@ -174,8 +215,9 @@ func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value {
case reflect.Bool, reflect.String, reflect.Slice, reflect.Int, reflect.Uint:
// Nothing
case reflect.Struct:
walk(fieldValue, prefix+proptools.PropertyNameForField(field.Name)+".")
nestStruct(field, fieldValue, field.Name)
case reflect.Ptr, reflect.Interface:
if !fieldValue.IsNil() {
// We leave the pointer intact and zero out the struct that's
// pointed to.
@ -188,9 +230,7 @@ func nestedPropertyStructs(s reflect.Value) map[string]reflect.Value {
elem = elem.Elem()
}
if elem.Kind() == reflect.Struct {
nestPoint := prefix + proptools.PropertyNameForField(field.Name)
ret[nestPoint] = elem
walk(elem, nestPoint+".")
nestStruct(field, elem, field.Name)
}
}
default:
@ -214,6 +254,27 @@ func removeEmptyPropertyStructs(mt *ModuleType) {
}
}
// Remove any property structs that are anonymous
func removeAnonymousProperties(mt *ModuleType) {
var removeAnonymousProps func(props []Property) []Property
removeAnonymousProps = func(props []Property) []Property {
newProps := make([]Property, 0, len(props))
for _, p := range props {
if p.Anonymous {
continue
}
if len(p.Properties) > 0 {
p.Properties = removeAnonymousProps(p.Properties)
}
newProps = append(newProps, p)
}
return newProps
}
for _, ps := range mt.PropertyStructs {
ps.Properties = removeAnonymousProps(ps.Properties)
}
}
// Squashes duplicates of the same property struct into single entries
func collapseDuplicatePropertyStructs(mt *ModuleType) {
var collapsed []*PropertyStruct

View file

@ -36,20 +36,23 @@ func TestNestedPropertyStructs(t *testing.T) {
// mutated shouldn't be found because it's a mutated property.
expected := []string{"child", "child.child"}
if len(allStructs) != len(expected) {
t.Errorf("expected %d structs, got %d, all entries: %q",
t.Fatalf("expected %d structs, got %d, all entries: %v",
len(expected), len(allStructs), allStructs)
}
for _, e := range expected {
if _, ok := allStructs[e]; !ok {
t.Errorf("missing entry %q, all entries: %q", e, allStructs)
}
got := []string{}
for _, s := range allStructs {
got = append(got, s.nestPoint)
}
if !reflect.DeepEqual(got, expected) {
t.Errorf("Expected nested properties:\n\t %q,\n but got\n\t %q", expected, got)
}
}
func TestAllPackages(t *testing.T) {
packages, err := AllPackages(pkgFiles, moduleTypeNameFactories, moduleTypeNamePropertyStructs)
if err != nil {
t.Errorf("expected nil error for AllPackages(%v, %v, %v), got %s", pkgFiles, moduleTypeNameFactories, moduleTypeNamePropertyStructs, err)
t.Fatalf("expected nil error for AllPackages(%v, %v, %v), got %s", pkgFiles, moduleTypeNameFactories, moduleTypeNamePropertyStructs, err)
}
if numPackages := len(packages); numPackages != 1 {
@ -58,24 +61,56 @@ func TestAllPackages(t *testing.T) {
pkg := packages[0]
expectedProps := map[string][]string{
"bar": []string{
"a",
"nested",
"nested.c",
"nested_struct",
"nested_struct.e",
"struct_has_embed",
"struct_has_embed.nested_in_embedded",
"struct_has_embed.nested_in_embedded.e",
"struct_has_embed.f",
"nested_in_other_embedded",
"nested_in_other_embedded.g",
"h",
},
"foo": []string{
"a",
},
}
for _, m := range pkg.ModuleTypes {
foundProps := []string{}
for _, p := range m.PropertyStructs {
for _, err := range noMutatedProperties(p.Properties) {
nestedProps, errs := findAllProperties("", p.Properties)
foundProps = append(foundProps, nestedProps...)
for _, err := range errs {
t.Errorf("%s", err)
}
}
if wanted, ok := expectedProps[m.Name]; ok {
if !reflect.DeepEqual(foundProps, wanted) {
t.Errorf("For %s, expected\n\t %q,\nbut got\n\t %q", m.Name, wanted, foundProps)
}
}
}
}
func noMutatedProperties(properties []Property) []error {
func findAllProperties(prefix string, properties []Property) ([]string, []error) {
foundProps := []string{}
errs := []error{}
for _, p := range properties {
foundProps = append(foundProps, prefix+p.Name)
if hasTag(p.Tag, "blueprint", "mutated") {
err := fmt.Errorf("Property %s has `blueprint:\"mutated\" tag but should have been excluded.", p)
err := fmt.Errorf("Property %s has `blueprint:\"mutated\" tag but should have been excluded.", p.Name)
errs = append(errs, err)
}
errs = append(errs, noMutatedProperties(p.Properties)...)
nestedProps, nestedErrs := findAllProperties(prefix+p.Name+".", p.Properties)
foundProps = append(foundProps, nestedProps...)
errs = append(errs, nestedErrs...)
}
return errs
return foundProps, errs
}

View file

@ -142,6 +142,10 @@ func (ps *PropertyStruct) GetByName(name string) *Property {
return getByName(name, "", &ps.Properties)
}
func (ps *PropertyStruct) Nest(nested *PropertyStruct) {
ps.Properties = append(ps.Properties, nested.Properties...)
}
func getByName(name string, prefix string, props *[]Property) *Property {
for i := range *props {
if prefix+(*props)[i].Name == name {
@ -157,6 +161,10 @@ func (p *Property) Nest(nested *PropertyStruct) {
p.Properties = append(p.Properties, nested.Properties...)
}
func (p *Property) SetAnonymous() {
p.Anonymous = true
}
func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) {
typeSpec := t.Decl.Specs[0].(*ast.TypeSpec)
ps := PropertyStruct{

View file

@ -36,6 +36,32 @@ func barFactory() (blueprint.Module, []interface{}) {
return nil, []interface{}{&complexProps{}}
}
type structToNest struct {
E string
}
type StructToEmbed struct {
Nested_in_embedded structToNest
// F string
F string
}
type otherStructToNest struct {
G string
}
type OtherStructToEmbed struct {
Nested_in_other_embedded otherStructToNest
// F string
H string
}
type StructWithEmbedded struct {
StructToEmbed
}
// for bpdoc_test.go
type complexProps struct {
A string
@ -45,6 +71,12 @@ type complexProps struct {
C string
D_mutated string `blueprint:"mutated"`
}
Nested_struct structToNest
Struct_has_embed StructWithEmbedded
OtherStructToEmbed
}
// props docs.