[clang-tools-extra] r365687 - [clang-doc] Add a structured HTML generator

Julie Hockett via cfe-commits cfe-commits at lists.llvm.org
Wed Jul 10 12:03:25 PDT 2019


Author: juliehockett
Date: Wed Jul 10 12:03:25 2019
New Revision: 365687

URL: http://llvm.org/viewvc/llvm-project?rev=365687&view=rev
Log:
[clang-doc] Add a structured HTML generator

Implements an HTML generator.
Nodes are used to represent each part of the HTML file. There are TagNodes that
represent every HTML tag (p, h1, div, ...) and they have children nodes, which
can be TagNodes or TextNodes (these nodes only have text).
Proper indentation is rendered within the files generated by tool.
No styling (CSS) is included.

Committed on behalf of Diego Astiazarán (diegoaat97 at gmail.com)

Differential Revision: https://reviews.llvm.org/D63857

Added:
    clang-tools-extra/trunk/clang-doc/HTMLGenerator.cpp
    clang-tools-extra/trunk/unittests/clang-doc/HTMLGeneratorTest.cpp
Modified:
    clang-tools-extra/trunk/clang-doc/CMakeLists.txt
    clang-tools-extra/trunk/clang-doc/Generators.cpp
    clang-tools-extra/trunk/clang-doc/Generators.h
    clang-tools-extra/trunk/clang-doc/MDGenerator.cpp
    clang-tools-extra/trunk/clang-doc/tool/ClangDocMain.cpp
    clang-tools-extra/trunk/unittests/clang-doc/CMakeLists.txt

Modified: clang-tools-extra/trunk/clang-doc/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-doc/CMakeLists.txt?rev=365687&r1=365686&r2=365687&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-doc/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/clang-doc/CMakeLists.txt Wed Jul 10 12:03:25 2019
@@ -8,6 +8,7 @@ add_clang_library(clangDoc
   BitcodeWriter.cpp
   ClangDoc.cpp
   Generators.cpp
+  HTMLGenerator.cpp
   Mapper.cpp
   MDGenerator.cpp
   Representation.cpp

Modified: clang-tools-extra/trunk/clang-doc/Generators.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-doc/Generators.cpp?rev=365687&r1=365686&r2=365687&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-doc/Generators.cpp (original)
+++ clang-tools-extra/trunk/clang-doc/Generators.cpp Wed Jul 10 12:03:25 2019
@@ -25,14 +25,62 @@ findGeneratorByName(llvm::StringRef Form
                                              llvm::inconvertibleErrorCode());
 }
 
+// Enum conversion
+
+std::string getAccess(AccessSpecifier AS) {
+  switch (AS) {
+  case AccessSpecifier::AS_public:
+    return "public";
+  case AccessSpecifier::AS_protected:
+    return "protected";
+  case AccessSpecifier::AS_private:
+    return "private";
+  case AccessSpecifier::AS_none:
+    return {};
+  }
+  llvm_unreachable("Unknown AccessSpecifier");
+}
+
+std::string getTagType(TagTypeKind AS) {
+  switch (AS) {
+  case TagTypeKind::TTK_Class:
+    return "class";
+  case TagTypeKind::TTK_Union:
+    return "union";
+  case TagTypeKind::TTK_Interface:
+    return "interface";
+  case TagTypeKind::TTK_Struct:
+    return "struct";
+  case TagTypeKind::TTK_Enum:
+    return "enum";
+  }
+  llvm_unreachable("Unknown TagTypeKind");
+}
+
+// Generates a comma-separated list of Refs
+// Used to display the parents of a record
+std::string genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) {
+  std::string Buffer;
+  llvm::raw_string_ostream Stream(Buffer);
+  for (const auto &R : Refs) {
+    if (&R != Refs.begin())
+      Stream << ", ";
+    Stream << R.Name;
+  }
+  return Stream.str();
+}
+
 // This anchor is used to force the linker to link in the generated object file
 // and thus register the generators.
 extern volatile int YAMLGeneratorAnchorSource;
 extern volatile int MDGeneratorAnchorSource;
+extern volatile int HTMLGeneratorAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED YAMLGeneratorAnchorDest =
     YAMLGeneratorAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED MDGeneratorAnchorDest =
     MDGeneratorAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest =
+    HTMLGeneratorAnchorSource;
 
 } // namespace doc
 } // namespace clang

Modified: clang-tools-extra/trunk/clang-doc/Generators.h
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-doc/Generators.h?rev=365687&r1=365686&r2=365687&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-doc/Generators.h (original)
+++ clang-tools-extra/trunk/clang-doc/Generators.h Wed Jul 10 12:03:25 2019
@@ -34,6 +34,12 @@ typedef llvm::Registry<Generator> Genera
 llvm::Expected<std::unique_ptr<Generator>>
 findGeneratorByName(llvm::StringRef Format);
 
+std::string getAccess(AccessSpecifier AS);
+
+std::string getTagType(TagTypeKind AS);
+
+std::string genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs);
+
 } // namespace doc
 } // namespace clang
 

