blob: 504ab322891097c6735c31b1de587146b6b5f854 [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
package model
import (
"encoding/json"
"fmt"
"io"
"regexp"
"sort"
"strings"
)
// ReportKind is the type of a platform surface element.
type ReportKind string
const (
ReportKindFunction ReportKind = "function"
ReportKindMethod ReportKind = "method"
ReportKindEnum ReportKind = "enum"
ReportKindEnumMember ReportKind = "enum/member"
ReportKindRecord ReportKind = "record"
ReportKindRecordMember ReportKind = "record/member"
)
var reportKindStringToEnum = map[string]ReportKind{
"function": ReportKindFunction,
"method": ReportKindMethod,
"enum": ReportKindEnum,
"enum/member": ReportKindEnumMember,
"record": ReportKindRecord,
"record/member": ReportKindRecordMember,
}
var _ json.Marshaler = (*ReportKind)(nil)
func (r ReportKind) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%q", r)), nil
}
var _ json.Unmarshaler = (*ReportKind)(nil)
func (r *ReportKind) UnmarshalJSON(value []byte) error {
var s string
if err := json.Unmarshal(value, &s); err != nil {
return fmt.Errorf("while recovering %v into string: %w", string(value), err)
}
var (
e ReportKind
ok bool
)
if e, ok = reportKindStringToEnum[s]; !ok {
return fmt.Errorf("could not parse ReportKind from %v", s)
}
*r = e
return nil
}
// ReportAccess is the type of access of a platform surface element.
type ReportAccess string
const (
ReportAccessPublic ReportAccess = "public"
ReportAccessProtected ReportAccess = "protected"
ReportAccessPrivate ReportAccess = "private"
)
var reportAccessStringToEnum = map[string]ReportAccess{
"": ReportAccessPublic,
"public": ReportAccessPublic,
"protected": ReportAccessProtected,
"private": ReportAccessPrivate,
}
var accessToReportAccess = map[Access]ReportAccess{
AccessPublic: ReportAccessPublic,
AccessPrivate: ReportAccessPrivate,
AccessProtected: ReportAccessProtected,
}
var _ json.Marshaler = (*ReportAccess)(nil)
func (r ReportAccess) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%q", r)), nil
}
var _ json.Unmarshaler = (*ReportAccess)(nil)
func (r *ReportAccess) UnmarshalJSON(value []byte) error {
var s string
if err := json.Unmarshal(value, &s); err != nil {
return fmt.Errorf("while recovering %v into string: %w", string(value), err)
}
var (
e ReportAccess
ok bool
)
if e, ok = reportAccessStringToEnum[s]; !ok {
return fmt.Errorf("could not parse ReportAccess from %v", s)
}
*r = e
return nil
}
// ReportItem is the format of a single report item. Expect this struct will
// grow over time to include more extracted information.
type ReportItem struct {
Name string `json:"name"`
// Filename is the file name in which the report item can be found.
Filename string `json:"file,omitempty"`
// Line is the line number (1-based) in Filename where the report item is defined.
LineNumber int `json:"line,omitempty"`
// Kind is the type of the item. It is required.
Kind ReportKind `json:"kind"`
// Type is the declaration type of an item, if there is one.
Type string `json:"type,omitempty"`
// ReturnType is a return type of a function or method, if there is one.
ReturnType string `json:"return_type,omitempty"`
// Params is the type declaration of all parameters, if there are some.
Params string `json:"params,omitempty"`
// Access lets us know if the item is publicly accessible. If omitted,
// the default is public.
Access ReportAccess `json:"access,omitempty"`
}
// Report is the format of the output report for the clang doc filter.
type Report struct {
Items []ReportItem `json:"items"`
// fileregex contains the file regexes to include when adding file names to report, if set.
// If unset, it has no effect.
fileregex []regexp.Regexp
// symregex contains the file regexes to include when adding symbol names to report, if set.
// If unset, it has no effect.
symregex []regexp.Regexp
}
// SetFileRegexes sets the regular expressions used to match the filename of
// the symbols. Only symbols that match at least one regexp will be included,
// if the regexp is specified. Otherwise, everything matches.
func (r *Report) SetFileRegexes(regexes []string) error {
return addRegexes(&r.fileregex, regexes)
}
func (r *Report) SetSymRegexes(regexes []string) error {
return addRegexes(&r.symregex, regexes)
}
// addRegexes compiles and adds regexes to a list.
func addRegexes(r *[]regexp.Regexp, regexes []string) error {
for _, rx := range regexes {
rc, err := regexp.Compile(rx)
if err != nil {
return fmt.Errorf("could not parse regexp: %+v: %w", r, err)
}
*r = append(*r, *rc)
}
return nil
}
func (r *Report) matchFilename(fn string) bool {
return matchToAnyRegex(fn, r.fileregex)
}
func (r *Report) matchSymbol(sym string) bool {
return matchToAnyRegex(sym, r.symregex)
}
func matchToAnyRegex(sym string, regexes []regexp.Regexp) bool {
if len(regexes) == 0 {
// If we defined no regexes, match everything.
return true
}
for _, rx := range regexes {
if rx.Match([]byte(sym)) {
return true
}
}
return false
}
// AddEnumMember adds an enum member to the plasa report.
func (r *Report) AddEnumMember(enumName, fn, member string) error {
fullName := fullNameMulticomponent([]ID{}, enumName, member)
if r.matchFilename(fn) && r.matchSymbol(fullName) {
i := ReportItem{
Name: fullName,
Kind: ReportKindEnumMember,
}
r.Items = append(r.Items, i)
}
return nil
}
// AddChildEnum adds the child enum and its members to the plasa report.
func (r *Report) AddChildEnum(c ChildEnum) error {
// Push this into ChildEnum.
fullName := fullName(c.Name, c.Namespace)
fn := c.DefLocation.Filename
if r.matchFilename(fn) && r.matchSymbol(fullName) {
i := ReportItem{
Name: fullName,
Kind: ReportKindEnum,
}
r.Items = append(r.Items, i)
}
// Add each enum member too.
for _, m := range c.Members {
if err := r.AddEnumMember(fullName, fn, m); err != nil {
return fmt.Errorf("while adding enum member: %v::%v: %w", fullName, m, err)
}
}
return nil
}
func (r *Report) AddChildFunction(c ChildFunction) error {
f := c.fullName()
n := c.DefLocation.Filename
if r.matchFilename(n) && r.matchSymbol(f) {
i := ReportItem{
Name: f,
Filename: n,
LineNumber: c.DefLocation.LineNumber,
ReturnType: c.ReturnType.TypeName(),
}
if c.IsMethod {
i.Kind = ReportKindMethod
} else {
i.Kind = ReportKindFunction
}
var params []string
for _, p := range c.Params {
params = append(params, p.TypeName())
}
i.Params = fmt.Sprintf("(%v)", strings.Join(params, ","))
// Needs return type and params.
r.Items = append(r.Items, i)
}
return nil
}
func (r *Report) AddMember(parentName string, c Member) error {
fullName := fullNameMulticomponent([]ID{}, parentName, string(c.Name))
i := ReportItem{
Name: fullName,
Kind: ReportKindRecordMember,
Type: c.Type.GetTypeString(),
Access: accessToReportAccess[c.Access],
}
r.Items = append(r.Items, i)
return nil
}
// Add inserts an Aggregate into the report.
func (r *Report) AddAggregate(a Aggregate) error {
f := fullName(a.Name, a.Namespace)
n := a.DefLocation.Filename
if r.matchFilename(n) && r.matchSymbol(f) {
i := ReportItem{
Name: f,
Kind: ReportKindRecord,
LineNumber: a.DefLocation.LineNumber,
Filename: n,
Access: accessToReportAccess[a.Access],
}
r.Items = append(r.Items, i)
}
// Recurse into components and ship them out too.
for _, c := range a.ChildEnums {
if err := r.AddChildEnum(c); err != nil {
return fmt.Errorf("while adding child enum: %v: %w", c, err)
}
}
for _, c := range a.ChildFunctions {
if err := r.AddChildFunction(c); err != nil {
return fmt.Errorf("while adding child function: %v: %w", c, err)
}
}
for _, c := range a.Members {
if err := r.AddMember(f, c); err != nil {
return fmt.Errorf("while adding a member: %v::%v: %w", f, c, err)
}
}
return nil
}
// WriteJSON writes the contents of the report in JSON format to the supplied
// writer.
func (r Report) WriteJSON(w io.Writer) error {
// Ensure the output is stable.
if r.Items != nil {
sort.SliceStable(r.Items, func(i, j int) bool {
return strings.ToLower(r.Items[i].Name) < strings.ToLower(r.Items[j].Name)
})
}
e := json.NewEncoder(w)
// We do not expect to need HTML escaping.
e.SetEscapeHTML(false)
// This indentation format is compatible with `fx format-code`.
e.SetIndent("", " ")
if err := e.Encode(r); err != nil {
return fmt.Errorf("while encoding JSON output: %w", err)
}
return nil
}
// ReadReportJSON reads the contents of the report in JSON format from the
// supplied reader.
func ReadReportJSON(r io.Reader) (Report, error) {
d := json.NewDecoder(r)
// Verifying the parsing gets confusing if we're lenient about unknown
// fields.
d.DisallowUnknownFields()
var ret Report
if err := d.Decode(&ret); err != nil {
return ret, fmt.Errorf("while reading Report as JSON: %w:", err)
}
return ret, nil
}