brintos

brintos / llvm-project-archived public Read only

0
0
Text · 40.1 KiB · 7c8c16b Raw
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