1// Copyright 2019 Google Inc. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15package bpdoc 16 17import ( 18 "fmt" 19 "go/ast" 20 "go/doc" 21 "html/template" 22 "reflect" 23 "slices" 24 "strconv" 25 "strings" 26 "unicode" 27 "unicode/utf8" 28 29 "github.com/google/blueprint/proptools" 30) 31 32// 33// Utility functions for PropertyStruct and Property 34// 35 36func (ps *PropertyStruct) Clone() *PropertyStruct { 37 ret := *ps 38 ret.Properties = slices.Clone(ret.Properties) 39 for i, prop := range ret.Properties { 40 ret.Properties[i] = prop.Clone() 41 } 42 43 return &ret 44} 45 46func (p *Property) Clone() Property { 47 ret := *p 48 ret.Properties = slices.Clone(ret.Properties) 49 for i, prop := range ret.Properties { 50 ret.Properties[i] = prop.Clone() 51 } 52 53 return ret 54} 55 56func (p *Property) Equal(other Property) bool { 57 return p.Name == other.Name && p.Type == other.Type && p.Tag == other.Tag && 58 p.Text == other.Text && p.Default == other.Default && 59 stringArrayEqual(p.OtherNames, other.OtherNames) && 60 htmlArrayEqual(p.OtherTexts, other.OtherTexts) && 61 p.SameSubProperties(other) 62} 63 64func (ps *PropertyStruct) SetDefaults(defaults reflect.Value) { 65 setDefaults(ps.Properties, defaults) 66} 67 68func setDefaults(properties []Property, defaults reflect.Value) { 69 for i := range properties { 70 prop := &properties[i] 71 fieldName := proptools.FieldNameForProperty(prop.Name) 72 f := defaults.FieldByName(fieldName) 73 if (f == reflect.Value{}) { 74 panic(fmt.Errorf("property %q does not exist in %q", fieldName, defaults.Type())) 75 } 76 77 if reflect.DeepEqual(f.Interface(), reflect.Zero(f.Type()).Interface()) { 78 continue 79 } 80 81 if f.Kind() == reflect.Interface { 82 f = f.Elem() 83 } 84 85 if f.Kind() == reflect.Ptr { 86 if f.IsNil() { 87 continue 88 } 89 f = f.Elem() 90 } 91 92 if f.Kind() == reflect.Struct { 93 setDefaults(prop.Properties, f) 94 } else { 95 prop.Default = fmt.Sprintf("%v", f.Interface()) 96 } 97 } 98} 99 100func stringArrayEqual(a, b []string) bool { 101 if len(a) != len(b) { 102 return false 103 } 104 105 for i := range a { 106 if a[i] != b[i] { 107 return false 108 } 109 } 110 111 return true 112} 113 114func htmlArrayEqual(a, b []template.HTML) bool { 115 if len(a) != len(b) { 116 return false 117 } 118 119 for i := range a { 120 if a[i] != b[i] { 121 return false 122 } 123 } 124 125 return true 126} 127 128func (p *Property) SameSubProperties(other Property) bool { 129 if len(p.Properties) != len(other.Properties) { 130 return false 131 } 132 133 for i := range p.Properties { 134 if !p.Properties[i].Equal(other.Properties[i]) { 135 return false 136 } 137 } 138 139 return true 140} 141 142func (ps *PropertyStruct) GetByName(name string) *Property { 143 return getByName(name, "", &ps.Properties) 144} 145 146func (ps *PropertyStruct) Nest(nested *PropertyStruct) { 147 ps.Properties = nestUnique(ps.Properties, nested.Properties) 148} 149 150// Adds a target element to src if it does not exist in src 151func nestUnique(src []Property, target []Property) []Property { 152 var ret []Property 153 ret = append(ret, src...) 154 for _, elem := range target { 155 isUnique := true 156 for _, retElement := range ret { 157 if elem.Equal(retElement) { 158 isUnique = false 159 break 160 } 161 } 162 if isUnique { 163 ret = append(ret, elem) 164 } 165 } 166 return ret 167} 168 169func getByName(name string, prefix string, props *[]Property) *Property { 170 for i := range *props { 171 if prefix+(*props)[i].Name == name { 172 return &(*props)[i] 173 } else if strings.HasPrefix(name, prefix+(*props)[i].Name+".") { 174 return getByName(name, prefix+(*props)[i].Name+".", &(*props)[i].Properties) 175 } 176 } 177 return nil 178} 179 180func (p *Property) Nest(nested *PropertyStruct) { 181 p.Properties = nestUnique(p.Properties, nested.Properties) 182} 183 184func (p *Property) SetAnonymous() { 185 p.Anonymous = true 186} 187 188func newPropertyStruct(t *doc.Type) (*PropertyStruct, error) { 189 typeSpec := t.Decl.Specs[0].(*ast.TypeSpec) 190 ps := PropertyStruct{ 191 Name: t.Name, 192 Text: t.Doc, 193 } 194 195 structType, ok := typeSpec.Type.(*ast.StructType) 196 if !ok { 197 return nil, fmt.Errorf("type of %q is not a struct", t.Name) 198 } 199 200 var err error 201 ps.Properties, err = structProperties(structType) 202 if err != nil { 203 return nil, err 204 } 205 206 return &ps, nil 207} 208 209func structProperties(structType *ast.StructType) (props []Property, err error) { 210 for _, f := range structType.Fields.List { 211 names := f.Names 212 if names == nil { 213 // Anonymous fields have no name, use the type as the name 214 // TODO: hide the name and make the properties show up in the embedding struct 215 if t, ok := f.Type.(*ast.Ident); ok { 216 names = append(names, t) 217 } 218 } 219 for _, n := range names { 220 var name, tag, text string 221 if n != nil { 222 name = proptools.PropertyNameForField(n.Name) 223 } 224 if f.Doc != nil { 225 text = f.Doc.Text() 226 } 227 if f.Tag != nil { 228 tag, err = strconv.Unquote(f.Tag.Value) 229 if err != nil { 230 return nil, err 231 } 232 } 233 typ, innerProps, err := getType(f.Type) 234 if err != nil { 235 return nil, err 236 } 237 238 props = append(props, Property{ 239 Name: name, 240 Type: typ, 241 Tag: reflect.StructTag(tag), 242 Text: formatText(text), 243 Properties: innerProps, 244 }) 245 } 246 } 247 248 return props, nil 249} 250 251func getType(expr ast.Expr) (typ string, innerProps []Property, err error) { 252 var t ast.Expr 253 if star, ok := expr.(*ast.StarExpr); ok { 254 t = star.X 255 } else { 256 t = expr 257 } 258 switch a := t.(type) { 259 case *ast.ArrayType: 260 var elt string 261 elt, innerProps, err = getType(a.Elt) 262 if err != nil { 263 return "", nil, err 264 } 265 typ = "list of " + elt 266 case *ast.InterfaceType: 267 typ = "interface" 268 case *ast.Ident: 269 typ = a.Name 270 case *ast.StructType: 271 innerProps, err = structProperties(a) 272 if err != nil { 273 return "", nil, err 274 } 275 case *ast.IndexExpr: 276 // IndexExpr is used to represent generic type arguments 277 if !isConfigurableAst(a.X) { 278 var writer strings.Builder 279 if err := ast.Fprint(&writer, nil, expr, nil); err != nil { 280 return "", nil, err 281 } 282 return "", nil, fmt.Errorf("unknown type %s", writer.String()) 283 } 284 var innerType string 285 innerType, innerProps, err = getType(a.Index) 286 if err != nil { 287 return "", nil, err 288 } 289 typ = "configurable " + innerType 290 default: 291 typ = fmt.Sprintf("%T", expr) 292 } 293 294 return typ, innerProps, nil 295} 296 297func isConfigurableAst(expr ast.Expr) bool { 298 switch e := expr.(type) { 299 case *ast.Ident: 300 return e.Name == "Configurable" 301 case *ast.SelectorExpr: 302 if l, ok := e.X.(*ast.Ident); ok && l.Name == "proptools" { 303 if e.Sel.Name == "Configurable" { 304 return true 305 } 306 } 307 } 308 return false 309} 310 311func (ps *PropertyStruct) ExcludeByTag(key, value string) { 312 filterPropsByTag(&ps.Properties, key, value, true) 313} 314 315func (ps *PropertyStruct) IncludeByTag(key, value string) { 316 filterPropsByTag(&ps.Properties, key, value, false) 317} 318 319func filterPropsByTag(props *[]Property, key, value string, exclude bool) { 320 // Create a slice that shares the storage of props but has 0 length. Appending up to 321 // len(props) times to this slice will overwrite the original slice contents 322 filtered := (*props)[:0] 323 for _, x := range *props { 324 if hasTag(x.Tag, key, value) == !exclude { 325 filterPropsByTag(&x.Properties, key, value, exclude) 326 filtered = append(filtered, x) 327 } 328 } 329 330 *props = filtered 331} 332 333func hasTag(tag reflect.StructTag, key, value string) bool { 334 for _, entry := range strings.Split(tag.Get(key), ",") { 335 if entry == value { 336 return true 337 } 338 } 339 return false 340} 341 342func formatText(text string) template.HTML { 343 var html template.HTML 344 lines := strings.Split(text, "\n") 345 preformatted := false 346 for _, line := range lines { 347 r, _ := utf8.DecodeRuneInString(line) 348 indent := unicode.IsSpace(r) 349 if indent && !preformatted { 350 html += "<pre>\n\n" 351 preformatted = true 352 } else if !indent && line != "" && preformatted { 353 html += "</pre>\n" 354 preformatted = false 355 } 356 html += template.HTML(template.HTMLEscapeString(line)) + "\n" 357 } 358 if preformatted { 359 html += "</pre>\n" 360 } 361 return html 362} 363