Index: clang-tools-extra/clang-doc/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-doc/CMakeLists.txt +++ clang-tools-extra/clang-doc/CMakeLists.txt @@ -9,6 +9,7 @@ BitcodeWriter.cpp ClangDoc.cpp Generators.cpp + HTMLGenerator.cpp Mapper.cpp MDGenerator.cpp Representation.cpp Index: clang-tools-extra/clang-doc/Generators.h =================================================================== --- clang-tools-extra/clang-doc/Generators.h +++ clang-tools-extra/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/clang-doc/Generators.cpp =================================================================== --- clang-tools-extra/clang-doc/Generators.cpp +++ clang-tools-extra/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"); +} + +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(); +} + // 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/clang-doc/HTMLGenerator.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -0,0 +1,279 @@ +//===-- 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 { + +namespace { + +// HTML generation + +std::string genTag(const Twine &Text, const Twine &Tag) { + return "<" + Tag.str() + ">" + Text.str() + ""; +} + +std::string genItalic(const Twine &Text) { return genTag(Text, "em"); } + +std::string genEmphasis(const Twine &Text) { return genTag(Text, "strong"); } + +void writeLine(const Twine &Text, raw_ostream &OS) { + OS << genTag(Text, "p") << "\n"; +} + +void writeNewLine(raw_ostream &OS) { OS << "
\n"; } + +void writeHeader(const Twine &Text, const Twine &Num, raw_ostream &OS) { + OS << genTag(Text, "h" + Num) << "\n"; +} + +void writeFileDefinition(const Location &L, raw_ostream &OS) { + writeLine(genItalic("Defined at line " + std::to_string(L.LineNumber) + + " of " + L.Filename), + OS); +} + +void writeDescription(const CommentInfo &I, raw_ostream &OS) { + if (I.Kind == "FullComment") { + for (const auto &Child : I.Children) + writeDescription(*Child, OS); + } else if (I.Kind == "ParagraphComment") { + for (const auto &Child : I.Children) + writeDescription(*Child, OS); + writeNewLine(OS); + } else if (I.Kind == "BlockCommandComment") { + OS << genEmphasis(I.Name); + for (const auto &Child : I.Children) + writeDescription(*Child, OS); + } else if (I.Kind == "InlineCommandComment") { + OS << genEmphasis(I.Name) << " " << I.Text; + } else if (I.Kind == "ParamCommandComment") { + std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; + OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n"; + } else if (I.Kind == "TParamCommandComment") { + std::string Direction = I.Explicit ? (" " + I.Direction).str() : ""; + OS << genEmphasis(I.ParamName) << I.Text << Direction << "\n\n"; + } else if (I.Kind == "VerbatimBlockComment") { + for (const auto &Child : I.Children) + writeDescription(*Child, OS); + } else if (I.Kind == "VerbatimBlockLineComment") { + OS << I.Text; + writeNewLine(OS); + } else if (I.Kind == "VerbatimLineComment") { + OS << I.Text; + writeNewLine(OS); + } else if (I.Kind == "HTMLStartTagComment") { + if (I.AttrKeys.size() != I.AttrValues.size()) + return; + std::string Buffer; + llvm::raw_string_ostream Attrs(Buffer); + for (unsigned Idx = 0; Idx < I.AttrKeys.size(); ++Idx) + Attrs << " \"" << I.AttrKeys[Idx] << "=" << I.AttrValues[Idx] << "\""; + + std::string CloseTag = I.SelfClosing ? "/>" : ">"; + writeLine("<" + I.Name + Attrs.str() + CloseTag, OS); + } else if (I.Kind == "HTMLEndTagComment") { + writeLine("", OS); + } else if (I.Kind == "TextComment") { + OS << I.Text; + } else { + OS << "Unknown comment kind: " << I.Kind << ".\n\n"; + } +} + +} // namespace + +void genHTML(const EnumInfo &I, llvm::raw_ostream &OS) { + if (I.Scoped) + writeLine("| enum class " + I.Name + " |", OS); + else + writeLine("| enum " + I.Name + " |", OS); + writeLine("--", OS); + + std::string Buffer; + llvm::raw_string_ostream Members(Buffer); + if (!I.Members.empty()) + for (const auto &N : I.Members) + Members << "| " << N << " |\n"; + writeLine(Members.str(), OS); + if (I.DefLoc) + writeFileDefinition(I.DefLoc.getValue(), OS); + + for (const auto &C : I.Description) + writeDescription(C, OS); +} + +void genHTML(const FunctionInfo &I, llvm::raw_ostream &OS) { + std::string Buffer; + llvm::raw_string_ostream Stream(Buffer); + bool First = true; + for (const auto &N : I.Params) { + if (!First) + Stream << ", "; + Stream << N.Type.Name + " " + N.Name; + First = false; + } + writeHeader(I.Name, "3", OS); + std::string Access = getAccess(I.Access); + if (Access != "") + writeLine(genItalic(Access + " " + I.ReturnType.Type.Name + " " + I.Name + + "(" + Stream.str() + ")"), + OS); + else + writeLine(genItalic(I.ReturnType.Type.Name + " " + I.Name + "(" + + Stream.str() + ")"), + OS); + if (I.DefLoc) + writeFileDefinition(I.DefLoc.getValue(), OS); + + for (const auto &C : I.Description) + writeDescription(C, OS); +} + +void genHTML(const NamespaceInfo &I, llvm::raw_ostream &OS) { + if (I.Name == "") + writeHeader("Global Namespace", "1", OS); + else + writeHeader("namespace " + I.Name, "1", OS); + writeNewLine(OS); + + if (!I.Description.empty()) { + for (const auto &C : I.Description) + writeDescription(C, OS); + writeNewLine(OS); + } + + if (!I.ChildNamespaces.empty()) { + writeHeader("Namespaces", "2", OS); + for (const auto &R : I.ChildNamespaces) + writeLine(R.Name, OS); + writeNewLine(OS); + } + if (!I.ChildRecords.empty()) { + writeHeader("Records", "2", OS); + for (const auto &R : I.ChildRecords) + writeLine(R.Name, OS); + writeNewLine(OS); + } + if (!I.ChildFunctions.empty()) { + writeHeader("Functions", "2", OS); + for (const auto &F : I.ChildFunctions) + genHTML(F, OS); + writeNewLine(OS); + } + if (!I.ChildEnums.empty()) { + writeHeader("Enums", "2", OS); + for (const auto &E : I.ChildEnums) + genHTML(E, OS); + writeNewLine(OS); + } +} + +void genHTML(const RecordInfo &I, llvm::raw_ostream &OS) { + writeHeader(getTagType(I.TagType) + " " + I.Name, "1", OS); + if (I.DefLoc) + writeFileDefinition(I.DefLoc.getValue(), OS); + + if (!I.Description.empty()) { + for (const auto &C : I.Description) + writeDescription(C, OS); + writeNewLine(OS); + } + + std::string Parents = genReferenceList(I.Parents); + std::string VParents = genReferenceList(I.VirtualParents); + if (!Parents.empty() || !VParents.empty()) { + if (Parents.empty()) + writeLine("Inherits from " + VParents, OS); + else if (VParents.empty()) + writeLine("Inherits from " + Parents, OS); + else + writeLine("Inherits from " + Parents + ", " + VParents, OS); + writeNewLine(OS); + } + + if (!I.Members.empty()) { + writeHeader("Members", "2", OS); + for (const auto Member : I.Members) { + std::string Access = getAccess(Member.Access); + if (Access != "") + writeLine(Access + " " + Member.Type.Name + " " + Member.Name, OS); + else + writeLine(Member.Type.Name + " " + Member.Name, OS); + } + writeNewLine(OS); + } + + if (!I.ChildRecords.empty()) { + writeHeader("Records", "2", OS); + for (const auto &R : I.ChildRecords) + writeLine(R.Name, OS); + writeNewLine(OS); + } + if (!I.ChildFunctions.empty()) { + writeHeader("Functions", "2", OS); + for (const auto &F : I.ChildFunctions) + genHTML(F, OS); + writeNewLine(OS); + } + if (!I.ChildEnums.empty()) { + writeHeader("Enums", "2", OS); + for (const auto &E : I.ChildEnums) + genHTML(E, OS); + writeNewLine(OS); + } +} + +/// 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) { + switch (I->IT) { + case InfoType::IT_namespace: + genHTML(*static_cast(I), OS); + break; + case InfoType::IT_record: + genHTML(*static_cast(I), OS); + break; + case InfoType::IT_enum: + genHTML(*static_cast(I), OS); + break; + case InfoType::IT_function: + genHTML(*static_cast(I), OS); + break; + case InfoType::IT_default: + return llvm::make_error("Unexpected info type.\n", + llvm::inconvertibleErrorCode()); + } + 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/clang-doc/MDGenerator.cpp =================================================================== --- clang-tools-extra/clang-doc/MDGenerator.cpp +++ clang-tools-extra/clang-doc/MDGenerator.cpp @@ -18,37 +18,7 @@ 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"); -} +namespace { // Markdown generation @@ -60,19 +30,6 @@ return "[" + Text.str() + "](" + Link.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(); -} - void writeLine(const Twine &Text, raw_ostream &OS) { OS << Text << "\n\n"; } void writeNewLine(raw_ostream &OS) { OS << "\n\n"; } @@ -135,6 +92,8 @@ } } +} // namespace + void genMarkdown(const EnumInfo &I, llvm::raw_ostream &OS) { if (I.Scoped) writeLine("| enum class " + I.Name + " |", OS); Index: clang-tools-extra/clang-doc/tool/ClangDocMain.cpp =================================================================== --- clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ clang-tools-extra/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/unittests/clang-doc/CMakeLists.txt =================================================================== --- clang-tools-extra/unittests/clang-doc/CMakeLists.txt +++ clang-tools-extra/unittests/clang-doc/CMakeLists.txt @@ -13,6 +13,7 @@ add_extra_unittest(ClangDocTests BitcodeTest.cpp ClangDocTest.cpp + HTMLGeneratorTest.cpp MDGeneratorTest.cpp MergeTest.cpp SerializeTest.cpp Index: clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp =================================================================== --- /dev/null +++ clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp @@ -0,0 +1,306 @@ +//===-- 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

