xref: /llvm-project/clang-tools-extra/clang-doc/HTMLGenerator.cpp (revision f8a469fc572778d05b72f34a772082cf3abd3cda)
1 //===-- HTMLGenerator.cpp - HTML Generator ----------------------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "Generators.h"
10 #include "Representation.h"
11 #include "clang/Basic/Version.h"
12 #include "llvm/ADT/StringExtras.h"
13 #include "llvm/ADT/StringRef.h"
14 #include "llvm/ADT/StringSet.h"
15 #include "llvm/Support/FileSystem.h"
16 #include "llvm/Support/JSON.h"
17 #include "llvm/Support/Path.h"
18 #include "llvm/Support/raw_ostream.h"
19 #include <string>
20 
21 using namespace llvm;
22 
23 namespace clang {
24 namespace doc {
25 
26 namespace {
27 
28 class HTMLTag {
29 public:
30   // Any other tag can be added if required
31   enum TagType {
32     TAG_A,
33     TAG_DIV,
34     TAG_FOOTER,
35     TAG_H1,
36     TAG_H2,
37     TAG_H3,
38     TAG_HEADER,
39     TAG_LI,
40     TAG_LINK,
41     TAG_MAIN,
42     TAG_META,
43     TAG_OL,
44     TAG_P,
45     TAG_SCRIPT,
46     TAG_SPAN,
47     TAG_TITLE,
48     TAG_UL,
49   };
50 
51   HTMLTag() = default;
52   constexpr HTMLTag(TagType Value) : Value(Value) {}
53 
54   operator TagType() const { return Value; }
55   operator bool() = delete;
56 
57   bool IsSelfClosing() const;
58   llvm::SmallString<16> ToString() const;
59 
60 private:
61   TagType Value;
62 };
63 
64 enum NodeType {
65   NODE_TEXT,
66   NODE_TAG,
67 };
68 
69 struct HTMLNode {
70   HTMLNode(NodeType Type) : Type(Type) {}
71   virtual ~HTMLNode() = default;
72 
73   virtual void Render(llvm::raw_ostream &OS, int IndentationLevel) = 0;
74   NodeType Type; // Type of node
75 };
76 
77 struct TextNode : public HTMLNode {
78   TextNode(const Twine &Text)
79       : HTMLNode(NodeType::NODE_TEXT), Text(Text.str()) {}
80 
81   std::string Text; // Content of node
82   void Render(llvm::raw_ostream &OS, int IndentationLevel) override;
83 };
84 
85 struct TagNode : public HTMLNode {
86   TagNode(HTMLTag Tag) : HTMLNode(NodeType::NODE_TAG), Tag(Tag) {}
87   TagNode(HTMLTag Tag, const Twine &Text) : TagNode(Tag) {
88     Children.emplace_back(std::make_unique<TextNode>(Text.str()));
89   }
90 
91   HTMLTag Tag; // Name of HTML Tag (p, div, h1)
92   std::vector<std::unique_ptr<HTMLNode>> Children; // List of child nodes
93   std::vector<std::pair<std::string, std::string>>
94       Attributes; // List of key-value attributes for tag
95 
96   void Render(llvm::raw_ostream &OS, int IndentationLevel) override;
97 };
98 
99 constexpr const char *kDoctypeDecl = "<!DOCTYPE html>";
100 
101 struct HTMLFile {
102   std::vector<std::unique_ptr<HTMLNode>> Children; // List of child nodes
103   void Render(llvm::raw_ostream &OS) {
104     OS << kDoctypeDecl << "\n";
105     for (const auto &C : Children) {
106       C->Render(OS, 0);
107       OS << "\n";
108     }
109   }
110 };
111 
112 } // namespace
113 
114 bool HTMLTag::IsSelfClosing() const {
115   switch (Value) {
116   case HTMLTag::TAG_META:
117   case HTMLTag::TAG_LINK:
118     return true;
119   case HTMLTag::TAG_A:
120   case HTMLTag::TAG_DIV:
121   case HTMLTag::TAG_FOOTER:
122   case HTMLTag::TAG_H1:
123   case HTMLTag::TAG_H2:
124   case HTMLTag::TAG_H3:
125   case HTMLTag::TAG_HEADER:
126   case HTMLTag::TAG_LI:
127   case HTMLTag::TAG_MAIN:
128   case HTMLTag::TAG_OL:
129   case HTMLTag::TAG_P:
130   case HTMLTag::TAG_SCRIPT:
131   case HTMLTag::TAG_SPAN:
132   case HTMLTag::TAG_TITLE:
133   case HTMLTag::TAG_UL:
134     return false;
135   }
136   llvm_unreachable("Unhandled HTMLTag::TagType");
137 }
138 
139 llvm::SmallString<16> HTMLTag::ToString() const {
140   switch (Value) {
141   case HTMLTag::TAG_A:
142     return llvm::SmallString<16>("a");
143   case HTMLTag::TAG_DIV:
144     return llvm::SmallString<16>("div");
145   case HTMLTag::TAG_FOOTER:
146     return llvm::SmallString<16>("footer");
147   case HTMLTag::TAG_H1:
148     return llvm::SmallString<16>("h1");
149   case HTMLTag::TAG_H2:
150     return llvm::SmallString<16>("h2");
151   case HTMLTag::TAG_H3:
152     return llvm::SmallString<16>("h3");
153   case HTMLTag::TAG_HEADER:
154     return llvm::SmallString<16>("header");
155   case HTMLTag::TAG_LI:
156     return llvm::SmallString<16>("li");
157   case HTMLTag::TAG_LINK:
158     return llvm::SmallString<16>("link");
159   case HTMLTag::TAG_MAIN:
160     return llvm::SmallString<16>("main");
161   case HTMLTag::TAG_META:
162     return llvm::SmallString<16>("meta");
163   case HTMLTag::TAG_OL:
164     return llvm::SmallString<16>("ol");
165   case HTMLTag::TAG_P:
166     return llvm::SmallString<16>("p");
167   case HTMLTag::TAG_SCRIPT:
168     return llvm::SmallString<16>("script");
169   case HTMLTag::TAG_SPAN:
170     return llvm::SmallString<16>("span");
171   case HTMLTag::TAG_TITLE:
172     return llvm::SmallString<16>("title");
173   case HTMLTag::TAG_UL:
174     return llvm::SmallString<16>("ul");
175   }
176   llvm_unreachable("Unhandled HTMLTag::TagType");
177 }
178 
179 void TextNode::Render(llvm::raw_ostream &OS, int IndentationLevel) {
180   OS.indent(IndentationLevel * 2);
181   printHTMLEscaped(Text, OS);
182 }
183 
184 void TagNode::Render(llvm::raw_ostream &OS, int IndentationLevel) {
185   // Children nodes are rendered in the same line if all of them are text nodes
186   bool InlineChildren = true;
187   for (const auto &C : Children)
188     if (C->Type == NodeType::NODE_TAG) {
189       InlineChildren = false;
190       break;
191     }
192   OS.indent(IndentationLevel * 2);
193   OS << "<" << Tag.ToString();
194   for (const auto &A : Attributes)
195     OS << " " << A.first << "=\"" << A.second << "\"";
196   if (Tag.IsSelfClosing()) {
197     OS << "/>";
198     return;
199   }
200   OS << ">";
201   if (!InlineChildren)
202     OS << "\n";
203   bool NewLineRendered = true;
204   for (const auto &C : Children) {
205     int ChildrenIndentation =
206         InlineChildren || !NewLineRendered ? 0 : IndentationLevel + 1;
207     C->Render(OS, ChildrenIndentation);
208     if (!InlineChildren && (C == Children.back() ||
209                             (C->Type != NodeType::NODE_TEXT ||
210                              (&C + 1)->get()->Type != NodeType::NODE_TEXT))) {
211       OS << "\n";
212       NewLineRendered = true;
213     } else
214       NewLineRendered = false;
215   }
216   if (!InlineChildren)
217     OS.indent(IndentationLevel * 2);
218   OS << "</" << Tag.ToString() << ">";
219 }
220 
221 template <typename Derived, typename Base,
222           typename = std::enable_if<std::is_base_of<Derived, Base>::value>>
223 static void AppendVector(std::vector<Derived> &&New,
224                          std::vector<Base> &Original) {
225   std::move(New.begin(), New.end(), std::back_inserter(Original));
226 }
227 
228 // Compute the relative path from an Origin directory to a Destination directory
229 static SmallString<128> computeRelativePath(StringRef Destination,
230                                             StringRef Origin) {
231   // If Origin is empty, the relative path to the Destination is its complete
232   // path.
233   if (Origin.empty())
234     return Destination;
235 
236   // The relative path is an empty path if both directories are the same.
237   if (Destination == Origin)
238     return {};
239 
240   // These iterators iterate through each of their parent directories
241   llvm::sys::path::const_iterator FileI = llvm::sys::path::begin(Destination);
242   llvm::sys::path::const_iterator FileE = llvm::sys::path::end(Destination);
243   llvm::sys::path::const_iterator DirI = llvm::sys::path::begin(Origin);
244   llvm::sys::path::const_iterator DirE = llvm::sys::path::end(Origin);
245   // Advance both iterators until the paths differ. Example:
246   //    Destination = A/B/C/D
247   //    Origin      = A/B/E/F
248   // FileI will point to C and DirI to E. The directories behind them is the
249   // directory they share (A/B).
250   while (FileI != FileE && DirI != DirE && *FileI == *DirI) {
251     ++FileI;
252     ++DirI;
253   }
254   SmallString<128> Result; // This will hold the resulting path.
255   // Result has to go up one directory for each of the remaining directories in
256   // Origin
257   while (DirI != DirE) {
258     llvm::sys::path::append(Result, "..");
259     ++DirI;
260   }
261   // Result has to append each of the remaining directories in Destination
262   while (FileI != FileE) {
263     llvm::sys::path::append(Result, *FileI);
264     ++FileI;
265   }
266   return Result;
267 }
268 
269 // HTML generation
270 
271 static std::vector<std::unique_ptr<TagNode>>
272 genStylesheetsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) {
273   std::vector<std::unique_ptr<TagNode>> Out;
274   for (const auto &FilePath : CDCtx.UserStylesheets) {
275     auto LinkNode = std::make_unique<TagNode>(HTMLTag::TAG_LINK);
276     LinkNode->Attributes.emplace_back("rel", "stylesheet");
277     SmallString<128> StylesheetPath = computeRelativePath("", InfoPath);
278     llvm::sys::path::append(StylesheetPath,
279                             llvm::sys::path::filename(FilePath));
280     // Paths in HTML must be in posix-style
281     llvm::sys::path::native(StylesheetPath, llvm::sys::path::Style::posix);
282     LinkNode->Attributes.emplace_back("href", std::string(StylesheetPath.str()));
283     Out.emplace_back(std::move(LinkNode));
284   }
285   return Out;
286 }
287 
288 static std::vector<std::unique_ptr<TagNode>>
289 genJsScriptsHTML(StringRef InfoPath, const ClangDocContext &CDCtx) {
290   std::vector<std::unique_ptr<TagNode>> Out;
291   for (const auto &FilePath : CDCtx.JsScripts) {
292     auto ScriptNode = std::make_unique<TagNode>(HTMLTag::TAG_SCRIPT);
293     SmallString<128> ScriptPath = computeRelativePath("", InfoPath);
294     llvm::sys::path::append(ScriptPath, llvm::sys::path::filename(FilePath));
295     // Paths in HTML must be in posix-style
296     llvm::sys::path::native(ScriptPath, llvm::sys::path::Style::posix);
297     ScriptNode->Attributes.emplace_back("src", std::string(ScriptPath.str()));
298     Out.emplace_back(std::move(ScriptNode));
299   }
300   return Out;
301 }
302 
303 static std::unique_ptr<TagNode> genLink(const Twine &Text, const Twine &Link) {
304   auto LinkNode = std::make_unique<TagNode>(HTMLTag::TAG_A, Text);
305   LinkNode->Attributes.emplace_back("href", Link.str());
306   return LinkNode;
307 }
308 
309 static std::unique_ptr<HTMLNode>
310 genReference(const Reference &Type, StringRef CurrentDirectory,
311              llvm::Optional<StringRef> JumpToSection = None) {
312   if (Type.Path.empty()) {
313     if (!JumpToSection)
314       return std::make_unique<TextNode>(Type.Name);
315     else
316       return genLink(Type.Name, "#" + *JumpToSection);
317   }
318   llvm::SmallString<64> Path = Type.getRelativeFilePath(CurrentDirectory);
319   llvm::sys::path::append(Path, Type.getFileBaseName() + ".html");
320 
321   // Paths in HTML must be in posix-style
322   llvm::sys::path::native(Path, llvm::sys::path::Style::posix);
323   if (JumpToSection)
324     Path += ("#" + *JumpToSection).str();
325   return genLink(Type.Name, Path);
326 }
327 
328 static std::vector<std::unique_ptr<HTMLNode>>
329 genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs,
330                  const StringRef &CurrentDirectory) {
331   std::vector<std::unique_ptr<HTMLNode>> Out;
332   for (const auto &R : Refs) {
333     if (&R != Refs.begin())
334       Out.emplace_back(std::make_unique<TextNode>(", "));
335     Out.emplace_back(genReference(R, CurrentDirectory));
336   }
337   return Out;
338 }
339 
340 static std::vector<std::unique_ptr<TagNode>>
341 genHTML(const EnumInfo &I, const ClangDocContext &CDCtx);
342 static std::vector<std::unique_ptr<TagNode>>
343 genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
344         StringRef ParentInfoDir);
345 
346 static std::vector<std::unique_ptr<TagNode>>
347 genEnumsBlock(const std::vector<EnumInfo> &Enums,
348               const ClangDocContext &CDCtx) {
349   if (Enums.empty())
350     return {};
351 
352   std::vector<std::unique_ptr<TagNode>> Out;
353   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H2, "Enums"));
354   Out.back()->Attributes.emplace_back("id", "Enums");
355   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_DIV));
356   auto &DivBody = Out.back();
357   for (const auto &E : Enums) {
358     std::vector<std::unique_ptr<TagNode>> Nodes = genHTML(E, CDCtx);
359     AppendVector(std::move(Nodes), DivBody->Children);
360   }
361   return Out;
362 }
363 
364 static std::unique_ptr<TagNode>
365 genEnumMembersBlock(const llvm::SmallVector<EnumValueInfo, 4> &Members) {
366   if (Members.empty())
367     return nullptr;
368 
369   auto List = std::make_unique<TagNode>(HTMLTag::TAG_UL);
370   for (const auto &M : Members)
371     List->Children.emplace_back(
372         std::make_unique<TagNode>(HTMLTag::TAG_LI, M.Name));
373   return List;
374 }
375 
376 static std::vector<std::unique_ptr<TagNode>>
377 genFunctionsBlock(const std::vector<FunctionInfo> &Functions,
378                   const ClangDocContext &CDCtx, StringRef ParentInfoDir) {
379   if (Functions.empty())
380     return {};
381 
382   std::vector<std::unique_ptr<TagNode>> Out;
383   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H2, "Functions"));
384   Out.back()->Attributes.emplace_back("id", "Functions");
385   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_DIV));
386   auto &DivBody = Out.back();
387   for (const auto &F : Functions) {
388     std::vector<std::unique_ptr<TagNode>> Nodes =
389         genHTML(F, CDCtx, ParentInfoDir);
390     AppendVector(std::move(Nodes), DivBody->Children);
391   }
392   return Out;
393 }
394 
395 static std::vector<std::unique_ptr<TagNode>>
396 genRecordMembersBlock(const llvm::SmallVector<MemberTypeInfo, 4> &Members,
397                       StringRef ParentInfoDir) {
398   if (Members.empty())
399     return {};
400 
401   std::vector<std::unique_ptr<TagNode>> Out;
402   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H2, "Members"));
403   Out.back()->Attributes.emplace_back("id", "Members");
404   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_UL));
405   auto &ULBody = Out.back();
406   for (const auto &M : Members) {
407     std::string Access = getAccessSpelling(M.Access).str();
408     if (Access != "")
409       Access = Access + " ";
410     auto LIBody = std::make_unique<TagNode>(HTMLTag::TAG_LI);
411     LIBody->Children.emplace_back(std::make_unique<TextNode>(Access));
412     LIBody->Children.emplace_back(genReference(M.Type, ParentInfoDir));
413     LIBody->Children.emplace_back(std::make_unique<TextNode>(" " + M.Name));
414     ULBody->Children.emplace_back(std::move(LIBody));
415   }
416   return Out;
417 }
418 
419 static std::vector<std::unique_ptr<TagNode>>
420 genReferencesBlock(const std::vector<Reference> &References,
421                    llvm::StringRef Title, StringRef ParentPath) {
422   if (References.empty())
423     return {};
424 
425   std::vector<std::unique_ptr<TagNode>> Out;
426   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H2, Title));
427   Out.back()->Attributes.emplace_back("id", std::string(Title));
428   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_UL));
429   auto &ULBody = Out.back();
430   for (const auto &R : References) {
431     auto LiNode = std::make_unique<TagNode>(HTMLTag::TAG_LI);
432     LiNode->Children.emplace_back(genReference(R, ParentPath));
433     ULBody->Children.emplace_back(std::move(LiNode));
434   }
435   return Out;
436 }
437 
438 static std::unique_ptr<TagNode>
439 writeFileDefinition(const Location &L,
440                     llvm::Optional<StringRef> RepositoryUrl = None) {
441   if (!L.IsFileInRootDir || !RepositoryUrl)
442     return std::make_unique<TagNode>(
443         HTMLTag::TAG_P, "Defined at line " + std::to_string(L.LineNumber) +
444                             " of file " + L.Filename);
445   SmallString<128> FileURL(*RepositoryUrl);
446   llvm::sys::path::append(FileURL, llvm::sys::path::Style::posix, L.Filename);
447   auto Node = std::make_unique<TagNode>(HTMLTag::TAG_P);
448   Node->Children.emplace_back(std::make_unique<TextNode>("Defined at line "));
449   auto LocNumberNode =
450       std::make_unique<TagNode>(HTMLTag::TAG_A, std::to_string(L.LineNumber));
451   // The links to a specific line in the source code use the github /
452   // googlesource notation so it won't work for all hosting pages.
453   LocNumberNode->Attributes.emplace_back(
454       "href", (FileURL + "#" + std::to_string(L.LineNumber)).str());
455   Node->Children.emplace_back(std::move(LocNumberNode));
456   Node->Children.emplace_back(std::make_unique<TextNode>(" of file "));
457   auto LocFileNode = std::make_unique<TagNode>(
458       HTMLTag::TAG_A, llvm::sys::path::filename(FileURL));
459   LocFileNode->Attributes.emplace_back("href", std::string(FileURL.str()));
460   Node->Children.emplace_back(std::move(LocFileNode));
461   return Node;
462 }
463 
464 static std::vector<std::unique_ptr<TagNode>>
465 genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList);
466 
467 // Generates a list of child nodes for the HTML head tag
468 // It contains a meta node, link nodes to import CSS files, and script nodes to
469 // import JS files
470 static std::vector<std::unique_ptr<TagNode>>
471 genFileHeadNodes(StringRef Title, StringRef InfoPath,
472                  const ClangDocContext &CDCtx) {
473   std::vector<std::unique_ptr<TagNode>> Out;
474   auto MetaNode = std::make_unique<TagNode>(HTMLTag::TAG_META);
475   MetaNode->Attributes.emplace_back("charset", "utf-8");
476   Out.emplace_back(std::move(MetaNode));
477   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_TITLE, Title));
478   std::vector<std::unique_ptr<TagNode>> StylesheetsNodes =
479       genStylesheetsHTML(InfoPath, CDCtx);
480   AppendVector(std::move(StylesheetsNodes), Out);
481   std::vector<std::unique_ptr<TagNode>> JsNodes =
482       genJsScriptsHTML(InfoPath, CDCtx);
483   AppendVector(std::move(JsNodes), Out);
484   return Out;
485 }
486 
487 // Generates a header HTML node that can be used for any file
488 // It contains the project name
489 static std::unique_ptr<TagNode> genFileHeaderNode(StringRef ProjectName) {
490   auto HeaderNode = std::make_unique<TagNode>(HTMLTag::TAG_HEADER, ProjectName);
491   HeaderNode->Attributes.emplace_back("id", "project-title");
492   return HeaderNode;
493 }
494 
495 // Generates a main HTML node that has all the main content of an info file
496 // It contains both indexes and the info's documented information
497 // This function should only be used for the info files (not for the file that
498 // only has the general index)
499 static std::unique_ptr<TagNode> genInfoFileMainNode(
500     StringRef InfoPath,
501     std::vector<std::unique_ptr<TagNode>> &MainContentInnerNodes,
502     const Index &InfoIndex) {
503   auto MainNode = std::make_unique<TagNode>(HTMLTag::TAG_MAIN);
504 
505   auto LeftSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
506   LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left");
507   LeftSidebarNode->Attributes.emplace_back("path", std::string(InfoPath));
508   LeftSidebarNode->Attributes.emplace_back(
509       "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
510 
511   auto MainContentNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
512   MainContentNode->Attributes.emplace_back("id", "main-content");
513   MainContentNode->Attributes.emplace_back(
514       "class", "col-xs-12 col-sm-9 col-md-8 main-content");
515   AppendVector(std::move(MainContentInnerNodes), MainContentNode->Children);
516 
517   auto RightSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
518   RightSidebarNode->Attributes.emplace_back("id", "sidebar-right");
519   RightSidebarNode->Attributes.emplace_back(
520       "class", "col-xs-6 col-sm-6 col-md-2 sidebar sidebar-offcanvas-right");
521   std::vector<std::unique_ptr<TagNode>> InfoIndexHTML =
522       genHTML(InfoIndex, InfoPath, true);
523   AppendVector(std::move(InfoIndexHTML), RightSidebarNode->Children);
524 
525   MainNode->Children.emplace_back(std::move(LeftSidebarNode));
526   MainNode->Children.emplace_back(std::move(MainContentNode));
527   MainNode->Children.emplace_back(std::move(RightSidebarNode));
528 
529   return MainNode;
530 }
531 
532 // Generates a footer HTML node that can be used for any file
533 // It contains clang-doc's version
534 static std::unique_ptr<TagNode> genFileFooterNode() {
535   auto FooterNode = std::make_unique<TagNode>(HTMLTag::TAG_FOOTER);
536   auto SpanNode = std::make_unique<TagNode>(
537       HTMLTag::TAG_SPAN, clang::getClangToolFullVersion("clang-doc"));
538   SpanNode->Attributes.emplace_back("class", "no-break");
539   FooterNode->Children.emplace_back(std::move(SpanNode));
540   return FooterNode;
541 }
542 
543 // Generates a complete HTMLFile for an Info
544 static HTMLFile
545 genInfoFile(StringRef Title, StringRef InfoPath,
546             std::vector<std::unique_ptr<TagNode>> &MainContentNodes,
547             const Index &InfoIndex, const ClangDocContext &CDCtx) {
548   HTMLFile F;
549 
550   std::vector<std::unique_ptr<TagNode>> HeadNodes =
551       genFileHeadNodes(Title, InfoPath, CDCtx);
552   std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(CDCtx.ProjectName);
553   std::unique_ptr<TagNode> MainNode =
554       genInfoFileMainNode(InfoPath, MainContentNodes, InfoIndex);
555   std::unique_ptr<TagNode> FooterNode = genFileFooterNode();
556 
557   AppendVector(std::move(HeadNodes), F.Children);
558   F.Children.emplace_back(std::move(HeaderNode));
559   F.Children.emplace_back(std::move(MainNode));
560   F.Children.emplace_back(std::move(FooterNode));
561 
562   return F;
563 }
564 
565 template <typename T,
566           typename = std::enable_if<std::is_base_of<T, Info>::value>>
567 static Index genInfoIndexItem(const std::vector<T> &Infos, StringRef Title) {
568   Index Idx(Title, Title);
569   for (const auto &C : Infos)
570     Idx.Children.emplace_back(C.extractName(),
571                               llvm::toHex(llvm::toStringRef(C.USR)));
572   return Idx;
573 }
574 
575 static std::vector<std::unique_ptr<TagNode>>
576 genHTML(const Index &Index, StringRef InfoPath, bool IsOutermostList) {
577   std::vector<std::unique_ptr<TagNode>> Out;
578   if (!Index.Name.empty()) {
579     Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_SPAN));
580     auto &SpanBody = Out.back();
581     if (!Index.JumpToSection)
582       SpanBody->Children.emplace_back(genReference(Index, InfoPath));
583     else
584       SpanBody->Children.emplace_back(
585           genReference(Index, InfoPath, Index.JumpToSection->str()));
586   }
587   if (Index.Children.empty())
588     return Out;
589   // Only the outermost list should use ol, the others should use ul
590   HTMLTag ListHTMLTag = IsOutermostList ? HTMLTag::TAG_OL : HTMLTag::TAG_UL;
591   Out.emplace_back(std::make_unique<TagNode>(ListHTMLTag));
592   const auto &UlBody = Out.back();
593   for (const auto &C : Index.Children) {
594     auto LiBody = std::make_unique<TagNode>(HTMLTag::TAG_LI);
595     std::vector<std::unique_ptr<TagNode>> Nodes = genHTML(C, InfoPath, false);
596     AppendVector(std::move(Nodes), LiBody->Children);
597     UlBody->Children.emplace_back(std::move(LiBody));
598   }
599   return Out;
600 }
601 
602 static std::unique_ptr<HTMLNode> genHTML(const CommentInfo &I) {
603   if (I.Kind == "FullComment") {
604     auto FullComment = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
605     for (const auto &Child : I.Children) {
606       std::unique_ptr<HTMLNode> Node = genHTML(*Child);
607       if (Node)
608         FullComment->Children.emplace_back(std::move(Node));
609     }
610     return std::move(FullComment);
611   } else if (I.Kind == "ParagraphComment") {
612     auto ParagraphComment = std::make_unique<TagNode>(HTMLTag::TAG_P);
613     for (const auto &Child : I.Children) {
614       std::unique_ptr<HTMLNode> Node = genHTML(*Child);
615       if (Node)
616         ParagraphComment->Children.emplace_back(std::move(Node));
617     }
618     if (ParagraphComment->Children.empty())
619       return nullptr;
620     return std::move(ParagraphComment);
621   } else if (I.Kind == "TextComment") {
622     if (I.Text == "")
623       return nullptr;
624     return std::make_unique<TextNode>(I.Text);
625   }
626   return nullptr;
627 }
628 
629 static std::unique_ptr<TagNode> genHTML(const std::vector<CommentInfo> &C) {
630   auto CommentBlock = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
631   for (const auto &Child : C) {
632     if (std::unique_ptr<HTMLNode> Node = genHTML(Child))
633       CommentBlock->Children.emplace_back(std::move(Node));
634   }
635   return CommentBlock;
636 }
637 
638 static std::vector<std::unique_ptr<TagNode>>
639 genHTML(const EnumInfo &I, const ClangDocContext &CDCtx) {
640   std::vector<std::unique_ptr<TagNode>> Out;
641   std::string EnumType;
642   if (I.Scoped)
643     EnumType = "enum class ";
644   else
645     EnumType = "enum ";
646 
647   Out.emplace_back(
648       std::make_unique<TagNode>(HTMLTag::TAG_H3, EnumType + I.Name));
649   Out.back()->Attributes.emplace_back("id",
650                                       llvm::toHex(llvm::toStringRef(I.USR)));
651 
652   std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members);
653   if (Node)
654     Out.emplace_back(std::move(Node));
655 
656   if (I.DefLoc) {
657     if (!CDCtx.RepositoryUrl)
658       Out.emplace_back(writeFileDefinition(I.DefLoc.value()));
659     else
660       Out.emplace_back(writeFileDefinition(
661           I.DefLoc.value(), StringRef{CDCtx.RepositoryUrl.value()}));
662   }
663 
664   std::string Description;
665   if (!I.Description.empty())
666     Out.emplace_back(genHTML(I.Description));
667 
668   return Out;
669 }
670 
671 static std::vector<std::unique_ptr<TagNode>>
672 genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
673         StringRef ParentInfoDir) {
674   std::vector<std::unique_ptr<TagNode>> Out;
675   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H3, I.Name));
676   // USR is used as id for functions instead of name to disambiguate function
677   // overloads.
678   Out.back()->Attributes.emplace_back("id",
679                                       llvm::toHex(llvm::toStringRef(I.USR)));
680 
681   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_P));
682   auto &FunctionHeader = Out.back();
683 
684   std::string Access = getAccessSpelling(I.Access).str();
685   if (Access != "")
686     FunctionHeader->Children.emplace_back(
687         std::make_unique<TextNode>(Access + " "));
688   if (I.ReturnType.Type.Name != "") {
689     FunctionHeader->Children.emplace_back(
690         genReference(I.ReturnType.Type, ParentInfoDir));
691     FunctionHeader->Children.emplace_back(std::make_unique<TextNode>(" "));
692   }
693   FunctionHeader->Children.emplace_back(
694       std::make_unique<TextNode>(I.Name + "("));
695 
696   for (const auto &P : I.Params) {
697     if (&P != I.Params.begin())
698       FunctionHeader->Children.emplace_back(std::make_unique<TextNode>(", "));
699     FunctionHeader->Children.emplace_back(genReference(P.Type, ParentInfoDir));
700     FunctionHeader->Children.emplace_back(
701         std::make_unique<TextNode>(" " + P.Name));
702   }
703   FunctionHeader->Children.emplace_back(std::make_unique<TextNode>(")"));
704 
705   if (I.DefLoc) {
706     if (!CDCtx.RepositoryUrl)
707       Out.emplace_back(writeFileDefinition(I.DefLoc.value()));
708     else
709       Out.emplace_back(writeFileDefinition(
710           I.DefLoc.value(), StringRef{CDCtx.RepositoryUrl.value()}));
711   }
712 
713   std::string Description;
714   if (!I.Description.empty())
715     Out.emplace_back(genHTML(I.Description));
716 
717   return Out;
718 }
719 
720 static std::vector<std::unique_ptr<TagNode>>
721 genHTML(const NamespaceInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
722         std::string &InfoTitle) {
723   std::vector<std::unique_ptr<TagNode>> Out;
724   if (I.Name.str() == "")
725     InfoTitle = "Global Namespace";
726   else
727     InfoTitle = ("namespace " + I.Name).str();
728 
729   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H1, InfoTitle));
730 
731   std::string Description;
732   if (!I.Description.empty())
733     Out.emplace_back(genHTML(I.Description));
734 
735   llvm::SmallString<64> BasePath = I.getRelativeFilePath("");
736 
737   std::vector<std::unique_ptr<TagNode>> ChildNamespaces =
738       genReferencesBlock(I.Children.Namespaces, "Namespaces", BasePath);
739   AppendVector(std::move(ChildNamespaces), Out);
740   std::vector<std::unique_ptr<TagNode>> ChildRecords =
741       genReferencesBlock(I.Children.Records, "Records", BasePath);
742   AppendVector(std::move(ChildRecords), Out);
743 
744   std::vector<std::unique_ptr<TagNode>> ChildFunctions =
745       genFunctionsBlock(I.Children.Functions, CDCtx, BasePath);
746   AppendVector(std::move(ChildFunctions), Out);
747   std::vector<std::unique_ptr<TagNode>> ChildEnums =
748       genEnumsBlock(I.Children.Enums, CDCtx);
749   AppendVector(std::move(ChildEnums), Out);
750 
751   if (!I.Children.Namespaces.empty())
752     InfoIndex.Children.emplace_back("Namespaces", "Namespaces");
753   if (!I.Children.Records.empty())
754     InfoIndex.Children.emplace_back("Records", "Records");
755   if (!I.Children.Functions.empty())
756     InfoIndex.Children.emplace_back(
757         genInfoIndexItem(I.Children.Functions, "Functions"));
758   if (!I.Children.Enums.empty())
759     InfoIndex.Children.emplace_back(
760         genInfoIndexItem(I.Children.Enums, "Enums"));
761 
762   return Out;
763 }
764 
765 static std::vector<std::unique_ptr<TagNode>>
766 genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
767         std::string &InfoTitle) {
768   std::vector<std::unique_ptr<TagNode>> Out;
769   InfoTitle = (getTagType(I.TagType) + " " + I.Name).str();
770   Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_H1, InfoTitle));
771 
772   if (I.DefLoc) {
773     if (!CDCtx.RepositoryUrl)
774       Out.emplace_back(writeFileDefinition(I.DefLoc.value()));
775     else
776       Out.emplace_back(writeFileDefinition(
777           I.DefLoc.value(), StringRef{CDCtx.RepositoryUrl.value()}));
778   }
779 
780   std::string Description;
781   if (!I.Description.empty())
782     Out.emplace_back(genHTML(I.Description));
783 
784   std::vector<std::unique_ptr<HTMLNode>> Parents =
785       genReferenceList(I.Parents, I.Path);
786   std::vector<std::unique_ptr<HTMLNode>> VParents =
787       genReferenceList(I.VirtualParents, I.Path);
788   if (!Parents.empty() || !VParents.empty()) {
789     Out.emplace_back(std::make_unique<TagNode>(HTMLTag::TAG_P));
790     auto &PBody = Out.back();
791     PBody->Children.emplace_back(std::make_unique<TextNode>("Inherits from "));
792     if (Parents.empty())
793       AppendVector(std::move(VParents), PBody->Children);
794     else if (VParents.empty())
795       AppendVector(std::move(Parents), PBody->Children);
796     else {
797       AppendVector(std::move(Parents), PBody->Children);
798       PBody->Children.emplace_back(std::make_unique<TextNode>(", "));
799       AppendVector(std::move(VParents), PBody->Children);
800     }
801   }
802 
803   std::vector<std::unique_ptr<TagNode>> Members =
804       genRecordMembersBlock(I.Members, I.Path);
805   AppendVector(std::move(Members), Out);
806   std::vector<std::unique_ptr<TagNode>> ChildRecords =
807       genReferencesBlock(I.Children.Records, "Records", I.Path);
808   AppendVector(std::move(ChildRecords), Out);
809 
810   std::vector<std::unique_ptr<TagNode>> ChildFunctions =
811       genFunctionsBlock(I.Children.Functions, CDCtx, I.Path);
812   AppendVector(std::move(ChildFunctions), Out);
813   std::vector<std::unique_ptr<TagNode>> ChildEnums =
814       genEnumsBlock(I.Children.Enums, CDCtx);
815   AppendVector(std::move(ChildEnums), Out);
816 
817   if (!I.Members.empty())
818     InfoIndex.Children.emplace_back("Members", "Members");
819   if (!I.Children.Records.empty())
820     InfoIndex.Children.emplace_back("Records", "Records");
821   if (!I.Children.Functions.empty())
822     InfoIndex.Children.emplace_back(
823         genInfoIndexItem(I.Children.Functions, "Functions"));
824   if (!I.Children.Enums.empty())
825     InfoIndex.Children.emplace_back(
826         genInfoIndexItem(I.Children.Enums, "Enums"));
827 
828   return Out;
829 }
830 
831 static std::vector<std::unique_ptr<TagNode>>
832 genHTML(const TypedefInfo &I, const ClangDocContext &CDCtx,
833         std::string &InfoTitle) {
834   // TODO support typedefs in HTML.
835   return {};
836 }
837 
838 /// Generator for HTML documentation.
839 class HTMLGenerator : public Generator {
840 public:
841   static const char *Format;
842 
843   llvm::Error generateDocs(StringRef RootDir,
844                            llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
845                            const ClangDocContext &CDCtx) override;
846   llvm::Error createResources(ClangDocContext &CDCtx) override;
847   llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
848                                  const ClangDocContext &CDCtx) override;
849 };
850 
851 const char *HTMLGenerator::Format = "html";
852 
853 llvm::Error
854 HTMLGenerator::generateDocs(StringRef RootDir,
855                             llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
856                             const ClangDocContext &CDCtx) {
857   // Track which directories we already tried to create.
858   llvm::StringSet<> CreatedDirs;
859 
860   // Collect all output by file name and create the nexessary directories.
861   llvm::StringMap<std::vector<doc::Info *>> FileToInfos;
862   for (const auto &Group : Infos) {
863     doc::Info *Info = Group.getValue().get();
864 
865     llvm::SmallString<128> Path;
866     llvm::sys::path::native(RootDir, Path);
867     llvm::sys::path::append(Path, Info->getRelativeFilePath(""));
868     if (CreatedDirs.find(Path) == CreatedDirs.end()) {
869       if (std::error_code Err = llvm::sys::fs::create_directories(Path);
870           Err != std::error_code()) {
871         return llvm::createStringError(Err, "Failed to create directory '%s'.",
872                                        Path.c_str());
873       }
874       CreatedDirs.insert(Path);
875     }
876 
877     llvm::sys::path::append(Path, Info->getFileBaseName() + ".html");
878     FileToInfos[Path].push_back(Info);
879   }
880 
881   for (const auto &Group : FileToInfos) {
882     std::error_code FileErr;
883     llvm::raw_fd_ostream InfoOS(Group.getKey(), FileErr,
884                                 llvm::sys::fs::OF_None);
885     if (FileErr) {
886       return llvm::createStringError(FileErr, "Error opening file '%s'",
887                                      Group.getKey().str().c_str());
888     }
889 
890     // TODO: https://github.com/llvm/llvm-project/issues/59073
891     // If there are multiple Infos for this file name (for example, template
892     // specializations), this will generate multiple complete web pages (with
893     // <DOCTYPE> and <title>, etc.) concatenated together. This generator needs
894     // some refactoring to be able to output the headers separately from the
895     // contents.
896     for (const auto &Info : Group.getValue()) {
897       if (llvm::Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) {
898         return Err;
899       }
900     }
901   }
902 
903   return llvm::Error::success();
904 }
905 
906 llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS,
907                                               const ClangDocContext &CDCtx) {
908   std::string InfoTitle;
909   std::vector<std::unique_ptr<TagNode>> MainContentNodes;
910   Index InfoIndex;
911   switch (I->IT) {
912   case InfoType::IT_namespace:
913     MainContentNodes = genHTML(*static_cast<clang::doc::NamespaceInfo *>(I),
914                                InfoIndex, CDCtx, InfoTitle);
915     break;
916   case InfoType::IT_record:
917     MainContentNodes = genHTML(*static_cast<clang::doc::RecordInfo *>(I),
918                                InfoIndex, CDCtx, InfoTitle);
919     break;
920   case InfoType::IT_enum:
921     MainContentNodes = genHTML(*static_cast<clang::doc::EnumInfo *>(I), CDCtx);
922     break;
923   case InfoType::IT_function:
924     MainContentNodes =
925         genHTML(*static_cast<clang::doc::FunctionInfo *>(I), CDCtx, "");
926     break;
927   case InfoType::IT_typedef:
928     MainContentNodes =
929         genHTML(*static_cast<clang::doc::TypedefInfo *>(I), CDCtx, InfoTitle);
930     break;
931   case InfoType::IT_default:
932     return llvm::createStringError(llvm::inconvertibleErrorCode(),
933                                    "unexpected info type");
934   }
935 
936   HTMLFile F = genInfoFile(InfoTitle, I->getRelativeFilePath(""),
937                            MainContentNodes, InfoIndex, CDCtx);
938   F.Render(OS);
939 
940   return llvm::Error::success();
941 }
942 
943 static std::string getRefType(InfoType IT) {
944   switch (IT) {
945   case InfoType::IT_default:
946     return "default";
947   case InfoType::IT_namespace:
948     return "namespace";
949   case InfoType::IT_record:
950     return "record";
951   case InfoType::IT_function:
952     return "function";
953   case InfoType::IT_enum:
954     return "enum";
955   case InfoType::IT_typedef:
956     return "typedef";
957   }
958   llvm_unreachable("Unknown InfoType");
959 }
960 
961 static llvm::Error SerializeIndex(ClangDocContext &CDCtx) {
962   std::error_code OK;
963   std::error_code FileErr;
964   llvm::SmallString<128> FilePath;
965   llvm::sys::path::native(CDCtx.OutDirectory, FilePath);
966   llvm::sys::path::append(FilePath, "index_json.js");
967   llvm::raw_fd_ostream OS(FilePath, FileErr, llvm::sys::fs::OF_None);
968   if (FileErr != OK) {
969     return llvm::createStringError(llvm::inconvertibleErrorCode(),
970                                    "error creating index file: " +
971                                        FileErr.message());
972   }
973   CDCtx.Idx.sort();
974   llvm::json::OStream J(OS, 2);
975   std::function<void(Index)> IndexToJSON = [&](const Index &I) {
976     J.object([&] {
977       J.attribute("USR", toHex(llvm::toStringRef(I.USR)));
978       J.attribute("Name", I.Name);
979       J.attribute("RefType", getRefType(I.RefType));
980       J.attribute("Path", I.getRelativeFilePath(""));
981       J.attributeArray("Children", [&] {
982         for (const Index &C : I.Children)
983           IndexToJSON(C);
984       });
985     });
986   };
987   OS << "var JsonIndex = `\n";
988   IndexToJSON(CDCtx.Idx);
989   OS << "`;\n";
990   return llvm::Error::success();
991 }
992 
993 // Generates a main HTML node that has the main content of the file that shows
994 // only the general index
995 // It contains the general index with links to all the generated files
996 static std::unique_ptr<TagNode> genIndexFileMainNode() {
997   auto MainNode = std::make_unique<TagNode>(HTMLTag::TAG_MAIN);
998 
999   auto LeftSidebarNode = std::make_unique<TagNode>(HTMLTag::TAG_DIV);
1000   LeftSidebarNode->Attributes.emplace_back("id", "sidebar-left");
1001   LeftSidebarNode->Attributes.emplace_back("path", "");
1002   LeftSidebarNode->Attributes.emplace_back(
1003       "class", "col-xs-6 col-sm-3 col-md-2 sidebar sidebar-offcanvas-left");
1004   LeftSidebarNode->Attributes.emplace_back("style", "flex: 0 100%;");
1005 
1006   MainNode->Children.emplace_back(std::move(LeftSidebarNode));
1007 
1008   return MainNode;
1009 }
1010 
1011 static llvm::Error GenIndex(const ClangDocContext &CDCtx) {
1012   std::error_code FileErr, OK;
1013   llvm::SmallString<128> IndexPath;
1014   llvm::sys::path::native(CDCtx.OutDirectory, IndexPath);
1015   llvm::sys::path::append(IndexPath, "index.html");
1016   llvm::raw_fd_ostream IndexOS(IndexPath, FileErr, llvm::sys::fs::OF_None);
1017   if (FileErr != OK) {
1018     return llvm::createStringError(llvm::inconvertibleErrorCode(),
1019                                    "error creating main index: " +
1020                                        FileErr.message());
1021   }
1022 
1023   HTMLFile F;
1024 
1025   std::vector<std::unique_ptr<TagNode>> HeadNodes =
1026       genFileHeadNodes("Index", "", CDCtx);
1027   std::unique_ptr<TagNode> HeaderNode = genFileHeaderNode(CDCtx.ProjectName);
1028   std::unique_ptr<TagNode> MainNode = genIndexFileMainNode();
1029   std::unique_ptr<TagNode> FooterNode = genFileFooterNode();
1030 
1031   AppendVector(std::move(HeadNodes), F.Children);
1032   F.Children.emplace_back(std::move(HeaderNode));
1033   F.Children.emplace_back(std::move(MainNode));
1034   F.Children.emplace_back(std::move(FooterNode));
1035 
1036   F.Render(IndexOS);
1037 
1038   return llvm::Error::success();
1039 }
1040 
1041 static llvm::Error CopyFile(StringRef FilePath, StringRef OutDirectory) {
1042   llvm::SmallString<128> PathWrite;
1043   llvm::sys::path::native(OutDirectory, PathWrite);
1044   llvm::sys::path::append(PathWrite, llvm::sys::path::filename(FilePath));
1045   llvm::SmallString<128> PathRead;
1046   llvm::sys::path::native(FilePath, PathRead);
1047   std::error_code OK;
1048   std::error_code FileErr = llvm::sys::fs::copy_file(PathRead, PathWrite);
1049   if (FileErr != OK) {
1050     return llvm::createStringError(llvm::inconvertibleErrorCode(),
1051                                    "error creating file " +
1052                                        llvm::sys::path::filename(FilePath) +
1053                                        ": " + FileErr.message() + "\n");
1054   }
1055   return llvm::Error::success();
1056 }
1057 
1058 llvm::Error HTMLGenerator::createResources(ClangDocContext &CDCtx) {
1059   auto Err = SerializeIndex(CDCtx);
1060   if (Err)
1061     return Err;
1062   Err = GenIndex(CDCtx);
1063   if (Err)
1064     return Err;
1065 
1066   for (const auto &FilePath : CDCtx.UserStylesheets) {
1067     Err = CopyFile(FilePath, CDCtx.OutDirectory);
1068     if (Err)
1069       return Err;
1070   }
1071   for (const auto &FilePath : CDCtx.FilesToCopy) {
1072     Err = CopyFile(FilePath, CDCtx.OutDirectory);
1073     if (Err)
1074       return Err;
1075   }
1076   return llvm::Error::success();
1077 }
1078 
1079 static GeneratorRegistry::Add<HTMLGenerator> HTML(HTMLGenerator::Format,
1080                                                   "Generator for HTML output.");
1081 
1082 // This anchor is used to force the linker to link in the generated object
1083 // file and thus register the generator.
1084 volatile int HTMLGeneratorAnchorSource = 0;
1085 
1086 } // namespace doc
1087 } // namespace clang
1088