Index: clang-tools-extra/clang-doc/HTMLGenerator.cpp =================================================================== --- clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -18,67 +18,300 @@ namespace clang { namespace doc { +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; +}; + +struct HTMLFile { + llvm::SmallString<16> DoctypeDecl{""}; + std::vector> Children; // List of child nodes + void Render(llvm::raw_ostream &OS) { + OS << DoctypeDecl << "\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() << "\""; + OS << (SelfClosing ? "/>" : ">"); + 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); + if (!SelfClosing) + OS << ""; +} + // HTML generation -std::string genTag(const Twine &Text, const Twine &Tag) { - return "<" + Tag.str() + ">" + Text.str() + ""; +static void genHTML(const EnumInfo &I, TagNode *N); +static void genHTML(const FunctionInfo &I, TagNode *N); + +static void genEnumsBlock(const std::vector &Enums, TagNode *N) { + if (Enums.empty()) + return; + + N->Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_H2, "Enums")); + + auto EnumsBlock = llvm::make_unique(HTMLTag::TAG_DIV); + for (const auto &E : Enums) + genHTML(E, EnumsBlock.get()); + N->Children.push_back(std::move(EnumsBlock)); } -static void writeLine(const Twine &Text, raw_ostream &OS) { - OS << genTag(Text, "p") << "\n"; +static void +genEnumMembersBlock(const llvm::SmallVector, 4> &Members, + TagNode *N) { + if (Members.empty()) + return; + auto List = llvm::make_unique(HTMLTag::TAG_UL); + for (const auto &M : Members) + List->Children.emplace_back(llvm::make_unique(HTMLTag::TAG_LI, M)); + N->Children.push_back(std::move(List)); } -static void writeHeader(const Twine &Text, const Twine &Num, raw_ostream &OS) { - OS << genTag(Text, "h" + Num) << "\n"; +static void genFunctionsBlock(const std::vector &Functions, + TagNode *N) { + if (Functions.empty()) + return; + + N->Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_H2, "Functions")); + + auto FunctionsBlock = llvm::make_unique(HTMLTag::TAG_DIV); + for (const auto &F : Functions) + genHTML(F, FunctionsBlock.get()); + N->Children.push_back(std::move(FunctionsBlock)); } -static void writeFileDefinition(const Location &L, raw_ostream &OS) { - writeLine("Defined at line " + std::to_string(L.LineNumber) + " of " + - L.Filename, - OS); +static void +genRecordMembersBlock(const llvm::SmallVector &Members, + TagNode *N) { + if (Members.empty()) + return; + + N->Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_H2, "Members")); + + auto List = llvm::make_unique(HTMLTag::TAG_UL); + for (const auto &M : Members) { + std::string Access = getAccess(M.Access); + if (Access != "") + Access = Access + " "; + List->Children.emplace_back(llvm::make_unique( + HTMLTag::TAG_LI, Access + M.Type.Name + " " + M.Name)); + } + N->Children.push_back(std::move(List)); } -static void writeDescription(const CommentInfo &I, raw_ostream &OS) { +static void genReferencesBlock(const std::vector &References, + llvm::StringRef Title, TagNode *N) { + if (References.empty()) + return; + + N->Children.emplace_back(llvm::make_unique(HTMLTag::TAG_H2, Title)); + + auto List = llvm::make_unique(HTMLTag::TAG_UL); + for (const auto &R : References) + List->Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_LI, R.Name)); + N->Children.push_back(std::move(List)); +} + +static void writeFileDefinition(const Location &L, TagNode *N) { + N->Children.emplace_back(llvm::make_unique( + HTMLTag::TAG_P, + "Defined at line " + std::to_string(L.LineNumber) + " of " + L.Filename)); +} + +static void genHTML(const CommentInfo &I, TagNode *N) { if (I.Kind == "FullComment") { + auto FullComment = llvm::make_unique(HTMLTag::TAG_DIV); for (const auto &Child : I.Children) - writeDescription(*Child, OS); + genHTML(*Child, FullComment.get()); + N->Children.push_back(std::move(FullComment)); } else if (I.Kind == "ParagraphComment") { - OS << "

"; + auto ParagraphComment = llvm::make_unique(HTMLTag::TAG_P); for (const auto &Child : I.Children) - writeDescription(*Child, OS); - OS << "

\n"; + genHTML(*Child, ParagraphComment.get()); + if (!ParagraphComment->Children.empty()) + N->Children.push_back(std::move(ParagraphComment)); } else if (I.Kind == "TextComment") { if (I.Text != "") - OS << I.Text; + N->Children.emplace_back(llvm::make_unique(I.Text, true)); } } -void genHTML(const EnumInfo &I, llvm::raw_ostream &OS) { +static void genHTML(const std::vector &C, TagNode *N) { + auto CommentBlock = llvm::make_unique(HTMLTag::TAG_DIV); + for (const auto &Child : C) + genHTML(Child, CommentBlock.get()); + N->Children.push_back(std::move(CommentBlock)); +} + +static void genHTML(const EnumInfo &I, TagNode *N) { std::string EnumType; if (I.Scoped) EnumType = "enum class "; else EnumType = "enum "; - writeHeader(EnumType + I.Name, "3", OS); - std::string Buffer; - llvm::raw_string_ostream Members(Buffer); - if (!I.Members.empty()) { - Members << "\n"; - for (const auto &N : I.Members) - Members << genTag(N, "li") << "\n"; - OS << genTag(Members.str(), "ul") << "\n"; - } + N->Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_H3, EnumType + I.Name)); + + genEnumMembersBlock(I.Members, N); if (I.DefLoc) - writeFileDefinition(I.DefLoc.getValue(), OS); + writeFileDefinition(I.DefLoc.getValue(), N); - for (const auto &C : I.Description) - writeDescription(C, OS); + std::string Description; + if (!I.Description.empty()) + genHTML(I.Description, N); } -void genHTML(const FunctionInfo &I, llvm::raw_ostream &OS) { - writeHeader(I.Name, "3", OS); +static void genHTML(const FunctionInfo &I, TagNode *N) { + N->Children.emplace_back(llvm::make_unique(HTMLTag::TAG_H3, I.Name)); std::string Buffer; llvm::raw_string_ostream Stream(Buffer); @@ -87,101 +320,75 @@ Stream << ", "; Stream << P.Type.Name + " " + P.Name; } + std::string Access = getAccess(I.Access); if (Access != "") - writeLine(Access + " " + I.ReturnType.Type.Name + " " + I.Name + "(" + - Stream.str() + ")", - OS); - else - writeLine(I.ReturnType.Type.Name + " " + I.Name + "(" + Stream.str() + ")", - OS); + Access = Access + " "; + + N->Children.emplace_back(llvm::make_unique( + HTMLTag::TAG_P, Access + I.ReturnType.Type.Name + " " + I.Name + "(" + + Stream.str() + ")")); + if (I.DefLoc) - writeFileDefinition(I.DefLoc.getValue(), OS); + writeFileDefinition(I.DefLoc.getValue(), N); - for (const auto &C : I.Description) - writeDescription(C, OS); + std::string Description; + if (!I.Description.empty()) + genHTML(I.Description, N); } -void genHTML(const NamespaceInfo &I, llvm::raw_ostream &OS) { - if (I.Name == "") - writeHeader("Global Namespace", "1", OS); +static void genHTML(const NamespaceInfo &I, TagNode *N, + std::string &InfoTitle) { + if (I.Name.str() == "") + InfoTitle = "Global Namespace"; else - writeHeader("namespace " + I.Name, "1", OS); + InfoTitle = ("namespace " + I.Name).str(); - if (!I.Description.empty()) { - for (const auto &C : I.Description) - writeDescription(C, OS); - } + N->Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_H1, InfoTitle)); - if (!I.ChildNamespaces.empty()) { - writeHeader("Namespaces", "2", OS); - for (const auto &R : I.ChildNamespaces) - writeLine(R.Name, OS); - } - if (!I.ChildRecords.empty()) { - writeHeader("Records", "2", OS); - for (const auto &R : I.ChildRecords) - writeLine(R.Name, OS); - } - if (!I.ChildFunctions.empty()) { - writeHeader("Functions", "2", OS); - for (const auto &F : I.ChildFunctions) - genHTML(F, OS); - } - if (!I.ChildEnums.empty()) { - writeHeader("Enums", "2", OS); - for (const auto &E : I.ChildEnums) - genHTML(E, OS); - } + std::string Description; + if (!I.Description.empty()) + genHTML(I.Description, N); + + genReferencesBlock(I.ChildNamespaces, "Namespaces", N); + genReferencesBlock(I.ChildRecords, "Records", N); + + genFunctionsBlock(I.ChildFunctions, N); + genEnumsBlock(I.ChildEnums, N); } -void genHTML(const RecordInfo &I, llvm::raw_ostream &OS) { - writeHeader(getTagType(I.TagType) + " " + I.Name, "1", OS); +static void genHTML(const RecordInfo &I, TagNode *N, std::string &InfoTitle) { + InfoTitle = (getTagType(I.TagType) + " " + I.Name).str(); + N->Children.emplace_back( + llvm::make_unique(HTMLTag::TAG_H1, InfoTitle)); + if (I.DefLoc) - writeFileDefinition(I.DefLoc.getValue(), OS); + writeFileDefinition(I.DefLoc.getValue(), N); - if (!I.Description.empty()) { - for (const auto &C : I.Description) - writeDescription(C, OS); - } + std::string Description; + if (!I.Description.empty()) + genHTML(I.Description, N); 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); + N->Children.emplace_back(llvm::make_unique( + HTMLTag::TAG_P, "Inherits from " + VParents)); else if (VParents.empty()) - writeLine("Inherits from " + Parents, OS); + N->Children.emplace_back(llvm::make_unique( + HTMLTag::TAG_P, "Inherits from " + Parents)); else - writeLine("Inherits from " + Parents + ", " + VParents, OS); + N->Children.emplace_back(llvm::make_unique( + HTMLTag::TAG_P, "Inherits from " + Parents + ", " + VParents)); } - 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); - } - } + genRecordMembersBlock(I.Members, N); + genReferencesBlock(I.ChildRecords, "Records", N); - if (!I.ChildRecords.empty()) { - writeHeader("Records", "2", OS); - for (const auto &R : I.ChildRecords) - writeLine(R.Name, OS); - } - if (!I.ChildFunctions.empty()) { - writeHeader("Functions", "2", OS); - for (const auto &F : I.ChildFunctions) - genHTML(F, OS); - } - if (!I.ChildEnums.empty()) { - writeHeader("Enums", "2", OS); - for (const auto &E : I.ChildEnums) - genHTML(E, OS); - } + genFunctionsBlock(I.ChildFunctions, N); + genEnumsBlock(I.ChildEnums, N); } /// Generator for HTML documentation. @@ -195,31 +402,50 @@ 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.push_back(std::move(MetaNode)); + + std::string InfoTitle; + + auto MainContentNode = llvm::make_unique(HTMLTag::TAG_DIV); + switch (I->IT) { case InfoType::IT_namespace: - genHTML(*static_cast(I), OS); + genHTML(*static_cast(I), MainContentNode.get(), + InfoTitle); break; case InfoType::IT_record: - genHTML(*static_cast(I), OS); + genHTML(*static_cast(I), MainContentNode.get(), + InfoTitle); break; case InfoType::IT_enum: - genHTML(*static_cast(I), OS); + genHTML(*static_cast(I), MainContentNode.get()); break; case InfoType::IT_function: - genHTML(*static_cast(I), OS); + genHTML(*static_cast(I), MainContentNode.get()); 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.push_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. +// 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 Index: clang-tools-extra/clang-doc/MDGenerator.cpp =================================================================== --- clang-tools-extra/clang-doc/MDGenerator.cpp +++ clang-tools-extra/clang-doc/MDGenerator.cpp @@ -20,11 +20,11 @@ // Markdown generation -std::string genItalic(const Twine &Text) { return "*" + Text.str() + "*"; } +static std::string genItalic(const Twine &Text) { return "*" + Text.str() + "*"; } -std::string genEmphasis(const Twine &Text) { return "**" + Text.str() + "**"; } +static std::string genEmphasis(const Twine &Text) { return "**" + Text.str() + "**"; } -std::string genLink(const Twine &Text, const Twine &Link) { +static std::string genLink(const Twine &Text, const Twine &Link) { return "[" + Text.str() + "](" + Link.str() + ")"; } @@ -92,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 @@ -112,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; @@ -139,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 @@ -178,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/unittests/clang-doc/HTMLGeneratorTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp +++ clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp @@ -40,16 +40,31 @@ 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

+ 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()); @@ -80,18 +95,37 @@ 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

+ 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()); @@ -116,9 +150,18 @@ 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

+ std::string Expected = R"raw( + + +
+

f

+

+ void f(int P) +

+

+ Defined at line 10 of test.cpp +

+
)raw"; EXPECT_EQ(Expected, Actual.str()); @@ -141,11 +184,18 @@ 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

+ std::string Expected = R"raw( + + +
+

enum class e

+
    +
  • X
  • +
+

+ Defined at line 10 of test.cpp +

+
)raw"; EXPECT_EQ(Expected, Actual.str()); @@ -194,12 +244,29 @@ 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.

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