Index: clang-tools-extra/trunk/clang-doc/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/clang-doc/CMakeLists.txt +++ clang-tools-extra/trunk/clang-doc/CMakeLists.txt @@ -8,6 +8,7 @@ BitcodeWriter.cpp ClangDoc.cpp Generators.cpp + HTMLGenerator.cpp Mapper.cpp MDGenerator.cpp Representation.cpp Index: clang-tools-extra/trunk/clang-doc/Generators.h =================================================================== --- clang-tools-extra/trunk/clang-doc/Generators.h +++ clang-tools-extra/trunk/clang-doc/Generators.h @@ -34,6 +34,12 @@ llvm::Expected> findGeneratorByName(llvm::StringRef Format); +std::string getAccess(AccessSpecifier AS); + +std::string getTagType(TagTypeKind AS); + +std::string genReferenceList(const llvm::SmallVectorImpl &Refs); + } // namespace doc } // namespace clang Index: clang-tools-extra/trunk/clang-doc/Generators.cpp =================================================================== --- clang-tools-extra/trunk/clang-doc/Generators.cpp +++ clang-tools-extra/trunk/clang-doc/Generators.cpp @@ -25,14 +25,62 @@ 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 &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 Index: clang-tools-extra/trunk/clang-doc/HTMLGenerator.cpp =================================================================== --- clang-tools-extra/trunk/clang-doc/HTMLGenerator.cpp +++ clang-tools-extra/trunk/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 + +using namespace llvm; + +namespace clang { +namespace doc { + +template ::value>> +static void AppendVector(std::vector &&New, + std::vector &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(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> Children; // List of child nodes + llvm::StringMap> + Attributes; // List of key-value attributes for tag + + void Render(llvm::raw_ostream &OS, int IndentationLevel) override; +}; + +constexpr const char *kDoctypeDecl = ""; + +struct HTMLFile { + std::vector> 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 << ""; +} + +// HTML generation + +static std::vector> genHTML(const EnumInfo &I); +static std::vector> genHTML(const FunctionInfo &I); + +static std::vector> +genEnumsBlock(const std::vector &Enums) { + if (Enums.empty()) + return {}; + + std::vector> Out; + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_H2, "Enums")); + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_DIV)); + auto &DivBody = Out.back(); + for (const auto &E : Enums) { + std::vector> Nodes = genHTML(E); + AppendVector(std::move(Nodes), DivBody->Children); + } + return Out; +} + +static std::unique_ptr +genEnumMembersBlock(const llvm::SmallVector, 4> &Members) { + if (Members.empty()) + return nullptr; + + auto List = llvm::make_unique(HTMLTag::TAG_UL); + for (const auto &M : Members) + List->Children.emplace_back(llvm::make_unique(HTMLTag::TAG_LI, M)); + return List; +} + +static std::vector> +genFunctionsBlock(const std::vector &Functions) { + if (Functions.empty()) + return {}; + + std::vector> Out; + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_H2, "Functions")); + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_DIV)); + auto &DivBody = Out.back(); + for (const auto &F : Functions) { + std::vector> Nodes = genHTML(F); + AppendVector(std::move(Nodes), DivBody->Children); + } + return Out; +} + +static std::vector> +genRecordMembersBlock(const llvm::SmallVector &Members) { + if (Members.empty()) + return {}; + + std::vector> Out; + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_H2, "Members")); + Out.emplace_back(llvm::make_unique(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( + HTMLTag::TAG_LI, Access + M.Type.Name + " " + M.Name)); + } + return Out; +} + +static std::vector> +genReferencesBlock(const std::vector &References, + llvm::StringRef Title) { + if (References.empty()) + return {}; + + std::vector> Out; + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_H2, Title)); + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_UL)); + auto &ULBody = Out.back(); + for (const auto &R : References) + ULBody->Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_LI, R.Name)); + return Out; +} + +static std::unique_ptr writeFileDefinition(const Location &L) { + return llvm::make_unique( + HTMLTag::TAG_P, + "Defined at line " + std::to_string(L.LineNumber) + " of " + L.Filename); +} + +static std::unique_ptr genHTML(const CommentInfo &I) { + if (I.Kind == "FullComment") { + auto FullComment = llvm::make_unique(HTMLTag::TAG_DIV); + for (const auto &Child : I.Children) { + std::unique_ptr 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(HTMLTag::TAG_P); + for (const auto &Child : I.Children) { + std::unique_ptr 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(I.Text, true); + } + return nullptr; +} + +static std::unique_ptr genHTML(const std::vector &C) { + auto CommentBlock = llvm::make_unique(HTMLTag::TAG_DIV); + for (const auto &Child : C) { + if (std::unique_ptr Node = genHTML(Child)) + CommentBlock->Children.emplace_back(std::move(Node)); + } + return CommentBlock; +} + +static std::vector> genHTML(const EnumInfo &I) { + std::vector> Out; + std::string EnumType; + if (I.Scoped) + EnumType = "enum class "; + else + EnumType = "enum "; + + Out.emplace_back( + llvm::make_unique(HTMLTag::TAG_H3, EnumType + I.Name)); + + std::unique_ptr 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> genHTML(const FunctionInfo &I) { + std::vector> Out; + Out.emplace_back(llvm::make_unique(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( + 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> genHTML(const NamespaceInfo &I, + std::string &InfoTitle) { + std::vector> Out; + if (I.Name.str() == "") + InfoTitle = "Global Namespace"; + else + InfoTitle = ("namespace " + I.Name).str(); + + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_H1, InfoTitle)); + + std::string Description; + if (!I.Description.empty()) + Out.emplace_back(genHTML(I.Description)); + + std::vector> ChildNamespaces = + genReferencesBlock(I.ChildNamespaces, "Namespaces"); + AppendVector(std::move(ChildNamespaces), Out); + std::vector> ChildRecords = + genReferencesBlock(I.ChildRecords, "Records"); + AppendVector(std::move(ChildRecords), Out); + + std::vector> ChildFunctions = + genFunctionsBlock(I.ChildFunctions); + AppendVector(std::move(ChildFunctions), Out); + std::vector> ChildEnums = + genEnumsBlock(I.ChildEnums); + AppendVector(std::move(ChildEnums), Out); + + return Out; +} + +static std::vector> genHTML(const RecordInfo &I, + std::string &InfoTitle) { + std::vector> Out; + InfoTitle = (getTagType(I.TagType) + " " + I.Name).str(); + Out.emplace_back(llvm::make_unique(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(HTMLTag::TAG_P, + "Inherits from " + VParents)); + else if (VParents.empty()) + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_P, + "Inherits from " + Parents)); + else + Out.emplace_back(llvm::make_unique( + HTMLTag::TAG_P, "Inherits from " + Parents + ", " + VParents)); + } + + std::vector> Members = + genRecordMembersBlock(I.Members); + AppendVector(std::move(Members), Out); + std::vector> ChildRecords = + genReferencesBlock(I.ChildRecords, "Records"); + AppendVector(std::move(ChildRecords), Out); + + std::vector> ChildFunctions = + genFunctionsBlock(I.ChildFunctions); + AppendVector(std::move(ChildFunctions), Out); + std::vector> 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(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(HTMLTag::TAG_DIV); + switch (I->IT) { + case InfoType::IT_namespace: { + std::vector> Nodes = + genHTML(*static_cast(I), InfoTitle); + AppendVector(std::move(Nodes), MainContentNode->Children); + break; + } + case InfoType::IT_record: { + std::vector> Nodes = + genHTML(*static_cast(I), InfoTitle); + AppendVector(std::move(Nodes), MainContentNode->Children); + break; + } + case InfoType::IT_enum: { + std::vector> Nodes = + genHTML(*static_cast(I)); + AppendVector(std::move(Nodes), MainContentNode->Children); + break; + } + case InfoType::IT_function: { + std::vector> Nodes = + genHTML(*static_cast(I)); + AppendVector(std::move(Nodes), MainContentNode->Children); + break; + } + case InfoType::IT_default: + return llvm::make_error("Unexpected info type.\n", + llvm::inconvertibleErrorCode()); + } + + F.Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_TITLE, InfoTitle)); + F.Children.emplace_back(std::move(MainContentNode)); + F.Render(OS); + + return llvm::Error::success(); +} + +static GeneratorRegistry::Add 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 Index: clang-tools-extra/trunk/clang-doc/MDGenerator.cpp =================================================================== --- clang-tools-extra/trunk/clang-doc/MDGenerator.cpp +++ clang-tools-extra/trunk/clang-doc/MDGenerator.cpp @@ -18,76 +18,33 @@ 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 &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 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 @@ 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 @@ 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 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); Index: clang-tools-extra/trunk/clang-doc/tool/ClangDocMain.cpp =================================================================== --- clang-tools-extra/trunk/clang-doc/tool/ClangDocMain.cpp +++ clang-tools-extra/trunk/clang-doc/tool/ClangDocMain.cpp @@ -65,6 +65,7 @@ enum OutputFormatTy { md, yaml, + html, }; static llvm::cl::opt @@ -72,7 +73,9 @@ 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 @@ return "yaml"; case OutputFormatTy::md: return "md"; + case OutputFormatTy::html: + return "html"; } llvm_unreachable("Unknown OutputFormatTy"); } Index: clang-tools-extra/trunk/unittests/clang-doc/CMakeLists.txt =================================================================== --- clang-tools-extra/trunk/unittests/clang-doc/CMakeLists.txt +++ clang-tools-extra/trunk/unittests/clang-doc/CMakeLists.txt @@ -12,6 +12,7 @@ add_extra_unittest(ClangDocTests BitcodeTest.cpp ClangDocTest.cpp + HTMLGeneratorTest.cpp MDGeneratorTest.cpp MergeTest.cpp SerializeTest.cpp Index: clang-tools-extra/trunk/unittests/clang-doc/HTMLGeneratorTest.cpp =================================================================== --- clang-tools-extra/trunk/unittests/clang-doc/HTMLGeneratorTest.cpp +++ clang-tools-extra/trunk/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 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( + +namespace Namespace +
+

