Index: clang-tools-extra/clang-doc/Generators.h =================================================================== --- clang-tools-extra/clang-doc/Generators.h +++ clang-tools-extra/clang-doc/Generators.h @@ -25,6 +25,14 @@ public: virtual ~Generator() = default; + // This should be called before calling any generateDocForInfo. + // FIXME: This currently needs to be run before generating any individual + // documentation pages, since the content it generates is directly included in + // every page. A better design would be to lazily include it in the individual + // documentation pages, in which case this could be run in parallel with calls + // to generateDocForInfo(). + static Index genIndex(const std::vector> &Infos); + // Write out the decl info in the specified format. virtual llvm::Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, const ClangDocContext &CDCtx) = 0; Index: clang-tools-extra/clang-doc/Generators.cpp =================================================================== --- clang-tools-extra/clang-doc/Generators.cpp +++ clang-tools-extra/clang-doc/Generators.cpp @@ -13,6 +13,30 @@ namespace clang { namespace doc { +Index Generator::genIndex(const std::vector> &Infos) { + Index Idx; + for (const auto &Info : Infos) { + Index *I = &Idx; + for (auto R = Info->Namespace.rbegin(), E = Info->Namespace.rend(); R != E; + ++R) { + auto It = std::find(I->Children.begin(), I->Children.end(), R->USR); + if (It != I->Children.end()) + I = &*It; + else { + I->Children.emplace_back(R->USR, R->Name, R->RefType, R->Path); + I = &I->Children.back(); + } + } + auto It = std::find(I->Children.begin(), I->Children.end(), Info->USR); + if (It == I->Children.end()) + I->Children.emplace_back(Info->USR, Info->Name, Info->IT, Info->Path); + else if (It->Path.empty()) + It->Path = I->Path; + } + Idx.sort(); + return Idx; +} + llvm::Expected> findGeneratorByName(llvm::StringRef Format) { for (auto I = GeneratorRegistry::begin(), E = GeneratorRegistry::end(); Index: clang-tools-extra/clang-doc/HTMLGenerator.cpp =================================================================== --- clang-tools-extra/clang-doc/HTMLGenerator.cpp +++ clang-tools-extra/clang-doc/HTMLGenerator.cpp @@ -25,17 +25,18 @@ public: // Any other tag can be added if required enum TagType { - TAG_META, - TAG_TITLE, + TAG_A, TAG_DIV, TAG_H1, TAG_H2, TAG_H3, - TAG_P, - TAG_UL, TAG_LI, - TAG_A, TAG_LINK, + TAG_META, + TAG_P, + TAG_SPAN, + TAG_TITLE, + TAG_UL, }; HTMLTag() = default; @@ -106,15 +107,16 @@ case HTMLTag::TAG_META: case HTMLTag::TAG_LINK: return true; - case HTMLTag::TAG_TITLE: + case HTMLTag::TAG_A: case HTMLTag::TAG_DIV: case HTMLTag::TAG_H1: case HTMLTag::TAG_H2: case HTMLTag::TAG_H3: + case HTMLTag::TAG_LI: case HTMLTag::TAG_P: + case HTMLTag::TAG_SPAN: + case HTMLTag::TAG_TITLE: case HTMLTag::TAG_UL: - case HTMLTag::TAG_LI: - case HTMLTag::TAG_A: return false; } llvm_unreachable("Unhandled HTMLTag::TagType"); @@ -122,10 +124,8 @@ 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_A: + return llvm::SmallString<16>("a"); case HTMLTag::TAG_DIV: return llvm::SmallString<16>("div"); case HTMLTag::TAG_H1: @@ -134,16 +134,20 @@ 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"); - case HTMLTag::TAG_A: - return llvm::SmallString<16>("a"); case HTMLTag::TAG_LINK: return llvm::SmallString<16>("link"); + case HTMLTag::TAG_META: + return llvm::SmallString<16>("meta"); + case HTMLTag::TAG_P: + return llvm::SmallString<16>("p"); + case HTMLTag::TAG_SPAN: + return llvm::SmallString<16>("span"); + case HTMLTag::TAG_TITLE: + return llvm::SmallString<16>("title"); + case HTMLTag::TAG_UL: + return llvm::SmallString<16>("ul"); } llvm_unreachable("Unhandled HTMLTag::TagType"); } @@ -358,6 +362,40 @@ "Defined at line " + std::to_string(L.LineNumber) + " of " + L.Filename); } +static std::vector> +genCommonFileNodes(StringRef Title, StringRef InfoPath, const ClangDocContext &CDCtx) { + std::vector> Out; + auto MetaNode = llvm::make_unique(HTMLTag::TAG_META); + MetaNode->Attributes.try_emplace("charset", "utf-8"); + Out.emplace_back(std::move(MetaNode)); + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_TITLE, Title)); + std::vector> StylesheetsNodes = + genStylesheetsHTML(InfoPath, CDCtx); + AppendVector(std::move(StylesheetsNodes), Out); + return Out; +} + +static std::vector> genHTML(const Index &Index, + StringRef InfoPath) { + std::vector> Out; + if (!Index.Name.empty()) { + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_SPAN)); + auto &SpanBody = Out.back(); + SpanBody->Children.emplace_back(genTypeReference(Index, InfoPath)); + } + if (Index.Children.empty()) + return Out; + Out.emplace_back(llvm::make_unique(HTMLTag::TAG_UL)); + const auto &UlBody = Out.back(); + for (const auto &C : Index.Children) { + auto LiBody = llvm::make_unique(HTMLTag::TAG_LI); + std::vector> Nodes = genHTML(C, InfoPath); + AppendVector(std::move(Nodes), LiBody->Children); + UlBody->Children.emplace_back(std::move(LiBody)); + } + return Out; +} + static std::unique_ptr genHTML(const CommentInfo &I) { if (I.Kind == "FullComment") { auto FullComment = llvm::make_unique(HTMLTag::TAG_DIV); @@ -554,13 +592,7 @@ llvm::Error HTMLGenerator::generateDocForInfo(Info *I, llvm::raw_ostream &OS, const ClangDocContext &CDCtx) { 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: { @@ -592,11 +624,11 @@ llvm::inconvertibleErrorCode()); } - F.Children.emplace_back( - llvm::make_unique(HTMLTag::TAG_TITLE, InfoTitle)); - std::vector> StylesheetsNodes = - genStylesheetsHTML(I->Path, CDCtx); - AppendVector(std::move(StylesheetsNodes), F.Children); + std::vector> BasicNodes = + genCommonFileNodes(InfoTitle, I->Path, CDCtx); + AppendVector(std::move(BasicNodes), F.Children); + std::vector> Index = genHTML(CDCtx.Idx, I->Path); + AppendVector(std::move(Index), F.Children); F.Children.emplace_back(std::move(MainContentNode)); F.Render(OS); Index: clang-tools-extra/clang-doc/Representation.h =================================================================== --- clang-tools-extra/clang-doc/Representation.h +++ clang-tools-extra/clang-doc/Representation.h @@ -338,6 +338,18 @@ llvm::SmallVector, 4> Members; // List of enum members. }; +struct Index : public Reference { + Index() = default; + Index(SymbolID USR, StringRef Name, InfoType IT, StringRef Path) + : Reference(USR, Name, IT, Path) {} + bool operator==(const SymbolID &Other) const { return USR == Other; } + bool operator<(const Index &Other) const { return Name < Other.Name; } + + std::vector Children; + + void sort(); +}; + // TODO: Add functionality to include separate markdown pages. // A standalone function to call to merge a vector of infos into one. @@ -347,10 +359,17 @@ mergeInfos(std::vector> &Values); struct ClangDocContext { + ClangDocContext() = default; + ClangDocContext(tooling::ExecutionContext *ECtx, bool PublicOnly, + StringRef OutDirectory, + std::vector UserStylesheets) + : ECtx(ECtx), PublicOnly(PublicOnly), OutDirectory(OutDirectory), + UserStylesheets(UserStylesheets) {} tooling::ExecutionContext *ECtx; bool PublicOnly; std::string OutDirectory; std::vector UserStylesheets; + Index Idx; }; } // namespace doc Index: clang-tools-extra/clang-doc/Representation.cpp =================================================================== --- clang-tools-extra/clang-doc/Representation.cpp +++ clang-tools-extra/clang-doc/Representation.cpp @@ -229,5 +229,11 @@ return llvm::SmallString<16>(""); } +void Index::sort() { + std::sort(Children.begin(), Children.end()); + for (auto &C : Children) + C.sort(); +} + } // namespace doc } // namespace clang 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 @@ -243,16 +243,23 @@ // First reducing phase (reduce all decls into one info per decl). llvm::outs() << "Reducing " << USRToInfos.size() << " infos...\n"; + std::vector> ReducedInfos; for (auto &Group : USRToInfos) { auto Reduced = doc::mergeInfos(Group.getValue()); if (!Reduced) { llvm::errs() << llvm::toString(Reduced.takeError()); continue; } + ReducedInfos.emplace_back(std::move(Reduced.get())); + } + + llvm::outs() << "Constructing index...\n"; + CDCtx.Idx = clang::doc::Generator::genIndex(ReducedInfos); - doc::Info *I = Reduced.get().get(); - auto InfoPath = getInfoOutputFile(OutDirectory, I->Path, I->extractName(), - "." + Format); + llvm::outs() << "Generating docs...\n"; + for (auto &Info : ReducedInfos) { + auto InfoPath = getInfoOutputFile(OutDirectory, Info->Path, + Info->extractName(), "." + Format); if (!InfoPath) { llvm::errs() << toString(InfoPath.takeError()) << "\n"; return 1; @@ -264,7 +271,7 @@ continue; } - if (auto Err = G->get()->generateDocForInfo(I, InfoOS, CDCtx)) + if (auto Err = G->get()->generateDocForInfo(Info.get(), InfoOS, CDCtx)) llvm::errs() << toString(std::move(Err)) << "\n"; } 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 @@ -12,6 +12,7 @@ add_extra_unittest(ClangDocTests BitcodeTest.cpp ClangDocTest.cpp + GeneratorTest.cpp HTMLGeneratorTest.cpp MDGeneratorTest.cpp MergeTest.cpp Index: clang-tools-extra/unittests/clang-doc/ClangDocTest.h =================================================================== --- clang-tools-extra/unittests/clang-doc/ClangDocTest.h +++ clang-tools-extra/unittests/clang-doc/ClangDocTest.h @@ -44,6 +44,8 @@ void CheckNamespaceInfo(NamespaceInfo *Expected, NamespaceInfo *Actual); void CheckRecordInfo(RecordInfo *Expected, RecordInfo *Actual); +void CheckIndex(Index &Expected, Index &Actual); + } // namespace doc } // namespace clang Index: clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp =================================================================== --- clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp +++ clang-tools-extra/unittests/clang-doc/ClangDocTest.cpp @@ -63,6 +63,7 @@ void CheckReference(Reference &Expected, Reference &Actual) { EXPECT_EQ(Expected.Name, Actual.Name); EXPECT_EQ(Expected.RefType, Actual.RefType); + EXPECT_EQ(Expected.Path, Actual.Path); } void CheckTypeInfo(TypeInfo *Expected, TypeInfo *Actual) { @@ -180,5 +181,12 @@ CheckEnumInfo(&Expected->ChildEnums[Idx], &Actual->ChildEnums[Idx]); } +void CheckIndex(Index &Expected, Index &Actual) { + CheckReference(Expected, Actual); + ASSERT_EQ(Expected.Children.size(), Actual.Children.size()); + for (size_t Idx = 0; Idx < Actual.Children.size(); ++Idx) + CheckIndex(Expected.Children[Idx], Actual.Children[Idx]); +} + } // namespace doc } // namespace clang Index: clang-tools-extra/unittests/clang-doc/GeneratorTest.cpp =================================================================== --- /dev/null +++ clang-tools-extra/unittests/clang-doc/GeneratorTest.cpp @@ -0,0 +1,70 @@ +//===-- clang-doc/GeneratorTest.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 "Serialize.h" +#include "gtest/gtest.h" + +namespace clang { +namespace doc { + +TEST(GeneratorTest, emitIndex) { + std::vector> Infos; + Infos.emplace_back(llvm::make_unique()); + Info *InfoA = Infos.back().get(); + InfoA->Name = "A"; + InfoA->USR = serialize::hashUSR("1"); + Infos.emplace_back(llvm::make_unique()); + Info *InfoC = Infos.back().get(); + InfoC->Name = "C"; + InfoC->USR = serialize::hashUSR("3"); + Reference RefB = Reference("B"); + RefB.USR = serialize::hashUSR("2"); + InfoC->Namespace = {std::move(RefB)}; + Infos.emplace_back(llvm::make_unique()); + Info *InfoD = Infos.back().get(); + InfoD->Name = "D"; + InfoD->USR = serialize::hashUSR("4"); + Infos.emplace_back(llvm::make_unique()); + Info *InfoF = Infos.back().get(); + InfoF->Name = "F"; + InfoF->USR = serialize::hashUSR("6"); + Reference RefD = Reference("D"); + RefD.USR = serialize::hashUSR("4"); + Reference RefE = Reference("E"); + RefE.USR = serialize::hashUSR("5"); + InfoF->Namespace = {std::move(RefE), std::move(RefD)}; + Index Idx = Generator::genIndex(Infos); + + Index ExpectedIdx; + Index IndexA; + IndexA.Name = "A"; + ExpectedIdx.Children.emplace_back(std::move(IndexA)); + Index IndexB; + IndexB.Name = "B"; + Index IndexC; + IndexC.Name = "C"; + IndexB.Children.emplace_back(std::move(IndexC)); + ExpectedIdx.Children.emplace_back(std::move(IndexB)); + Index IndexD; + IndexD.Name = "D"; + Index IndexE; + IndexE.Name = "E"; + Index IndexF; + IndexF.Name = "F"; + IndexE.Children.emplace_back(std::move(IndexF)); + IndexD.Children.emplace_back(std::move(IndexE)); + ExpectedIdx.Children.emplace_back(std::move(IndexD)); + + CheckIndex(ExpectedIdx, Idx); +} + +} // namespace doc +} // namespace clang 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 @@ -9,6 +9,7 @@ #include "ClangDocTest.h" #include "Generators.h" #include "Representation.h" +#include "Serialize.h" #include "gtest/gtest.h" namespace clang { @@ -315,5 +316,79 @@ EXPECT_EQ(Expected, Actual.str()); } +TEST(HTMLGeneratorTest, emitIndexHTML) { + RecordInfo I; + I.Path = ""; + ClangDocContext CDCtx; + std::vector> Infos; + Infos.emplace_back(llvm::make_unique()); + Info *InfoA = Infos.back().get(); + InfoA->Name = "A"; + InfoA->USR = serialize::hashUSR("1"); + Infos.emplace_back(llvm::make_unique()); + Info *InfoC = Infos.back().get(); + InfoC->Name = "C"; + InfoC->USR = serialize::hashUSR("3"); + Reference RefB = Reference("B"); + RefB.USR = serialize::hashUSR("2"); + InfoC->Namespace = {std::move(RefB)}; + Infos.emplace_back(llvm::make_unique()); + Info *InfoD = Infos.back().get(); + InfoD->Name = "D"; + InfoD->USR = serialize::hashUSR("4"); + Infos.emplace_back(llvm::make_unique()); + Info *InfoF = Infos.back().get(); + InfoF->Name = "F"; + InfoF->USR = serialize::hashUSR("6"); + Reference RefD = Reference("D"); + RefD.USR = serialize::hashUSR("4"); + Reference RefE = Reference("E"); + RefE.USR = serialize::hashUSR("5"); + InfoF->Namespace = {std::move(RefE), std::move(RefD)}; + CDCtx.Idx = Generator::genIndex(Infos); + + auto G = getHTMLGenerator(); + assert(G); + std::string Buffer; + llvm::raw_string_ostream Actual(Buffer); + auto Err = G->generateDocForInfo(&I, Actual, CDCtx); + assert(!Err); + std::string Expected = R"raw( + +struct +
    +
  • + A +
  • +
  • + B +
      +
    • + C +
    • +
    +
  • +
  • + D +
      +
    • + E +
        +
      • + F +
      • +
      +
    • +
    +
  • +
+
+

struct

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