Added: clang-tools-extra/trunk/clang-doc/HTMLGenerator.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-doc/HTMLGenerator.cpp?rev=365687&view=auto
==============================================================================
--- clang-tools-extra/trunk/clang-doc/HTMLGenerator.cpp (added)
+++ clang-tools-extra/trunk/clang-doc/HTMLGenerator.cpp Wed Jul 10 12:03:25 2019
@@ -0,0 +1,517 @@
+//===-- HTMLGenerator.cpp - HTML Generator ----------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Generators.h"
+#include "Representation.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/Path.h"
+#include <string>
+
+using namespace llvm;
+
+namespace clang {
+namespace doc {
+
+template <typename Derived, typename Base,
+          typename = std::enable_if<std::is_base_of<Derived, Base>::value>>
+static void AppendVector(std::vector<Derived> &&New,
+                         std::vector<Base> &Original) {
+  std::move(New.begin(), New.end(), std::back_inserter(Original));
+}
+
+namespace {
+
+class HTMLTag {
+public:
+  // Any other tag can be added if required
+  enum TagType {
+    TAG_META,
+    TAG_TITLE,
+    TAG_DIV,
+    TAG_H1,
+    TAG_H2,
+    TAG_H3,
+    TAG_P,
+    TAG_UL,
+    TAG_LI,
+  };
+
+  HTMLTag() = default;
+  constexpr HTMLTag(TagType Value) : Value(Value) {}
+
+  operator TagType() const { return Value; }
+  operator bool() = delete;
+
+  bool IsSelfClosing() const;
+
+  bool HasInlineChildren() const;
+
+  llvm::SmallString<16> ToString() const;
+
+private:
+  TagType Value;
+};
+
+struct HTMLNode {
+  virtual ~HTMLNode() = default;
+
+  virtual void Render(llvm::raw_ostream &OS, int IndentationLevel) = 0;
+};
+
+struct TextNode : public HTMLNode {
+  TextNode(llvm::StringRef Text, bool Indented)
+      : Text(Text), Indented(Indented) {}
+
+  std::string Text; // Content of node
+  bool Indented; // Indicates if an indentation must be rendered before the text
+  void Render(llvm::raw_ostream &OS, int IndentationLevel) override;
+};
+
+struct TagNode : public HTMLNode {
+  TagNode(HTMLTag Tag)
+      : Tag(Tag), InlineChildren(Tag.HasInlineChildren()),
+        SelfClosing(Tag.IsSelfClosing()) {}
+  TagNode(HTMLTag Tag, const Twine &Text) : TagNode(Tag) {
+    Children.emplace_back(
+        llvm::make_unique<TextNode>(Text.str(), !InlineChildren));
+  }
+
+  HTMLTag Tag;         // Name of HTML Tag (p, div, h1)
+  bool InlineChildren; // Indicates if children nodes are rendered in the same
+                       // line as itself or if children must rendered in the
+                       // next line and with additional indentation
+  bool SelfClosing;    // Indicates if tag is self-closing
+  std::vector<std::unique_ptr<HTMLNode>> Children; // List of child nodes
+  llvm::StringMap<llvm::SmallString<16>>
+      Attributes; // List of key-value attributes for tag
+
+  void Render(llvm::raw_ostream &OS, int IndentationLevel) override;
+};
+
+constexpr const char *kDoctypeDecl = "<!DOCTYPE html>";
+
+struct HTMLFile {
+  std::vector<std::unique_ptr<HTMLNode>> Children; // List of child nodes
+  void Render(llvm::raw_ostream &OS) {
+    OS << kDoctypeDecl << "\n";
+    for (const auto &C : Children) {
+      C->Render(OS, 0);
+      OS << "\n";
+    }
+  }
+};
+
+} // namespace
+
+bool HTMLTag::IsSelfClosing() const {
+  switch (Value) {
+  case HTMLTag::TAG_META:
+    return true;
+  case HTMLTag::TAG_TITLE:
+  case HTMLTag::TAG_DIV:
+  case HTMLTag::TAG_H1:
+  case HTMLTag::TAG_H2:
+  case HTMLTag::TAG_H3:
+  case HTMLTag::TAG_P:
+  case HTMLTag::TAG_UL:
+  case HTMLTag::TAG_LI:
+    return false;
+  }
+}
+
+bool HTMLTag::HasInlineChildren() const {
+  switch (Value) {
+  case HTMLTag::TAG_META:
+  case HTMLTag::TAG_TITLE:
+  case HTMLTag::TAG_H1:
+  case HTMLTag::TAG_H2:
+  case HTMLTag::TAG_H3:
+  case HTMLTag::TAG_LI:
+    return true;
+  case HTMLTag::TAG_DIV:
+  case HTMLTag::TAG_P:
+  case HTMLTag::TAG_UL:
+    return false;
+  }
+}
+
+llvm::SmallString<16> HTMLTag::ToString() const {
+  switch (Value) {
+  case HTMLTag::TAG_META:
+    return llvm::SmallString<16>("meta");
+  case HTMLTag::TAG_TITLE:
+    return llvm::SmallString<16>("title");
+  case HTMLTag::TAG_DIV:
+    return llvm::SmallString<16>("div");
+  case HTMLTag::TAG_H1:
+    return llvm::SmallString<16>("h1");
+  case HTMLTag::TAG_H2:
+    return llvm::SmallString<16>("h2");
+  case HTMLTag::TAG_H3:
+    return llvm::SmallString<16>("h3");
+  case HTMLTag::TAG_P:
+    return llvm::SmallString<16>("p");
+  case HTMLTag::TAG_UL:
+    return llvm::SmallString<16>("ul");
+  case HTMLTag::TAG_LI:
+    return llvm::SmallString<16>("li");
+  }
+}
+
+void TextNode::Render(llvm::raw_ostream &OS, int IndentationLevel) {
+  if (Indented)
+    OS.indent(IndentationLevel * 2);
+  OS << Text;
+}
+
+void TagNode::Render(llvm::raw_ostream &OS, int IndentationLevel) {
+  OS.indent(IndentationLevel * 2);
+  OS << "<" << Tag.ToString();
+  for (const auto &A : Attributes)
+    OS << " " << A.getKey() << "=\"" << A.getValue() << "\"";
+  if (SelfClosing) {
+    OS << "/>";
+    return;
+  }
+  OS << ">";
+  if (!InlineChildren)
+    OS << "\n";
+  int ChildrenIndentation = InlineChildren ? 0 : IndentationLevel + 1;
+  for (const auto &C : Children) {
+    C->Render(OS, ChildrenIndentation);
+    if (!InlineChildren)
+      OS << "\n";
+  }
+  if (!InlineChildren)
+    OS.indent(IndentationLevel * 2);
+  OS << "</" << Tag.ToString() << ">";
+}
+
+// HTML generation
+
+static std::vector<std::unique_ptr<TagNode>> genHTML(const EnumInfo &I);
+static std::vector<std::unique_ptr<TagNode>> genHTML(const FunctionInfo &I);
+
+static std::vector<std::unique_ptr<TagNode>>
+genEnumsBlock(const std::vector<EnumInfo> &Enums) {
+  if (Enums.empty())
+    return {};
+
+  std::vector<std::unique_ptr<TagNode>> Out;
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_H2, "Enums"));
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_DIV));
+  auto &DivBody = Out.back();
+  for (const auto &E : Enums) {
+    std::vector<std::unique_ptr<TagNode>> Nodes = genHTML(E);
+    AppendVector(std::move(Nodes), DivBody->Children);
+  }
+  return Out;
+}
+
+static std::unique_ptr<TagNode>
+genEnumMembersBlock(const llvm::SmallVector<SmallString<16>, 4> &Members) {
+  if (Members.empty())
+    return nullptr;
+
+  auto List = llvm::make_unique<TagNode>(HTMLTag::TAG_UL);
+  for (const auto &M : Members)
+    List->Children.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_LI, M));
+  return List;
+}
+
+static std::vector<std::unique_ptr<TagNode>>
+genFunctionsBlock(const std::vector<FunctionInfo> &Functions) {
+  if (Functions.empty())
+    return {};
+
+  std::vector<std::unique_ptr<TagNode>> Out;
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_H2, "Functions"));
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_DIV));
+  auto &DivBody = Out.back();
+  for (const auto &F : Functions) {
+    std::vector<std::unique_ptr<TagNode>> Nodes = genHTML(F);
+    AppendVector(std::move(Nodes), DivBody->Children);
+  }
+  return Out;
+}
+
+static std::vector<std::unique_ptr<TagNode>>
+genRecordMembersBlock(const llvm::SmallVector<MemberTypeInfo, 4> &Members) {
+  if (Members.empty())
+    return {};
+
+  std::vector<std::unique_ptr<TagNode>> Out;
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_H2, "Members"));
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_UL));
+  auto &ULBody = Out.back();
+  for (const auto &M : Members) {
+    std::string Access = getAccess(M.Access);
+    if (Access != "")
+      Access = Access + " ";
+    ULBody->Children.emplace_back(llvm::make_unique<TagNode>(
+        HTMLTag::TAG_LI, Access + M.Type.Name + " " + M.Name));
+  }
+  return Out;
+}
+
+static std::vector<std::unique_ptr<TagNode>>
+genReferencesBlock(const std::vector<Reference> &References,
+                   llvm::StringRef Title) {
+  if (References.empty())
+    return {};
+
+  std::vector<std::unique_ptr<TagNode>> Out;
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_H2, Title));
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_UL));
+  auto &ULBody = Out.back();
+  for (const auto &R : References)
+    ULBody->Children.emplace_back(
+        llvm::make_unique<TagNode>(HTMLTag::TAG_LI, R.Name));
+  return Out;
+}
+
+static std::unique_ptr<TagNode> writeFileDefinition(const Location &L) {
+  return llvm::make_unique<TagNode>(
+      HTMLTag::TAG_P,
+      "Defined at line " + std::to_string(L.LineNumber) + " of " + L.Filename);
+}
+
+static std::unique_ptr<HTMLNode> genHTML(const CommentInfo &I) {
+  if (I.Kind == "FullComment") {
+    auto FullComment = llvm::make_unique<TagNode>(HTMLTag::TAG_DIV);
+    for (const auto &Child : I.Children) {
+      std::unique_ptr<HTMLNode> Node = genHTML(*Child);
+      if (Node)
+        FullComment->Children.emplace_back(std::move(Node));
+    }
+    return std::move(FullComment);
+  } else if (I.Kind == "ParagraphComment") {
+    auto ParagraphComment = llvm::make_unique<TagNode>(HTMLTag::TAG_P);
+    for (const auto &Child : I.Children) {
+      std::unique_ptr<HTMLNode> Node = genHTML(*Child);
+      if (Node)
+        ParagraphComment->Children.emplace_back(std::move(Node));
+    }
+    if (ParagraphComment->Children.empty())
+      return nullptr;
+    return std::move(ParagraphComment);
+  } else if (I.Kind == "TextComment") {
+    if (I.Text == "")
+      return nullptr;
+    return llvm::make_unique<TextNode>(I.Text, true);
+  }
+  return nullptr;
+}
+
+static std::unique_ptr<TagNode> genHTML(const std::vector<CommentInfo> &C) {
+  auto CommentBlock = llvm::make_unique<TagNode>(HTMLTag::TAG_DIV);
+  for (const auto &Child : C) {
+    if (std::unique_ptr<HTMLNode> Node = genHTML(Child))
+      CommentBlock->Children.emplace_back(std::move(Node));
+  }
+  return CommentBlock;
+}
+
+static std::vector<std::unique_ptr<TagNode>> genHTML(const EnumInfo &I) {
+  std::vector<std::unique_ptr<TagNode>> Out;
+  std::string EnumType;
+  if (I.Scoped)
+    EnumType = "enum class ";
+  else
+    EnumType = "enum ";
+
+  Out.emplace_back(
+      llvm::make_unique<TagNode>(HTMLTag::TAG_H3, EnumType + I.Name));
+
+  std::unique_ptr<TagNode> Node = genEnumMembersBlock(I.Members);
+  if (Node)
+    Out.emplace_back(std::move(Node));
+
+  if (I.DefLoc)
+    Out.emplace_back(writeFileDefinition(I.DefLoc.getValue()));
+
+  std::string Description;
+  if (!I.Description.empty())
+    Out.emplace_back(genHTML(I.Description));
+
+  return Out;
+}
+
+static std::vector<std::unique_ptr<TagNode>> genHTML(const FunctionInfo &I) {
+  std::vector<std::unique_ptr<TagNode>> Out;
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_H3, I.Name));
+
+  std::string Buffer;
+  llvm::raw_string_ostream Stream(Buffer);
+  for (const auto &P : I.Params) {
+    if (&P != I.Params.begin())
+      Stream << ", ";
+    Stream << P.Type.Name + " " + P.Name;
+  }
+
+  std::string Access = getAccess(I.Access);
+  if (Access != "")
+    Access = Access + " ";
+
+  Out.emplace_back(llvm::make_unique<TagNode>(
+      HTMLTag::TAG_P, Access + I.ReturnType.Type.Name + " " + I.Name + "(" +
+                          Stream.str() + ")"));
+
+  if (I.DefLoc)
+    Out.emplace_back(writeFileDefinition(I.DefLoc.getValue()));
+
+  std::string Description;
+  if (!I.Description.empty())
+    Out.emplace_back(genHTML(I.Description));
+
+  return Out;
+}
+
+static std::vector<std::unique_ptr<TagNode>> genHTML(const NamespaceInfo &I,
+                                                     std::string &InfoTitle) {
+  std::vector<std::unique_ptr<TagNode>> Out;
+  if (I.Name.str() == "")
+    InfoTitle = "Global Namespace";
+  else
+    InfoTitle = ("namespace " + I.Name).str();
+
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_H1, InfoTitle));
+
+  std::string Description;
+  if (!I.Description.empty())
+    Out.emplace_back(genHTML(I.Description));
+
+  std::vector<std::unique_ptr<TagNode>> ChildNamespaces =
+      genReferencesBlock(I.ChildNamespaces, "Namespaces");
+  AppendVector(std::move(ChildNamespaces), Out);
+  std::vector<std::unique_ptr<TagNode>> ChildRecords =
+      genReferencesBlock(I.ChildRecords, "Records");
+  AppendVector(std::move(ChildRecords), Out);
+
+  std::vector<std::unique_ptr<TagNode>> ChildFunctions =
+      genFunctionsBlock(I.ChildFunctions);
+  AppendVector(std::move(ChildFunctions), Out);
+  std::vector<std::unique_ptr<TagNode>> ChildEnums =
+      genEnumsBlock(I.ChildEnums);
+  AppendVector(std::move(ChildEnums), Out);
+
+  return Out;
+}
+
+static std::vector<std::unique_ptr<TagNode>> genHTML(const RecordInfo &I,
+                                                     std::string &InfoTitle) {
+  std::vector<std::unique_ptr<TagNode>> Out;
+  InfoTitle = (getTagType(I.TagType) + " " + I.Name).str();
+  Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_H1, InfoTitle));
+
+  if (I.DefLoc)
+    Out.emplace_back(writeFileDefinition(I.DefLoc.getValue()));
+
+  std::string Description;
+  if (!I.Description.empty())
+    Out.emplace_back(genHTML(I.Description));
+
+  std::string Parents = genReferenceList(I.Parents);
+  std::string VParents = genReferenceList(I.VirtualParents);
+  if (!Parents.empty() || !VParents.empty()) {
+    if (Parents.empty())
+      Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_P,
+                                                  "Inherits from " + VParents));
+    else if (VParents.empty())
+      Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_P,
+                                                  "Inherits from " + Parents));
+    else
+      Out.emplace_back(llvm::make_unique<TagNode>(
+          HTMLTag::TAG_P, "Inherits from " + Parents + ", " + VParents));
+  }
+
+  std::vector<std::unique_ptr<TagNode>> Members =
+      genRecordMembersBlock(I.Members);
+  AppendVector(std::move(Members), Out);
+  std::vector<std::unique_ptr<TagNode>> ChildRecords =
+      genReferencesBlock(I.ChildRecords, "Records");
+  AppendVector(std::move(ChildRecords), Out);
+
+  std::vector<std::unique_ptr<TagNode>> ChildFunctions =
+      genFunctionsBlock(I.ChildFunctions);
+  AppendVector(std::move(ChildFunctions), Out);
+  std::vector<std::unique_ptr<TagNode>> ChildEnums =
+      genEnumsBlock(I.ChildEnums);
+  AppendVector(std::move(ChildEnums), Out);
+
+  return Out;
+}
+
+/// Generator for HTML documentation.
+class HTMLGenerator : public Generator {
+public:
+  static const char *Format;
+
+  llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS) override;
+};
+
+const char *HTMLGenerator::Format = "html";
+
+llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS) {
+  HTMLFile F;
+
+  auto MetaNode = llvm::make_unique<TagNode>(HTMLTag::TAG_META);
+  MetaNode->Attributes.try_emplace("charset", "utf-8");
+  F.Children.emplace_back(std::move(MetaNode));
+
+  std::string InfoTitle;
+  Info CastedInfo;
+  auto MainContentNode = llvm::make_unique<TagNode>(HTMLTag::TAG_DIV);
+  switch (I->IT) {
+  case InfoType::IT_namespace: {
+    std::vector<std::unique_ptr<TagNode>> Nodes =
+        genHTML(*static_cast<clang::doc::NamespaceInfo *>(I), InfoTitle);
+    AppendVector(std::move(Nodes), MainContentNode->Children);
+    break;
+  }
+  case InfoType::IT_record: {
+    std::vector<std::unique_ptr<TagNode>> Nodes =
+        genHTML(*static_cast<clang::doc::RecordInfo *>(I), InfoTitle);
+    AppendVector(std::move(Nodes), MainContentNode->Children);
+    break;
+  }
+  case InfoType::IT_enum: {
+    std::vector<std::unique_ptr<TagNode>> Nodes =
+        genHTML(*static_cast<clang::doc::EnumInfo *>(I));
+    AppendVector(std::move(Nodes), MainContentNode->Children);
+    break;
+  }
+  case InfoType::IT_function: {
+    std::vector<std::unique_ptr<TagNode>> Nodes =
+        genHTML(*static_cast<clang::doc::FunctionInfo *>(I));
+    AppendVector(std::move(Nodes), MainContentNode->Children);
+    break;
+  }
+  case InfoType::IT_default:
+    return llvm::make_error<llvm::StringError>("Unexpected info type.\n",
+                                               llvm::inconvertibleErrorCode());
+  }
+
+  F.Children.emplace_back(
+      llvm::make_unique<TagNode>(HTMLTag::TAG_TITLE, InfoTitle));
+  F.Children.emplace_back(std::move(MainContentNode));
+  F.Render(OS);
+
+  return llvm::Error::success();
+}
+
+static GeneratorRegistry::Add<HTMLGenerator> HTML(HTMLGenerator::Format,
+                                                  "Generator for HTML output.");
+
+// This anchor is used to force the linker to link in the generated object
+// file and thus register the generator.
+volatile int HTMLGeneratorAnchorSource = 0;
+
+} // namespace doc
+} // namespace clang

