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