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"); +} + +// 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/clang-doc/HTMLGenerator.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -0,0 +1,226 @@ +//===-- 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 { + +// HTML generation + +std::string genTag(const Twine &Text, const Twine &Tag) { + return "<" + Tag.str() + ">" + Text.str() + ""; +} + +static void writeLine(const Twine &Text, raw_ostream &OS) { + OS << genTag(Text, "p") << "\n"; +} + +static void writeHeader(const Twine &Text, const Twine &Num, raw_ostream &OS) { + OS << genTag(Text, "h" + Num) << "\n"; +} + +static void writeFileDefinition(const Location &L, raw_ostream &OS) { + writeLine("Defined at line " + std::to_string(L.LineNumber) + " of " + + L.Filename, + OS); +} + +static 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") { + OS << "

"; + for (const auto &Child : I.Children) + writeDescription(*Child, OS); + OS << "

\n"; + } else if (I.Kind == "TextComment") { + if (I.Text != "") + OS << I.Text; + } +} + +void genHTML(const EnumInfo &I, llvm::raw_ostream &OS) { + 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"; + } + + 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) { + writeHeader(I.Name, "3", OS); + + 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 != "") + writeLine(Access + " " + I.ReturnType.Type.Name + " " + I.Name + "(" + + Stream.str() + ")", + OS); + else + writeLine(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); + + if (!I.Description.empty()) { + for (const auto &C : I.Description) + writeDescription(C, OS); + } + + 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); + } +} + +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); + } + + 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); + } + + 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); + } + } + + 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); + } +} + +/// 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,38 +18,6 @@ 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() + "*"; } @@ -60,34 +28,23 @@ 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(); +static void writeLine(const Twine &Text, raw_ostream &OS) { + OS << Text << "\n\n"; } -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); 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,209 @@ +//===-- 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."; + + 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