blob: b1621c0f27800565335a82402b614ea9866ae421 [file] [log] [blame]
// Copyright 2022 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 docgen
import (
"fmt"
"go.fuchsia.dev/fuchsia/tools/cppdocgen/clangdoc"
"io"
"sort"
"strings"
)
// Interface for sorting the record list by function name.
type recordByName []*clangdoc.RecordInfo
func (f recordByName) Len() int {
return len(f)
}
func (f recordByName) Swap(i, j int) {
f[i], f[j] = f[j], f[i]
}
func (f recordByName) Less(i, j int) bool {
return f[i].Name < f[j].Name
}
// See seenFuncs variable in WriteClass reference for how it works. This function will avoid adding
// duplicates and will update the map as it adds functions.
func writeBaseClassFunctions(index *Index, r *clangdoc.RecordInfo, headingLevel int, seenFuncs map[string]bool, f io.Writer) {
// Split out the public member functions.
var funcs []*clangdoc.FunctionInfo
for _, fn := range r.ChildFunctions {
if fn.IsPublic() && !r.IsConstructor(fn) && !r.IsDestructor(fn) {
key := fn.IdentityKey()
if !seenFuncs[key] {
seenFuncs[key] = true
if !commentContains(fn.Description, NoDocTag) {
funcs = append(funcs, fn)
}
}
}
}
if len(funcs) == 0 {
return
}
sort.Sort(functionByName(funcs))
// Re-lookup this record by USR because the name contains the full context on the real
// record but not the Base one.
//
// This can be null if we're not documenting the base class.
fullRecord := index.RecordUsrs[r.USR]
url := recordLink(index, r)
if len(url) == 0 {
// When there's no full record, we need to use the Path+Name as the name (there's
// no full Namespace record) and there is no documentation link.
fmt.Fprintf(f, "%s Inherited from %s\n\n",
headingMarkerAtLevel(headingLevel),
clangdoc.PathNameToFullyQualified(r.Path, r.Name))
} else {
fmt.Fprintf(f, "%s Inherited from [%s](%s)\n\n",
headingMarkerAtLevel(headingLevel), recordFullName(fullRecord), url)
}
if true {
// Write out the functions as declarations.
writePreHeader(f)
for _, fn := range funcs {
writeFunctionDeclaration(fn, "", 0, true, memberFunctionLink(index, r, fn), f)
}
writePreFooter(f)
} else {
// Write out the functions (as links when possible).
for _, fn := range funcs {
url := memberFunctionLink(index, r, fn)
if len(url) == 0 {
fmt.Fprintf(f, " - %s\n\n", fn.Name)
} else {
fmt.Fprintf(f, " - [%s](%s)\n\n", fn.Name, url)
}
}
}
}
func writeRecordReference(settings WriteSettings, index *Index, h *Header, r *clangdoc.RecordInfo, f io.Writer) {
fullName := recordFullName(r)
// Devsite uses {:#htmlId} to give the title a custom ID.
fmt.Fprintf(f, "## %s {:#%s}\n\n",
titleWithTemplateSpecializations(fullName, r.Template, "", recordKind(r)),
recordHtmlId(index, r))
// This prefix is used for function names. Don't include the full scope (like namespaces)
// because this will be printed as a declaration where namespaces are not used. This will
// look weird. There should be enough context with just the class name (and template
// specialization parameters) for it to make sense.
//
// This is used for titles (needs non-syntax highlighted version) and code (wants syntax
// highlighting). The namePrefixCharLen is the length not counting escaping and HTML tags
// which is used for function parameter alignment.
namePrefix := ""
namePrefixWithSyntax := ""
namePrefixCharLen := 0
if r.Template != nil && r.Template.Specialization != nil {
templateParams, templateParamsCharLen := getTemplateParameterList(r.Template.Specialization.Params, false)
namePrefix = r.Name + templateParams + "::"
namePrefixCharLen = len(r.Name) + templateParamsCharLen + 2 // Extra 2 for "::".
// Get syntax-highlighted version (should have the same # visible chars).
templateParams, templateParamsCharLen = getTemplateParameterList(r.Template.Specialization.Params, true)
namePrefixWithSyntax = r.Name + templateParams + "::"
} else {
namePrefix = r.Name + "::"
namePrefixCharLen = len(r.Name) + 2 // Extra 2 for "::".
namePrefixWithSyntax = r.Name + "::"
}
fmt.Fprintf(f, "[Declaration source code](%s)\n\n", settings.locationSourceLink(r.DefLocation))
// Split out the member functions.
var funcs []*clangdoc.FunctionInfo
var ctors []*clangdoc.FunctionInfo
for _, fn := range r.ChildFunctions {
if !fn.IsPublic() {
continue
} else if r.IsConstructor(fn) {
ctors = append(ctors, fn)
} else if r.IsDestructor(fn) {
// Ignore destructors. It's not currently clear how to present this as
// there's almost never any documentation for these. The main relevant part
// is whether the destructor might be private in some cases like internally
// reference counted classes.
} else if !commentContains(fn.Description, NoDocTag) {
funcs = append(funcs, fn)
}
}
groupedFuncs := h.groupFunctions(funcs)
groupedCtors := h.groupFunctions(ctors)
// Collect the public records to output.
var dataMembers []clangdoc.MemberTypeInfo
for _, m := range r.Members {
if m.IsPublic() {
dataMembers = append(dataMembers, m)
}
}
if !commentContains(r.Description, NoDeclTag) {
writeRecordDeclarationBlock(index, r, dataMembers, f)
}
writeComment(index, r.Description, markdownHeading2, f)
// Data member documentation first.
for _, d := range dataMembers {
// Only add separate documentation when there is some. Unlike functions, the
// class/struct code declaration includes all data members and our code often
// doesn't have documentation for obvious ones.
if len(d.Description) > 0 && !commentContains(d.Description, NoDocTag) {
// TODO we can consider adding an anchor ref here if we ever need to
// be able to link to members (currently there is no need for this).
fmt.Fprintf(f, "### %s\n\n", d.Name)
writeComment(index, d.Description, markdownHeading3, f)
}
}
if len(groupedCtors) > 0 {
fmt.Fprintf(f, "### Constructor{:#%s}\n\n", functionHtmlId(ctors[0]))
for _, g := range groupedCtors {
writeFunctionGroupBody(settings, index, g, namePrefix, namePrefixCharLen,
false, f)
}
}
// Tracks whether we have seen a given function so we don't duplicate it. Functions can
// appear multiple times, one for each base class they appear in and once for the class'
// implementation. If an implemented function declaration in base class' base class, it will
// appear three times, once for each base and once for the implementation.
//
// Here, we only want to show it the first time. This is indexed by each function's
// IdentityKey().
seenFuncs := make(map[string]bool)
// This needs to be done in reverse order because the comments are normally associated with the
// lowest-level base class that declares the function.
for i := len(r.Bases) - 1; i >= 0; i-- {
writeBaseClassFunctions(index, r.Bases[i], 3, seenFuncs, f)
}
// Member functions.
for _, g := range groupedFuncs {
// Don't include functions that were included in the base class list above.
// If a section of functions includes both ones from the base class and not, include
// all of them.
hasNonBaseClass := false
for _, fn := range g.Funcs {
if !seenFuncs[fn.IdentityKey()] {
hasNonBaseClass = true
}
}
if hasNonBaseClass {
// Don't fully qualify the name since including namespaces looks too noisy.
// Just include the class name for clarity.
if len(g.ExplicitTitle) != 0 {
fmt.Fprintf(f, "### %s {:#%s}\n\n", g.ExplicitTitle, functionGroupHtmlId(g))
} else {
fmt.Fprintf(f, "### %s%s%s {:#%s}\n\n",
namePrefix, g.Funcs[0].Name,
functionGroupEllipsesParens(g), functionGroupHtmlId(g))
}
writeFunctionGroupBody(settings, index, g, namePrefixWithSyntax,
namePrefixCharLen, true, f)
}
}
}
func recordFullName(r *clangdoc.RecordInfo) string {
return getScopeQualifier(r.Namespace, true) + r.Name
}
// Returns the empty string if the record does not have emitted documentation.
func recordHtmlId(index *Index, r *clangdoc.RecordInfo) string {
// Use the fully-qualified type name as the ID.
//
// TODO(https://fxbug.dev/42070119) Allow links to template specializations (the same name). If this
// function took an Index parameter, it could check for this case and use the USR id for all
// but the first instance.
fullRecord := index.RecordUsrs[r.USR]
if fullRecord == nil {
return ""
}
return recordFullName(fullRecord)
}
// Returns the empty string if the record does not have emitted documentation.
func recordLink(index *Index, r *clangdoc.RecordInfo) string {
fullRecord := index.RecordUsrs[r.USR]
if fullRecord == nil {
return ""
}
return HeaderReferenceFile(fullRecord.DefLocation.Filename) + "#" + recordHtmlId(index, r)
}
// Returns the empty string if the record isn't documented.
func memberFunctionLink(index *Index, r *clangdoc.RecordInfo, f *clangdoc.FunctionInfo) string {
fullRecord := index.RecordUsrs[r.USR]
if fullRecord == nil {
return ""
}
return HeaderReferenceFile(fullRecord.DefLocation.Filename) + "#" + functionHtmlId(f)
}
func recordKind(r *clangdoc.RecordInfo) string {
// The TagType matches the C++ construct except for capitalization ("Class", "Struct", ...).
return strings.ToLower(r.TagType)
}
func writeRecordDeclarationBlock(index *Index, r *clangdoc.RecordInfo, data []clangdoc.MemberTypeInfo, f io.Writer) {
// TODO omit this whole function if there are no namespaces, base classes, or members
// ("class Foo {...};" is pointless by itself).
writePreHeader(f)
// This includes any parent struct/class name but not namespaces.
nsBegin, nsEnd := getNamespaceDecl(r.Namespace)
fmt.Fprintf(f, "%s", nsBegin)
// Template definition.
if r.Template != nil {
writeTemplateDeclaration(*r.Template, f)
fmt.Fprintf(f, "\n")
}
kind := recordKind(r)
scopeQualifier := getScopeQualifier(r.Namespace, false)
fmt.Fprintf(f, "<span class=\"kwd\">%s</span> %s%s", kind, scopeQualifier, r.Name)
// Template specializations.
templateSpecCharLen := 0
if r.Template != nil && r.Template.Specialization != nil {
templateParams, templateParamsCharLen := getTemplateParameterList(
r.Template.Specialization.Params, true)
fmt.Fprintf(f, "%s", templateParams)
templateSpecCharLen = templateParamsCharLen
}
// Rendered character width of the class declaration. This is used to horizontally align the
// derived class references below.
declCharLen := len(kind) + 1 + len(scopeQualifier) + len(r.Name) + templateSpecCharLen
// Base and virtual base class records. The direct base classes are in Parents and
// VirtualParents but this does not have the access record on it (public/private/protected).
// The Bases list is in the correct order so use that as the primary key, and look up those
// in [Virtual]Parents to see if they're direct parents.
parents := make(map[string]bool) // Set of USR ids of direct parents.
for _, p := range r.Parents {
parents[p.USR] = true
}
for _, p := range r.VirtualParents {
parents[p.USR] = true
}
emittedBase := false
for _, b := range r.Bases {
if !parents[b.USR] {
continue // Not a direct parent.
}
const derivedSeparator = " : "
if !emittedBase {
fmt.Fprintf(f, derivedSeparator)
emittedBase = true
} else {
fmt.Fprintf(f, ",\n%s", makeIndent(declCharLen+len(derivedSeparator)))
}
if len(b.Access) > 0 {
fmt.Fprintf(f, "<span class=\"kwd\">%s</span>", strings.ToLower(b.Access))
}
if b.IsVirtual {
fmt.Fprintf(f, "<span class=\"kwd\">virtual</span>")
}
baseName, _ := getEscapedTypeName(clangdoc.PathNameToFullyQualified(b.Path, b.Name))
url := recordLink(index, b)
if len(url) == 0 {
fmt.Fprintf(f, " <span class=\"typ\">%s</span>", baseName)
} else {
fmt.Fprintf(f, " <span class=\"typ\"><a href=\"%s\">%s</a></span>", url, baseName)
}
}
fmt.Fprintf(f, " {")
if len(data) == 0 {
// No members, just output "Foo { ... }" since the functions will be documented
// later and we don't want to imply the class is empty.
fmt.Fprintf(f, " <span class=\"com\">...</span> ")
} else {
// Output data members.
fmt.Fprintf(f, "\n")
// For classes, try to make clear that this only lists the public data.
if r.TagType == "Class" {
fmt.Fprintf(f, " <span class=\"kwd\">public</span>:\n")
fmt.Fprintf(f, " <span class=\"com\">// Public data members:</span>\n")
}
for _, d := range data {
if !commentContains(d.Description, NoDocTag) && !commentContains(d.Description, NoDeclTag) {
tn, _ := getEscapedTypeName(d.TypeRef.QualName)
fmt.Fprintf(f, " <span class=\"typ\">%s</span> %s;\n", tn, d.Name)
}
}
}
fmt.Fprintf(f, "};\n")
fmt.Fprintf(f, "%s", nsEnd)
writePreFooter(f)
}