summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulie Hockett <juliehockett@google.com>2019-07-10 19:03:25 +0000
committerJulie Hockett <juliehockett@google.com>2019-07-10 19:03:25 +0000
commit671bac743673f2149709a44b8c27b028bdb38d00 (patch)
tree047324afb3ead23467749f0591ba47ac9f049cd3
parent6522bedefe3b9c0454c8dc6c75360c28dca5f8b4 (diff)
downloadbcm5719-llvm-671bac743673f2149709a44b8c27b028bdb38d00.tar.gz
bcm5719-llvm-671bac743673f2149709a44b8c27b028bdb38d00.zip
[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@gmail.com) Differential Revision: https://reviews.llvm.org/D63857 llvm-svn: 365687
-rw-r--r--clang-tools-extra/clang-doc/CMakeLists.txt1
-rw-r--r--clang-tools-extra/clang-doc/Generators.cpp48
-rw-r--r--clang-tools-extra/clang-doc/Generators.h6
-rw-r--r--clang-tools-extra/clang-doc/HTMLGenerator.cpp517
-rw-r--r--clang-tools-extra/clang-doc/MDGenerator.cpp73
-rw-r--r--clang-tools-extra/clang-doc/tool/ClangDocMain.cpp7
-rw-r--r--clang-tools-extra/unittests/clang-doc/CMakeLists.txt1
-rw-r--r--clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp276
8 files changed, 870 insertions, 59 deletions
diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt b/clang-tools-extra/clang-doc/CMakeLists.txt
index a2123989e54..c301ad5afac 100644
--- a/clang-tools-extra/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/CMakeLists.txt
@@ -8,6 +8,7 @@ add_clang_library(clangDoc
BitcodeWriter.cpp
ClangDoc.cpp
Generators.cpp
+ HTMLGenerator.cpp
Mapper.cpp
MDGenerator.cpp
Representation.cpp
diff --git a/clang-tools-extra/clang-doc/Generators.cpp b/clang-tools-extra/clang-doc/Generators.cpp
index e3b672ada29..1cc830200f8 100644
--- a/clang-tools-extra/clang-doc/Generators.cpp
+++ b/clang-tools-extra/clang-doc/Generators.cpp
@@ -25,14 +25,62 @@ findGeneratorByName(llvm::StringRef Format) {
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
diff --git a/clang-tools-extra/clang-doc/Generators.h b/clang-tools-extra/clang-doc/Generators.h
index 25e5e0b4e53..89aeab9bd24 100644
--- a/clang-tools-extra/clang-doc/Generators.h
+++ b/clang-tools-extra/clang-doc/Generators.h
@@ -34,6 +34,12 @@ typedef llvm::Registry<Generator> GeneratorRegistry;
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
diff --git a/clang-tools-extra/clang-doc/HTMLGenerator.cpp b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
new file mode 100644
index 00000000000..12ba6840409
--- /dev/null
+++ b/clang-tools-extra/clang-doc/HTMLGenerator.cpp
@@ -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
diff --git a/clang-tools-extra/clang-doc/MDGenerator.cpp b/clang-tools-extra/clang-doc/MDGenerator.cpp
index d7784200fac..69e961467af 100644
--- a/clang-tools-extra/clang-doc/MDGenerator.cpp
+++ b/clang-tools-extra/clang-doc/MDGenerator.cpp
@@ -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 &I, raw_ostream &OS) {
}
}
-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::raw_ostream &OS) {
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, llvm::raw_ostream &OS) {
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, llvm::raw_ostream &OS) {
}
}
-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);
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index f6ac3d28b3b..1e37ce819bf 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -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");
}
diff --git a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
index 7e3746fa8d1..f2899d1e96b 100644
--- a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
@@ -12,6 +12,7 @@ include_directories(
add_extra_unittest(ClangDocTests
BitcodeTest.cpp
ClangDocTest.cpp
+ HTMLGeneratorTest.cpp
MDGeneratorTest.cpp
MergeTest.cpp
SerializeTest.cpp
diff --git a/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
new file mode 100644
index 00000000000..fe6bc6122ef
--- /dev/null
+++ b/clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
@@ -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
OpenPOWER on IntegriCloud