Modified: clang-tools-extra/trunk/clang-doc/MDGenerator.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-doc/MDGenerator.cpp?rev=365687&r1=365686&r2=365687&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-doc/MDGenerator.cpp (original)
+++ clang-tools-extra/trunk/clang-doc/MDGenerator.cpp Wed Jul 10 12:03:25 2019
@@ -18,76 +18,33 @@ using namespace llvm;
 namespace clang {
 namespace doc {
 
-// Enum conversion
-
-std::string getAccess(AccessSpecifier AS) {
-  switch (AS) {
-  case AccessSpecifier::AS_public:
-    return "public";
-  case AccessSpecifier::AS_protected:
-    return "protected";
-  case AccessSpecifier::AS_private:
-    return "private";
-  case AccessSpecifier::AS_none:
-    return {};
-  }
-  llvm_unreachable("Unknown AccessSpecifier");
-}
-
-std::string getTagType(TagTypeKind AS) {
-  switch (AS) {
-  case TagTypeKind::TTK_Class:
-    return "class";
-  case TagTypeKind::TTK_Union:
-    return "union";
-  case TagTypeKind::TTK_Interface:
-    return "interface";
-  case TagTypeKind::TTK_Struct:
-    return "struct";
-  case TagTypeKind::TTK_Enum:
-    return "enum";
-  }
-  llvm_unreachable("Unknown TagTypeKind");
-}
-
 // Markdown generation
 
-std::string genItalic(const Twine &Text) { return "*" + Text.str() + "*"; }
-
-std::string genEmphasis(const Twine &Text) { return "**" + Text.str() + "**"; }
-
-std::string genLink(const Twine &Text, const Twine &Link) {
-  return "[" + Text.str() + "](" + Link.str() + ")";
+static std::string genItalic(const Twine &Text) {
+  return "*" + Text.str() + "*";
 }
 
-std::string genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs) {
-  std::string Buffer;
-  llvm::raw_string_ostream Stream(Buffer);
-  bool First = true;
-  for (const auto &R : Refs) {
-    if (!First)
-      Stream << ", ";
-    Stream << R.Name;
-    First = false;
-  }
-  return Stream.str();
+static std::string genEmphasis(const Twine &Text) {
+  return "**" + Text.str() + "**";
 }
 
-void writeLine(const Twine &Text, raw_ostream &OS) { OS << Text << "\n\n"; }
+static void writeLine(const Twine &Text, raw_ostream &OS) {
+  OS << Text << "\n\n";
+}
 
-void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
+static void writeNewLine(raw_ostream &OS) { OS << "\n\n"; }
 
-void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
+static void writeHeader(const Twine &Text, unsigned int Num, raw_ostream &OS) {
   OS << std::string(Num, '#') + " " + Text << "\n\n";
 }
 
-void writeFileDefinition(const Location &L, raw_ostream &OS) {
+static void writeFileDefinition(const Location &L, raw_ostream &OS) {
   OS << genItalic("Defined at line " + std::to_string(L.LineNumber) + " of " +
                   L.Filename)
      << "\n\n";
 }
 
-void writeDescription(const CommentInfo &I, raw_ostream &OS) {
+static void writeDescription(const CommentInfo &I, raw_ostream &OS) {
   if (I.Kind == "FullComment") {
     for (const auto &Child : I.Children)
       writeDescription(*Child, OS);
@@ -135,7 +92,7 @@ void writeDescription(const CommentInfo
   }
 }
 
-void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) {
+static void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) {
   if (I.Scoped)
     writeLine("| enum class " + I.Name + " |", OS);
   else
@@ -155,7 +112,7 @@ void genMarkdown(const EnumInfo &I, llvm
     writeDescription(C, OS);
 }
 
-void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) {
+static void genMarkdown(const FunctionInfo &I, llvm::raw_ostream &OS) {
   std::string Buffer;
   llvm::raw_string_ostream Stream(Buffer);
   bool First = true;
@@ -182,7 +139,7 @@ void genMarkdown(const FunctionInfo &I,
     writeDescription(C, OS);
 }
 
-void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) {
+static void genMarkdown(const NamespaceInfo &I, llvm::raw_ostream &OS) {
   if (I.Name == "")
     writeHeader("Global Namespace", 1, OS);
   else
@@ -221,7 +178,7 @@ void genMarkdown(const NamespaceInfo &I,
   }
 }
 
-void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) {
+static void genMarkdown(const RecordInfo &I, llvm::raw_ostream &OS) {
   writeHeader(getTagType(I.TagType) + " " + I.Name, 1, OS);
   if (I.DefLoc)
     writeFileDefinition(I.DefLoc.getValue(), OS);

Modified: clang-tools-extra/trunk/clang-doc/tool/ClangDocMain.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/clang-doc/tool/ClangDocMain.cpp?rev=365687&r1=365686&r2=365687&view=diff
==============================================================================
--- clang-tools-extra/trunk/clang-doc/tool/ClangDocMain.cpp (original)
+++ clang-tools-extra/trunk/clang-doc/tool/ClangDocMain.cpp Wed Jul 10 12:03:25 2019
@@ -65,6 +65,7 @@ static llvm::cl::opt<bool> DoxygenOnly(
 enum OutputFormatTy {
   md,
   yaml,
+  html,
 };
 
 static llvm::cl::opt<OutputFormatTy>
@@ -72,7 +73,9 @@ static llvm::cl::opt<OutputFormatTy>
                llvm::cl::values(clEnumValN(OutputFormatTy::yaml, "yaml",
                                            "Documentation in YAML format."),
                                 clEnumValN(OutputFormatTy::md, "md",
-                                           "Documentation in MD format.")),
+                                           "Documentation in MD format."),
+                                clEnumValN(OutputFormatTy::html, "html",
+                                           "Documentation in HTML format.")),
                llvm::cl::init(OutputFormatTy::yaml),
                llvm::cl::cat(ClangDocCategory));
 
@@ -82,6 +85,8 @@ std::string getFormatString() {
     return "yaml";
   case OutputFormatTy::md:
     return "md";
+  case OutputFormatTy::html:
+    return "html";
   }
   llvm_unreachable("Unknown OutputFormatTy");
 }

Modified: clang-tools-extra/trunk/unittests/clang-doc/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-doc/CMakeLists.txt?rev=365687&r1=365686&r2=365687&view=diff
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-doc/CMakeLists.txt (original)
+++ clang-tools-extra/trunk/unittests/clang-doc/CMakeLists.txt Wed Jul 10 12:03:25 2019
@@ -12,6 +12,7 @@ include_directories(
 add_extra_unittest(ClangDocTests
   BitcodeTest.cpp
   ClangDocTest.cpp
+  HTMLGeneratorTest.cpp
   MDGeneratorTest.cpp
   MergeTest.cpp
   SerializeTest.cpp

Added: clang-tools-extra/trunk/unittests/clang-doc/HTMLGeneratorTest.cpp
URL: http://llvm.org/viewvc/llvm-project/clang-tools-extra/trunk/unittests/clang-doc/HTMLGeneratorTest.cpp?rev=365687&view=auto
==============================================================================
--- clang-tools-extra/trunk/unittests/clang-doc/HTMLGeneratorTest.cpp (added)
+++ clang-tools-extra/trunk/unittests/clang-doc/HTMLGeneratorTest.cpp Wed Jul 10 12:03:25 2019
@@ -0,0 +1,276 @@
+//===-- clang-doc/HTMLGeneratorTest.cpp -----------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "ClangDocTest.h"
+#include "Generators.h"
+#include "Representation.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace doc {
+
+std::unique_ptr<Generator> getHTMLGenerator() {
+  auto G = doc::findGeneratorByName("html");
+  if (!G)
+    return nullptr;
+  return std::move(G.get());
+}
+
+TEST(HTMLGeneratorTest, emitNamespaceHTML) {
+  NamespaceInfo I;
+  I.Name = "Namespace";
+  I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
+
+  I.ChildNamespaces.emplace_back(EmptySID, "ChildNamespace",
+                                 InfoType::IT_namespace);
+  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record);
+  I.ChildFunctions.emplace_back();
+  I.ChildFunctions.back().Name = "OneFunction";
+  I.ChildEnums.emplace_back();
+  I.ChildEnums.back().Name = "OneEnum";
+
+  auto G = getHTMLGenerator();
+  assert(G);
+  std::string Buffer;
+  llvm::raw_string_ostream Actual(Buffer);
+  auto Err = G->generateDocForInfo(&I, Actual);
+  assert(!Err);
+  std::string Expected = R"raw(<!DOCTYPE html>
+<meta charset="utf-8"/>
+<title>namespace Namespace</title>
+<div>
+  <h1>namespace Namespace</h1>
+  <h2>Namespaces</h2>
+  <ul>
+    <li>ChildNamespace</li>
+  </ul>
+  <h2>Records</h2>
+  <ul>
+    <li>ChildStruct</li>
+  </ul>
+  <h2>Functions</h2>
+  <div>
+    <h3>OneFunction</h3>
+    <p>
+       OneFunction()
+    </p>
+  </div>
+  <h2>Enums</h2>
+  <div>
+    <h3>enum OneEnum</h3>
+  </div>
+</div>
+)raw";
+
+  EXPECT_EQ(Expected, Actual.str());
+}
+
+TEST(HTMLGeneratorTest, emitRecordHTML) {
+  RecordInfo I;
+  I.Name = "r";
+  I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
+
+  I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp"});
+  I.Loc.emplace_back(12, llvm::SmallString<16>{"test.cpp"});
+
+  I.Members.emplace_back("int", "X", AccessSpecifier::AS_private);
+  I.TagType = TagTypeKind::TTK_Class;
+  I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record);
+  I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record);
+
+  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record);
+  I.ChildFunctions.emplace_back();
+  I.ChildFunctions.back().Name = "OneFunction";
+  I.ChildEnums.emplace_back();
+  I.ChildEnums.back().Name = "OneEnum";
+
+  auto G = getHTMLGenerator();
+  assert(G);
+  std::string Buffer;
+  llvm::raw_string_ostream Actual(Buffer);
+  auto Err = G->generateDocForInfo(&I, Actual);
+  assert(!Err);
+  std::string Expected = R"raw(<!DOCTYPE html>
+<meta charset="utf-8"/>
+<title>class r</title>
+<div>
+  <h1>class r</h1>
+  <p>
+    Defined at line 10 of test.cpp
+  </p>
+  <p>
+    Inherits from F, G
+  </p>
+  <h2>Members</h2>
+  <ul>
+    <li>private int X</li>
+  </ul>
+  <h2>Records</h2>
+  <ul>
+    <li>ChildStruct</li>
+  </ul>
+  <h2>Functions</h2>
+  <div>
+    <h3>OneFunction</h3>
+    <p>
+       OneFunction()
+    </p>
+  </div>
+  <h2>Enums</h2>
+  <div>
+    <h3>enum OneEnum</h3>
+  </div>
+</div>
+)raw";
+
+  EXPECT_EQ(Expected, Actual.str());
+}
+
+TEST(HTMLGeneratorTest, emitFunctionHTML) {
+  FunctionInfo I;
+  I.Name = "f";
+  I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
+
+  I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp"});
+  I.Loc.emplace_back(12, llvm::SmallString<16>{"test.cpp"});
+
+  I.ReturnType = TypeInfo(EmptySID, "void", InfoType::IT_default);
+  I.Params.emplace_back("int", "P");
+  I.IsMethod = true;
+  I.Parent = Reference(EmptySID, "Parent", InfoType::IT_record);
+
+  auto G = getHTMLGenerator();
+  assert(G);
+  std::string Buffer;
+  llvm::raw_string_ostream Actual(Buffer);
+  auto Err = G->generateDocForInfo(&I, Actual);
+  assert(!Err);
+  std::string Expected = R"raw(<!DOCTYPE html>
+<meta charset="utf-8"/>
+<title></title>
+<div>
+  <h3>f</h3>
+  <p>
+    void f(int P)
+  </p>
+  <p>
+    Defined at line 10 of test.cpp
+  </p>
+</div>
+)raw";
+
+  EXPECT_EQ(Expected, Actual.str());
+}
+
+TEST(HTMLGeneratorTest, emitEnumHTML) {
+  EnumInfo I;
+  I.Name = "e";
+  I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
+
+  I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp"});
+  I.Loc.emplace_back(12, llvm::SmallString<16>{"test.cpp"});
+
+  I.Members.emplace_back("X");
+  I.Scoped = true;
+
+  auto G = getHTMLGenerator();
+  assert(G);
+  std::string Buffer;
+  llvm::raw_string_ostream Actual(Buffer);
+  auto Err = G->generateDocForInfo(&I, Actual);
+  assert(!Err);
+  std::string Expected = R"raw(<!DOCTYPE html>
+<meta charset="utf-8"/>
+<title></title>
+<div>
+  <h3>enum class e</h3>
+  <ul>
+    <li>X</li>
+  </ul>
+  <p>
+    Defined at line 10 of test.cpp
+  </p>
+</div>
+)raw";
+
+  EXPECT_EQ(Expected, Actual.str());
+}
+
+TEST(HTMLGeneratorTest, emitCommentHTML) {
+  FunctionInfo I;
+  I.Name = "f";
+  I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp"});
+  I.ReturnType = TypeInfo(EmptySID, "void", InfoType::IT_default);
+  I.Params.emplace_back("int", "I");
+  I.Params.emplace_back("int", "J");
+
+  CommentInfo Top;
+  Top.Kind = "FullComment";
+
+  Top.Children.emplace_back(llvm::make_unique<CommentInfo>());
+  CommentInfo *BlankLine = Top.Children.back().get();
+  BlankLine->Kind = "ParagraphComment";
+  BlankLine->Children.emplace_back(llvm::make_unique<CommentInfo>());
+  BlankLine->Children.back()->Kind = "TextComment";
+
+  Top.Children.emplace_back(llvm::make_unique<CommentInfo>());
+  CommentInfo *Brief = Top.Children.back().get();
+  Brief->Kind = "ParagraphComment";
+  Brief->Children.emplace_back(llvm::make_unique<CommentInfo>());
+  Brief->Children.back()->Kind = "TextComment";
+  Brief->Children.back()->Name = "ParagraphComment";
+  Brief->Children.back()->Text = " Brief description.";
+
+  Top.Children.emplace_back(llvm::make_unique<CommentInfo>());
+  CommentInfo *Extended = Top.Children.back().get();
+  Extended->Kind = "ParagraphComment";
+  Extended->Children.emplace_back(llvm::make_unique<CommentInfo>());
+  Extended->Children.back()->Kind = "TextComment";
+  Extended->Children.back()->Text = " Extended description that";
+  Extended->Children.emplace_back(llvm::make_unique<CommentInfo>());
+  Extended->Children.back()->Kind = "TextComment";
+  Extended->Children.back()->Text = " continues onto the next line.";
+
+  I.Description.emplace_back(std::move(Top));
+
+  auto G = getHTMLGenerator();
+  assert(G);
+  std::string Buffer;
+  llvm::raw_string_ostream Actual(Buffer);
+  auto Err = G->generateDocForInfo(&I, Actual);
+  assert(!Err);
+  std::string Expected = R"raw(<!DOCTYPE html>
+<meta charset="utf-8"/>
+<title></title>
+<div>
+  <h3>f</h3>
+  <p>
+    void f(int I, int J)
+  </p>
+  <p>
+    Defined at line 10 of test.cpp
+  </p>
+  <div>
+    <div>
+      <p>
+         Brief description.
+      </p>
+      <p>
+         Extended description that
+         continues onto the next line.
+      </p>
+    </div>
+  </div>
+</div>
+)raw";
+
+  EXPECT_EQ(Expected, Actual.str());
+}
+
+} // namespace doc
+} // namespace clang




More information about the cfe-commits mailing list