| // 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" |
| "log" |
| "path" |
| "sort" |
| ) |
| |
| type IndexSettings struct { |
| // Path to the build directory where the clang-doc paths are relative to. |
| BuildDir string |
| |
| // Names of the header files we want to index. |
| Headers map[string]struct{} |
| } |
| |
| func MakeIndexSettings(buildDir string) IndexSettings { |
| return IndexSettings{ |
| BuildDir: buildDir, |
| Headers: make(map[string]struct{}), |
| } |
| } |
| |
| func (s IndexSettings) ShouldIndexInHeader(hdrName string) bool { |
| _, found := s.Headers[hdrName] |
| return found |
| } |
| |
| // HeaderPath returns the path for the given build-dir-relative (what clang-doc generates) header |
| // path. |
| func (s IndexSettings) HeaderPath(h string) string { |
| return path.Join(s.BuildDir, h) |
| } |
| |
| // Flattened version of everything stored in this library. |
| type Index struct { |
| // All nonmember functions/records in all non-anonymous namespaces, indexed by the USR/Name. |
| // Since functions overloads may have name collisions, FunctionNames may not include |
| // everything (it currently stores the last one of that name encountered). FunctionUsrs |
| // will include everything since Clang will generate a unique USR for each function. |
| // |
| // This could be enhanced in the future if needed to map a name to a list of functions. But |
| // currently we only use the name index to generate links where there can only be one |
| // destination anyway. |
| FunctionUsrs map[string]*clangdoc.FunctionInfo |
| FunctionNames map[string]*clangdoc.FunctionInfo |
| |
| // Toplevel records (classes, structs, and unions), indexed by the Name/USR. |
| RecordNames map[string]*clangdoc.RecordInfo |
| RecordUsrs map[string]*clangdoc.RecordInfo |
| |
| // All unique header files in this library indexed by their file name. |
| Headers map[string]*Header |
| |
| // All unique preprocessor defines in this library indexed by their name. |
| Defines map[string]*Define |
| |
| // All toplevel and namespaced enums indexed by their name. |
| Enums map[string]*clangdoc.EnumInfo |
| |
| // All typedefs indexed by their name. |
| Typedefs map[string]*clangdoc.TypedefInfo |
| } |
| |
| // Returns a sorted list of all functions. |
| func (index Index) AllFunctions() []*clangdoc.FunctionInfo { |
| result := make([]*clangdoc.FunctionInfo, 0, len(index.FunctionUsrs)) |
| for _, fn := range index.FunctionUsrs { |
| result = append(result, fn) |
| } |
| sort.Sort(functionByName(result)) |
| return result |
| } |
| |
| // Returns a sorted list of all records. |
| func (index Index) AllRecords() []*clangdoc.RecordInfo { |
| result := make([]*clangdoc.RecordInfo, 0, len(index.RecordUsrs)) |
| for _, r := range index.RecordUsrs { |
| result = append(result, r) |
| } |
| sort.Sort(recordByName(result)) |
| return result |
| } |
| |
| // AllDefines returns a sorted list of all defines. |
| func (index Index) AllDefines() []*Define { |
| result := make([]*Define, 0, len(index.Defines)) |
| for _, d := range index.Defines { |
| result = append(result, d) |
| } |
| sort.Sort(defineByName(result)) |
| return result |
| } |
| |
| func (index *Index) HeaderForFileName(file string) *Header { |
| header := index.Headers[file] |
| if header == nil { |
| // New item, init Header struct. |
| header = &Header{Name: file} |
| index.Headers[file] = header |
| } |
| return header |
| } |
| |
| type FunctionGroup struct { |
| // Nonempty when an explicit title is given. Empty means just use the first name. |
| ExplicitTitle string |
| |
| Funcs []*clangdoc.FunctionInfo |
| } |
| |
| // Interface for sorting a function group by the first function's name. |
| type functionGroupByTitle []*FunctionGroup |
| |
| func (f functionGroupByTitle) Len() int { |
| return len(f) |
| } |
| func (f functionGroupByTitle) Swap(i, j int) { |
| f[i], f[j] = f[j], f[i] |
| } |
| func (f functionGroupByTitle) Less(i, j int) bool { |
| getName := func(g *FunctionGroup) string { |
| if len(g.ExplicitTitle) > 0 { |
| return g.ExplicitTitle |
| } |
| return g.Funcs[0].Name |
| } |
| return getName(f[i]) < getName(f[j]) |
| } |
| |
| // Interface for sorting an enum list by name. |
| type enumByName []*clangdoc.EnumInfo |
| |
| func (f enumByName) Len() int { |
| return len(f) |
| } |
| func (f enumByName) Swap(i, j int) { |
| f[i], f[j] = f[j], f[i] |
| } |
| func (f enumByName) Less(i, j int) bool { |
| return f[i].Name < f[j].Name |
| } |
| |
| // Interface for sorting a typedef list by name. |
| type typedefByName []*clangdoc.TypedefInfo |
| |
| func (f typedefByName) Len() int { |
| return len(f) |
| } |
| func (f typedefByName) Swap(i, j int) { |
| f[i], f[j] = f[j], f[i] |
| } |
| func (f typedefByName) Less(i, j int) bool { |
| return f[i].Name < f[j].Name |
| } |
| |
| type DefineGroup struct { |
| // Nonempty when an explicit title is given. Empty means just use the first name. |
| ExplicitTitle string |
| |
| Defines []*Define |
| } |
| |
| // Interface for sorting a define group by the first define's name. |
| type defineGroupByTitle []*DefineGroup |
| |
| func (f defineGroupByTitle) Len() int { |
| return len(f) |
| } |
| func (f defineGroupByTitle) Swap(i, j int) { |
| f[i], f[j] = f[j], f[i] |
| } |
| func (f defineGroupByTitle) Less(i, j int) bool { |
| getName := func(g *DefineGroup) string { |
| if len(g.ExplicitTitle) > 0 { |
| return g.ExplicitTitle |
| } |
| return g.Defines[0].Name |
| } |
| return getName(f[i]) < getName(f[j]) |
| } |
| |
| func indexFunction(settings IndexSettings, index *Index, f *clangdoc.FunctionInfo) { |
| // When the function has no separate declaration location, use the definition location. |
| var loc = f.GetLocation() |
| if len(loc.Filename) == 0 { |
| fmt.Printf("WARNING: Function %s does not have a location.\n", f.Name) |
| } else if settings.ShouldIndexInHeader(loc.Filename) && |
| !commentContains(f.Description, NoDocTag) { |
| // TODO(brettw) there can be multiple locations! I think this might be for every |
| // forward declaration. In this case we will want to pick the "best" one. |
| index.FunctionUsrs[f.USR] = f |
| index.FunctionNames[functionFullName(f)] = f |
| |
| decl := loc.Filename |
| |
| header := index.HeaderForFileName(decl) |
| header.Functions = append(header.Functions, f) |
| index.Headers[decl] = header |
| } |
| } |
| |
| func indexRecord(settings IndexSettings, index *Index, r *clangdoc.RecordInfo) { |
| if settings.ShouldIndexInHeader(r.DefLocation.Filename) && |
| !commentContains(r.Description, NoDocTag) { |
| index.RecordUsrs[r.USR] = r |
| index.RecordNames[recordFullName(r)] = r |
| |
| header := index.HeaderForFileName(r.DefLocation.Filename) |
| header.Records = append(header.Records, r) |
| } |
| } |
| |
| func indexEnum(settings IndexSettings, index *Index, e *clangdoc.EnumInfo) { |
| if settings.ShouldIndexInHeader(e.DefLocation.Filename) && |
| !commentContains(e.Description, NoDocTag) { |
| index.Enums[enumFullName(e)] = e |
| |
| header := index.HeaderForFileName(e.DefLocation.Filename) |
| header.Enums = append(header.Enums, e) |
| } |
| } |
| |
| func indexTypedef(settings IndexSettings, index *Index, t *clangdoc.TypedefInfo) { |
| if settings.ShouldIndexInHeader(t.DefLocation.Filename) && |
| !commentContains(t.Description, NoDocTag) { |
| index.Typedefs[typedefFullName(t)] = t |
| |
| header := index.HeaderForFileName(t.DefLocation.Filename) |
| header.Typedefs = append(header.Typedefs, t) |
| } |
| } |
| |
| func indexNamespace(settings IndexSettings, index *Index, r *clangdoc.NamespaceInfo) { |
| for _, f := range r.ChildFunctions { |
| indexFunction(settings, index, f) |
| } |
| for _, c := range r.ChildNamespaces { |
| indexNamespace(settings, index, c) |
| } |
| for _, r := range r.ChildRecords { |
| indexRecord(settings, index, r) |
| } |
| for _, e := range r.ChildEnums { |
| indexEnum(settings, index, e) |
| } |
| for _, t := range r.ChildTypedefs { |
| indexTypedef(settings, index, t) |
| } |
| } |
| |
| // Returns true if the two locations have a comment or a blank line separating them. |
| func (h *Header) hasSeparatorsBetweenLocations(a clangdoc.Location, b clangdoc.Location) bool { |
| // Note: line numbers are 1-based. |
| if a.LineNumber < 1 || a.LineNumber > len(h.LineClasses) || |
| b.LineNumber < 1 || b.LineNumber > len(h.LineClasses) { |
| // Something is out-of-range, assume separated. |
| return true |
| } |
| if a.LineNumber > b.LineNumber { |
| log.Fatal("Line numbers not in order") |
| } |
| |
| for line := a.LineNumber + 1; line < b.LineNumber; line++ { |
| // The array is 0-indexed while |line| is 1-indexed. |
| if h.LineClasses[line-1] == LineClassBlank || h.LineClasses[line-1] == LineClassComment { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func (h *Header) groupFunctions(f []*clangdoc.FunctionInfo) []*FunctionGroup { |
| if len(f) == 0 { |
| return nil |
| } |
| |
| byLoc := make([]*clangdoc.FunctionInfo, len(f)) |
| copy(byLoc, f) |
| sort.Sort(functionByLocation(byLoc)) |
| |
| groups := make([]*FunctionGroup, 0, len(f)) |
| |
| // Makes a new group containing the given function. The group is appended to the list. |
| makeNewGroup := func(firstFunc *clangdoc.FunctionInfo) (g *FunctionGroup) { |
| g = &FunctionGroup{} |
| g.Funcs = make([]*clangdoc.FunctionInfo, 1) |
| g.Funcs[0] = firstFunc |
| headingLine, _ := extractCommentHeading1(firstFunc.Description) |
| g.ExplicitTitle = trimMarkdownHeadings(headingLine) |
| |
| groups = append(groups, g) |
| return g |
| } |
| |
| curGroup := makeNewGroup(byLoc[0]) |
| |
| for i := 1; i < len(byLoc); i++ { |
| hasSeparators := h.hasSeparatorsBetweenLocations( |
| byLoc[i-1].GetLocation(), byLoc[i].GetLocation()) |
| nameMatches := curGroup.Funcs[0].Name == f[i].Name |
| |
| if !hasSeparators && (nameMatches || len(curGroup.ExplicitTitle) > 0) { |
| // Grouped with previous function |
| curGroup.Funcs = append(curGroup.Funcs, byLoc[i]) |
| } else { |
| // Not grouped, this function starts a new one. |
| curGroup = makeNewGroup(byLoc[i]) |
| } |
| } |
| |
| sort.Sort(functionGroupByTitle(groups)) |
| return groups |
| } |
| |
| func (h *Header) groupDefines(allDefines []*Define) []*DefineGroup { |
| if len(allDefines) == 0 { |
| return nil |
| } |
| |
| byLoc := make([]*Define, len(allDefines)) |
| copy(byLoc, allDefines) |
| sort.Sort(defineByLocation(byLoc)) |
| |
| groups := make([]*DefineGroup, 0, len(allDefines)) |
| |
| // Makes a new group containing the given define. The group is appended to the list. |
| makeNewGroup := func(firstDefine *Define) (g *DefineGroup) { |
| g = &DefineGroup{} |
| g.Defines = make([]*Define, 1) |
| g.Defines[0] = firstDefine |
| headingLine, _ := extractCommentHeading1(firstDefine.Description) |
| g.ExplicitTitle = trimMarkdownHeadings(headingLine) |
| |
| groups = append(groups, g) |
| return g |
| } |
| |
| curGroup := makeNewGroup(byLoc[0]) |
| |
| for i := 1; i < len(byLoc); i++ { |
| // Assume if there's no location info there is no separator. |
| hasSeparators := h.hasSeparatorsBetweenLocations(byLoc[i-1].Location, byLoc[i].Location) |
| |
| // Unlike functions, there is no name matching logic because defines can't have |
| // overloaded names. |
| if !hasSeparators && len(curGroup.ExplicitTitle) > 0 { |
| // Grouped with previous function |
| curGroup.Defines = append(curGroup.Defines, byLoc[i]) |
| } else { |
| // Not grouped, this function starts a new one. |
| curGroup = makeNewGroup(byLoc[i]) |
| } |
| } |
| |
| sort.Sort(defineGroupByTitle(groups)) |
| return groups |
| } |
| |
| func makeEmptyIndex() Index { |
| index := Index{} |
| index.FunctionUsrs = make(map[string]*clangdoc.FunctionInfo) |
| index.FunctionNames = make(map[string]*clangdoc.FunctionInfo) |
| index.RecordNames = make(map[string]*clangdoc.RecordInfo) |
| index.RecordUsrs = make(map[string]*clangdoc.RecordInfo) |
| index.Headers = make(map[string]*Header) |
| index.Defines = make(map[string]*Define) |
| index.Enums = make(map[string]*clangdoc.EnumInfo) |
| index.Typedefs = make(map[string]*clangdoc.TypedefInfo) |
| return index |
| } |
| |
| func MakeIndex(settings IndexSettings, r *clangdoc.NamespaceInfo) Index { |
| index := makeEmptyIndex() |
| indexNamespace(settings, &index, r) |
| |
| // Get the header comments and #defines for all the headers. |
| for name, h := range index.Headers { |
| headerValues := ReadHeader(settings.HeaderPath(name)) |
| h.Description = headerValues.Description |
| h.Defines = headerValues.Defines |
| h.LineClasses = headerValues.Classes |
| |
| // Add the defines to the global index. |
| for _, d := range headerValues.Defines { |
| index.Defines[d.Name] = d |
| } |
| |
| // Apply grouping. |
| h.FunctionGroups = h.groupFunctions(h.Functions) |
| h.DefineGroups = h.groupDefines(h.Defines) |
| } |
| |
| return index |
| } |