namespace Namespace

+

Namespaces

+
    +
  • ChildNamespace
  • +
+

Records

+
    +
  • ChildStruct
  • +
+

Functions

+
+

OneFunction

+

+ OneFunction() +

+
+

Enums

+
+

enum OneEnum

+
+
+)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( + +class r +
+

class r

+

+ Defined at line 10 of test.cpp +

+

+ Inherits from F, G +

+

Members

+
    +
  • private int X
  • +
+

Records

+
    +
  • ChildStruct
  • +
+

Functions

+
+

OneFunction

+

+ OneFunction() +

+
+

Enums

+
+

enum OneEnum

+
+
+)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( + + +
+

f

+

+ void f(int P) +

+

+ Defined at line 10 of test.cpp +

+
+)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( + + +
+

enum class e

+
    +
  • X
  • +
+

+ Defined at line 10 of test.cpp +

+
+)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 *BlankLine = Top.Children.back().get(); + BlankLine->Kind = "ParagraphComment"; + BlankLine->Children.emplace_back(llvm::make_unique()); + BlankLine->Children.back()->Kind = "TextComment"; + + Top.Children.emplace_back(llvm::make_unique()); + CommentInfo *Brief = Top.Children.back().get(); + Brief->Kind = "ParagraphComment"; + Brief->Children.emplace_back(llvm::make_unique()); + Brief->Children.back()->Kind = "TextComment"; + Brief->Children.back()->Name = "ParagraphComment"; + Brief->Children.back()->Text = " Brief description."; + + Top.Children.emplace_back(llvm::make_unique()); + CommentInfo *Extended = Top.Children.back().get(); + Extended->Kind = "ParagraphComment"; + Extended->Children.emplace_back(llvm::make_unique()); + Extended->Children.back()->Kind = "TextComment"; + Extended->Children.back()->Text = " Extended description that"; + Extended->Children.emplace_back(llvm::make_unique()); + 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( + + +
+

f

+

+ void f(int I, int J) +

+

+ Defined at line 10 of test.cpp +

+
+
+

+ Brief description. +

+

+ Extended description that + continues onto the next line. +

+
+
+
+)raw"; + + EXPECT_EQ(Expected, Actual.str()); +} + +} // namespace doc +} // namespace clang