summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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