| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file |
| |
| // This file contains the data model for the clang_doc files. |
| // |
| // The data model was reverse engineered from sample clang_doc output, so may |
| // need to be adjusted; and/or evolved over time as clang_doc evolves. |
| // |
| // In the declared structs the fields are sorted alphanumerically by name. This |
| // does not give a pretty go formatting, but is likely the only way to keep |
| // track of the coverage of the recovered data model. |
| |
| package model |
| |
| import ( |
| "fmt" |
| "io" |
| "strings" |
| |
| "gopkg.in/yaml.v2" |
| ) |
| |
| // fullnameMulticomponent reconstructs a name from the namespace component, |
| // and several name components, of which the more precise ones come later. |
| func fullNameMulticomponent(ns []ID, names ...string) string { |
| s := make([]string, len(ns)) |
| for i, n := range ns { |
| nc := string(n.Name) |
| if nc == "" { |
| nc = "(anonymous)" |
| } |
| if nc == "GlobalNamespace" { |
| nc = "" |
| } |
| s[len(s)-i-1] = nc |
| } |
| for _, name := range names { |
| if name == "" { |
| name = "(anonymous)" |
| } |
| s = append(s, name) |
| } |
| return strings.Join(s, "::") |
| } |
| |
| // fullName reconstructs the fully qualified name of an identifier, given its |
| // unqualified name and namespace components. |
| func fullName(name Name, ns []ID) string { |
| return fullNameMulticomponent(ns, string(name)) |
| } |
| |
| // Needs serdes. Unclear why Type is not the same as TagType. |
| type Type string |
| |
| const ( |
| TypeClass Type = "Class" |
| TypeNamespace Type = "Namespace" |
| TypeRecord Type = "Record" |
| ) |
| |
| var typeFromString = map[string]Type{ |
| "Class": TypeClass, |
| "Namespace": TypeNamespace, |
| "Record": TypeRecord, |
| } |
| |
| var _ yaml.Unmarshaler = (*Type)(nil) |
| |
| func (t *Type) UnmarshalYAML(unmarshal func(interface{}) error) error { |
| var s string |
| if err := unmarshal(&s); err != nil { |
| return fmt.Errorf("could not unmarshal Type: %w", err) |
| } |
| u := typeFromString[s] |
| t = &u |
| return nil |
| } |
| |
| // USR is a unique symbol resolution identifier. |
| type USR string |
| |
| // Name is an unqualified name for a symbol. |
| type Name string |
| |
| // ID contains the complete information about a symbol and its type. |
| type ID struct { |
| // Name is an unqualified name of an identifier |
| Name `yaml:"Name,omitempty"` |
| Path `yaml:"Path,omitempty"` |
| // In contrast to other fields, this one can not be an empbedded type, as |
| // the custom unmarshaller will attempt to submit a YAML representation of |
| // ID to the parser for Type. Not sure why it thinks that would be OK. |
| Type Type `yaml:"Type,omitempty"` |
| // USR seems to be a unique identifier for each symbol. |
| USR `yaml:"USR,omitempty"` |
| } |
| |
| // GetTypeString returns the string encoding of this ID, as a type string. |
| func (i ID) GetTypeString() string { |
| if i.Name != "" { |
| return string(i.Name) |
| } |
| return fmt.Sprintf("%v::%v::%v", i.Type, i.Path, i.USR) |
| } |
| |
| // NamespaceID is a fully qualified namespace identifier |
| type NamespaceID struct { |
| ID ID `yaml:",inline"` |
| IsInGlobalNamespace bool `yaml:"IsInGlobalNamespace,omitempty"` |
| } |
| |
| type Path string |
| |
| type DefLocation struct { |
| Filename string `yaml:"Filename"` |
| LineNumber int `yaml:"LineNumber"` |
| } |
| |
| type ParamType struct { |
| Name `yaml:"Name"` |
| Type ID `yaml:"Type"` |
| } |
| |
| // TypeName returns the type of this parameter. |
| func (p ParamType) TypeName() string { |
| ret := []string{p.Type.GetTypeString()} |
| if p.Name != "" { |
| ret = append(ret, string(p.Name)) |
| } |
| return strings.Join(ret, " ") |
| } |
| |
| type Access string |
| |
| const ( |
| AccessPublic Access = "Public" |
| AccessPrivate Access = "Private" |
| AccessProtected Access = "Protected" |
| ) |
| |
| var accessFromString = map[string]Access{ |
| "Public": AccessPublic, |
| "Private": AccessPrivate, |
| "Protected": AccessProtected, |
| } |
| |
| var _ yaml.Unmarshaler = (*Access)(nil) |
| |
| func (a *Access) UnmarshalYAML(unmarshal func(interface{}) error) error { |
| var s string |
| if err := unmarshal(&s); err != nil { |
| return fmt.Errorf("could not unmarshal Access: %w", err) |
| } |
| u := accessFromString[s] |
| a = &u |
| return nil |
| } |
| |
| // ChildFunction describes a function (possibly a method, too), enclosed within |
| // a parent (namespace in case of free functions; class in case of methods). |
| type ChildFunction struct { |
| Access Access `yaml:"Access"` |
| DefLocation `yaml:"DefLocation"` |
| Description GenericJSON `yaml:"Description,omitempty"` |
| IsMethod bool `yaml:"IsMethod"` |
| Location []DefLocation `yaml:"Location,omitempty"` |
| Name `yaml:"Name"` |
| Namespace []ID `yaml:"Namespace"` |
| Params []ParamType `yaml:"Params"` |
| Parent ID `yaml:"Parent"` |
| ReturnType ParamType `yaml:"ReturnType,omitempty"` |
| USR `yaml:"USR"` |
| } |
| |
| // fullName returns the fully qualified name of a function. |
| func (c ChildFunction) fullName() string { |
| return fullName(c.Name, c.Namespace) |
| } |
| |
| // GenericJSON is a catch-all for parts of the report we do not |
| // care about currently. |
| type GenericJSON interface{} |
| |
| // ChildEnum defines an enumeration (usually defined as a child of a namespace |
| // it is nested in). |
| type ChildEnum struct { |
| DefLocation `yaml:"DefLocation"` |
| Description GenericJSON `yaml:"Description"` |
| Members []string `yaml:"Members,omitempty"` |
| Name `yaml:"Name"` |
| Namespace []ID `yaml:"Namespace"` |
| Scoped bool `yaml:"Scoped,omitempty"` |
| USR `yaml:"USR"` |
| } |
| |
| // Member defines a class member. |
| type Member struct { |
| // Access denotes one of private, protected or public access. |
| Access Access `yaml:"Access,omitempty"` |
| Name `yaml:"Name,omitempty"` |
| Type ID `yaml:"Type"` |
| } |
| |
| // Aggregate is the top-level entity in each of the YAML files that clang-doc |
| // produces. |
| type Aggregate struct { |
| // Access shows whether the aggregate is public or not. |
| Access Access `yaml:"Access"` |
| /// Bases contain references to base classes for this aggregate, if applicable. |
| Bases []Aggregate `yaml:"Bases"` |
| ChildEnums []ChildEnum `yaml:"ChildEnums,omitempty"` |
| ChildFunctions []ChildFunction `yaml:"ChildFunctions,omitempty"` |
| ChildNamespaces []NamespaceID `yaml:"ChildNamespaces,omitempty"` |
| ChildRecords []ID `yaml:"ChildRecords,omitempty"` |
| DefLocation `yaml:"DefLocation"` |
| Description GenericJSON `yaml:"Description,omitempty"` |
| IsParent bool `yaml:"IsParent"` |
| Location []DefLocation `yaml:"Location,omitempty"` |
| Members []Member `yaml:"Members,omitempty"` |
| Name `yaml:"Name"` |
| Namespace []ID `yaml:"Namespace"` |
| Parents []ID `yaml:"Parents,omitempty"` |
| Path `yaml:"Path"` |
| TagType Type `yaml:"TagType,omitempty"` |
| USR `yaml:"USR"` |
| } |
| |
| // ParseYAML reads an Aggregate from the supplied reader. |
| // If lenient is set, decode errors are reported as an empty aggregate |
| // instead of a stopping error. |
| func ParseYAML(r io.Reader, lenient bool) (Aggregate, error) { |
| d := yaml.NewDecoder(r) |
| // Without this, we'd have no idea whether YAML parsing made sense. |
| d.SetStrict(true) |
| var ret Aggregate |
| if err := d.Decode(&ret); err != nil { |
| if lenient { |
| return ret, nil |
| } |
| return ret, fmt.Errorf("while reading YAML: %w\n", err) |
| } |
| return ret, nil |
| } |