blob: db0fae1282e32298cc8ccd745489ca6c4864d2ae [file] [log] [blame]
// 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
}