+
+

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

+

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."; + + Top.Children.emplace_back(llvm::make_unique()); + CommentInfo *HTML = Top.Children.back().get(); + HTML->Kind = "ParagraphComment"; + HTML->Children.emplace_back(llvm::make_unique()); + HTML->Children.back()->Kind = "TextComment"; + HTML->Children.emplace_back(llvm::make_unique()); + HTML->Children.back()->Kind = "HTMLStartTagComment"; + HTML->Children.back()->Name = "ul"; + HTML->Children.back()->AttrKeys.emplace_back("class"); + HTML->Children.back()->AttrValues.emplace_back("test"); + HTML->Children.emplace_back(llvm::make_unique()); + HTML->Children.back()->Kind = "HTMLStartTagComment"; + HTML->Children.back()->Name = "li"; + HTML->Children.emplace_back(llvm::make_unique()); + HTML->Children.back()->Kind = "TextComment"; + HTML->Children.back()->Text = " Testing."; + HTML->Children.emplace_back(llvm::make_unique()); + HTML->Children.back()->Kind = "HTMLEndTagComment"; + HTML->Children.back()->Name = "ul"; + HTML->Children.back()->SelfClosing = true; + + Top.Children.emplace_back(llvm::make_unique()); + CommentInfo *Verbatim = Top.Children.back().get(); + Verbatim->Kind = "VerbatimBlockComment"; + Verbatim->Name = "verbatim"; + Verbatim->CloseName = "endverbatim"; + Verbatim->Children.emplace_back(llvm::make_unique()); + Verbatim->Children.back()->Kind = "VerbatimBlockLineComment"; + Verbatim->Children.back()->Text = " The description continues."; + + Top.Children.emplace_back(llvm::make_unique()); + CommentInfo *ParamOut = Top.Children.back().get(); + ParamOut->Kind = "ParamCommandComment"; + ParamOut->Direction = "[out]"; + ParamOut->ParamName = "I"; + ParamOut->Explicit = true; + ParamOut->Children.emplace_back(llvm::make_unique()); + ParamOut->Children.back()->Kind = "ParagraphComment"; + ParamOut->Children.back()->Children.emplace_back( + llvm::make_unique()); + ParamOut->Children.back()->Children.back()->Kind = "TextComment"; + ParamOut->Children.back()->Children.emplace_back( + llvm::make_unique()); + ParamOut->Children.back()->Children.back()->Kind = "TextComment"; + ParamOut->Children.back()->Children.back()->Text = " is a parameter."; + + Top.Children.emplace_back(llvm::make_unique()); + CommentInfo *ParamIn = Top.Children.back().get(); + ParamIn->Kind = "ParamCommandComment"; + ParamIn->Direction = "[in]"; + ParamIn->ParamName = "J"; + ParamIn->Children.emplace_back(llvm::make_unique()); + ParamIn->Children.back()->Kind = "ParagraphComment"; + ParamIn->Children.back()->Children.emplace_back( + llvm::make_unique()); + ParamIn->Children.back()->Children.back()->Kind = "TextComment"; + ParamIn->Children.back()->Children.back()->Text = " is a parameter."; + ParamIn->Children.back()->Children.emplace_back( + llvm::make_unique()); + ParamIn->Children.back()->Children.back()->Kind = "TextComment"; + + Top.Children.emplace_back(llvm::make_unique()); + CommentInfo *Return = Top.Children.back().get(); + Return->Kind = "BlockCommandComment"; + Return->Name = "return"; + Return->Explicit = true; + Return->Children.emplace_back(llvm::make_unique()); + Return->Children.back()->Kind = "ParagraphComment"; + Return->Children.back()->Children.emplace_back( + llvm::make_unique()); + Return->Children.back()->Children.back()->Kind = "TextComment"; + Return->Children.back()->Children.back()->Text = "void"; + + 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.
+

    +

  • + Testing.

+
+ The description continues.
+I [out] + +J + +returnvoid
+)raw"; + + EXPECT_EQ(Expected, Actual.str()); +} + +} // namespace doc +} // namespace clang