https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/138064
>From aecb686a8701784a2d49445843bbe2c3fae33a84 Mon Sep 17 00:00:00 2001 From: Paul Kirth <paulki...@google.com> Date: Wed, 30 Apr 2025 08:13:46 -0700 Subject: [PATCH] [clang-doc] Implement setupTemplateValue for HTMLMustacheGenerator This patch implements the business logic for setupTemplateValue, which was split from #133161. The implementation configures the relative path relationships between the various HTML components, and prepares them prior to their use in the generator. Co-authored-by: Peter Chou <peter.c...@mail.utoronto.ca> --- .../clang-doc/HTMLMustacheGenerator.cpp | 25 +- .../clang-doc/HTMLMustacheGeneratorTest.cpp | 416 +++++++++++++++++- 2 files changed, 432 insertions(+), 9 deletions(-) diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index ae446f7a2991b..8afea28c110c9 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -383,7 +383,7 @@ static json::Value extractValue(const RecordInfo &I, maybeInsertLocation(I.DefLoc, CDCtx, RecordValue); - StringRef BasePath = I.getRelativeFilePath(""); + SmallString<64> BasePath = I.getRelativeFilePath(""); extractScopeChildren(I.Children, RecordValue, BasePath, CDCtx); json::Value PublicMembers = Array(); json::Array &PubMemberRef = *PublicMembers.getAsArray(); @@ -417,8 +417,26 @@ static json::Value extractValue(const RecordInfo &I, static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V, Info *I) { - return createStringError(inconvertibleErrorCode(), - "setupTemplateValue is unimplemented"); + V.getAsObject()->insert({"ProjectName", CDCtx.ProjectName}); + json::Value StylesheetArr = Array(); + auto InfoPath = I->getRelativeFilePath(""); + SmallString<128> RelativePath = computeRelativePath("", InfoPath); + sys::path::native(RelativePath, sys::path::Style::posix); + for (const auto &FilePath : CDCtx.UserStylesheets) { + SmallString<128> StylesheetPath = RelativePath; + sys::path::append(StylesheetPath, sys::path::filename(FilePath)); + StylesheetArr.getAsArray()->emplace_back(StylesheetPath); + } + V.getAsObject()->insert({"Stylesheets", StylesheetArr}); + + json::Value ScriptArr = Array(); + for (auto Script : CDCtx.JsScripts) { + SmallString<128> JsPath = RelativePath; + sys::path::append(JsPath, sys::path::filename(Script)); + ScriptArr.getAsArray()->emplace_back(JsPath); + } + V.getAsObject()->insert({"Scripts", ScriptArr}); + return Error::success(); } Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS, @@ -429,6 +447,7 @@ Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS, extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx); if (auto Err = setupTemplateValue(CDCtx, V, I)) return Err; + assert(NamespaceTemplate && "NamespaceTemplate is nullptr."); NamespaceTemplate->render(V, OS); break; } diff --git a/clang-tools-extra/unittests/clang-doc/HTMLMustacheGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/HTMLMustacheGeneratorTest.cpp index 4d1af9d387092..681964969ec01 100644 --- a/clang-tools-extra/unittests/clang-doc/HTMLMustacheGeneratorTest.cpp +++ b/clang-tools-extra/unittests/clang-doc/HTMLMustacheGeneratorTest.cpp @@ -20,10 +20,10 @@ using namespace llvm; using namespace testing; +using namespace clang; using namespace clang::doc; -static const std::string ClangDocVersion = - clang::getClangToolFullVersion("clang-doc"); +static const std::string ClangDocVersion = getClangToolFullVersion("clang-doc"); static std::unique_ptr<Generator> getHTMLMustacheGenerator() { auto G = findGeneratorByName("mustache"); @@ -114,12 +114,416 @@ TEST(HTMLMustacheGeneratorTest, generateDocsForInfo) { I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record, "Namespace::ChildStruct", "Namespace"); I.Children.Functions.emplace_back(); - I.Children.Functions.back().Access = clang::AccessSpecifier::AS_none; + I.Children.Functions.back().Access = AccessSpecifier::AS_none; I.Children.Functions.back().Name = "OneFunction"; I.Children.Enums.emplace_back(); - EXPECT_THAT_ERROR(G->generateDocForInfo(&I, Actual, CDCtx), Failed()); + unittest::TempDir RootTestDirectory("generateDocForInfoTest", + /*Unique=*/true); + CDCtx.OutDirectory = RootTestDirectory.path(); + + getMustacheHtmlFiles(CLANG_DOC_TEST_ASSET_DIR, CDCtx); + + // FIXME: This is a terrible hack, since we can't initialize the templates + // directly. We'll need to update the interfaces so that we can call + // SetupTemplateFiles() from outsize of HTMLMustacheGenerator.cpp + EXPECT_THAT_ERROR(G->generateDocs(RootTestDirectory.path(), {}, CDCtx), + Succeeded()) + << "Failed to generate docs."; + + EXPECT_THAT_ERROR(G->generateDocForInfo(&I, Actual, CDCtx), Succeeded()); + + std::string Expected = R"raw(<!DOCTYPE html> +<html lang="en-US"> + <head> + <meta charset="utf-8"/> + <title>namespace Namespace</title> + <link rel="stylesheet" type="text/css" href="../clang-doc-mustache.css"/> + <link rel="stylesheet" type="text/css" href="../"/> + <script src="../mustache-index.js"></script> + <script src="../"></script> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css"> + <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/cpp.min.js"></script> + </head> + <body> + <nav class="navbar"> + Navbar + </nav> + <main> + <div class="container"> + <div class="sidebar"> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, + sed do eiusmod tempor incididunt ut labore et dolore magna + aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco + laboris nisi ut aliquip ex ea commodo consequat. + Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat + cupidatat non proident, sunt in culpa qui officia deserunt mollit + anim id est laborum + </div> + <div class="resizer" id="resizer"></div> + <div class="content"> + Content + </div> + </div> + </main> + </body> +</html> +)raw"; + EXPECT_EQ(Actual.str(), Expected); +} + +TEST(HTMLMustacheGeneratorTest, emitRecordHTML) { + auto G = getHTMLMustacheGenerator(); + assert(G && "Could not find HTMLMustacheGenerator"); + ClangDocContext CDCtx = getClangDocContext(); + std::string Buffer; + llvm::raw_string_ostream Actual(Buffer); + + unittest::TempDir RootTestDirectory("emitRecordHTML", + /*Unique=*/true); + CDCtx.OutDirectory = RootTestDirectory.path(); + + getMustacheHtmlFiles(CLANG_DOC_TEST_ASSET_DIR, CDCtx); + + // FIXME: This is a terrible hack, since we can't initialize the templates + // directly. We'll need to update the interfaces so that we can call + // SetupTemplateFiles() from outsize of HTMLMustacheGenerator.cpp + EXPECT_THAT_ERROR(G->generateDocs(RootTestDirectory.path(), {}, CDCtx), + Succeeded()) + << "Failed to generate docs."; + + CDCtx.RepositoryUrl = "http://www.repository.com"; + + RecordInfo I; + I.Name = "r"; + I.Path = "X/Y/Z"; + I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace); + + I.DefLoc = Location(10, 10, "dir/test.cpp", true); + I.Loc.emplace_back(12, 12, "test.cpp"); + + SmallString<16> PathTo; + llvm::sys::path::native("path/to", PathTo); + I.Members.emplace_back(clang::doc::TypeInfo("int"), "X", + AccessSpecifier::AS_private); + I.TagType = TagTypeKind::Class; + I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, "F", PathTo); + I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record); + + I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record, + "X::Y::Z::r::ChildStruct", "X/Y/Z/r"); + I.Children.Functions.emplace_back(); + I.Children.Functions.back().Name = "OneFunction"; + I.Children.Enums.emplace_back(); + I.Children.Enums.back().Name = "OneEnum"; + + EXPECT_THAT_ERROR(G->generateDocForInfo(&I, Actual, CDCtx), Succeeded()); + + std::string Expected = R"raw(<!DOCTYPE html> +<html lang="en-US"> +<head> + <meta charset="utf-8"/> + <title>r</title> + <link rel="stylesheet" type="text/css" href="../../../clang-doc-mustache.css"/> + <link rel="stylesheet" type="text/css" href="../../../"/> + <script src="../../../mustache-index.js"></script> + <script src="../../../"></script> + <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css"> + <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script> + <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/cpp.min.js"></script> +</head> +<body> +<nav class="navbar"> + <div class="navbar__container"> + <div class="navbar__logo"> + test-project + </div> + <div class="navbar__menu"> + <ul class="navbar__links"> + <li class="navbar__item"> + <a href="/" class="navbar__link">Namespace</a> + </li> + <li class="navbar__item"> + <a href="/" class="navbar__link">Class</a> + </li> + </ul> + </div> + </div> +</nav> +<main> + <div class="container"> + <div class="sidebar"> + <h2>class r</h2> + <ul> + <li class="sidebar-section"> + <a class="sidebar-item" href="#PublicMethods">Protected Members</a> + </li> + <ul> + <li class="sidebar-item-container"> + <a class="sidebar-item" href="#X">X</a> + </li> + </ul> + <li class="sidebar-section"> + <a class="sidebar-item" href="#PublicMethods">Public Method</a> + </li> + <ul> + <li class="sidebar-item-container"> + <a class="sidebar-item" href="#0000000000000000000000000000000000000000">OneFunction</a> + </li> + </ul> + <li class="sidebar-section"> + <a class="sidebar-item" href="#Enums">Enums</a> + </li> + <ul> + <li class="sidebar-item-container"> + <a class="sidebar-item" href="#0000000000000000000000000000000000000000">enum OneEnum</a> + </li> + </ul> + <li class="sidebar-section"> + <a class="sidebar-item" href="#Classes">Inner Classes</a> + </li> + <ul> + <li class="sidebar-item-container"> + <a class="sidebar-item" href="#0000000000000000000000000000000000000000">ChildStruct</a> + </li> + </ul> + </ul> + </div> + <div class="resizer" id="resizer"></div> + <div class="content"> + <section class="hero section-container"> + <div class="hero__title"> + <h1 class="hero__title-large">class r</h1> + </div> + </section> + <section id="ProtectedMembers" class="section-container"> + <h2>Protected Members</h2> + <div> + <div id="X" class="delimiter-container"> + <pre> +<code class="language-cpp code-clang-doc" >int X</code> + </pre> + </div> + </div> + </section> + <section id="PublicMethods" class="section-container"> + <h2>Public Methods</h2> + <div> +<div class="delimiter-container"> + <div id="0000000000000000000000000000000000000000"> + <pre> + <code class="language-cpp code-clang-doc"> + OneFunction () + </code> + </pre> + </div> +</div> + </div> + </section> + <section id="Enums" class="section-container"> + <h2>Enumerations</h2> + <div> +<div id="0000000000000000000000000000000000000000" class="delimiter-container"> + <div> + <pre> + <code class="language-cpp code-clang-doc"> +enum OneEnum + </code> + </pre> + </div> + <table class="table-wrapper"> + <tbody> + <tr> + <th>Name</th> + <th>Value</th> + </tr> + </tbody> + </table> +</div> + </div> + </section> + <section id="Classes" class="section-container"> + <h2>Inner Classes</h2> + <ul class="class-container"> + <li id="0000000000000000000000000000000000000000" style="max-height: 40px;"> +<a href="../../../X/Y/Z/r/ChildStruct.html"><pre><code class="language-cpp code-clang-doc" >class ChildStruct</code></pre></a> + </li> + </ul> + </section> + </div> + </div> +</main> +</body> +</html> +)raw"; + EXPECT_EQ(Actual.str(), Expected); +} + +TEST(HTMLGeneratorTest, emitFunctionHTML) { + auto G = getHTMLMustacheGenerator(); + assert(G && "Could not find HTMLMustacheGenerator"); + ClangDocContext CDCtx = getClangDocContext(); + std::string Buffer; + llvm::raw_string_ostream Actual(Buffer); + + unittest::TempDir RootTestDirectory("emitRecordHTML", + /*Unique=*/true); + CDCtx.OutDirectory = RootTestDirectory.path(); + + getMustacheHtmlFiles(CLANG_DOC_TEST_ASSET_DIR, CDCtx); + + // FIXME: This is a terrible hack, since we can't initialize the templates + // directly. We'll need to update the interfaces so that we can call + // SetupTemplateFiles() from outsize of HTMLMustacheGenerator.cpp + EXPECT_THAT_ERROR(G->generateDocs(RootTestDirectory.path(), {}, CDCtx), + Succeeded()) + << "Failed to generate docs."; + + CDCtx.RepositoryUrl = "http://www.repository.com"; + + FunctionInfo I; + I.Name = "f"; + I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace); + + I.DefLoc = Location(10, 10, "dir/test.cpp", true); + I.Loc.emplace_back(12, 12, "test.cpp"); + + I.Access = AccessSpecifier::AS_none; + + SmallString<16> PathTo; + llvm::sys::path::native("path/to", PathTo); + I.ReturnType = doc::TypeInfo( + Reference(EmptySID, "float", InfoType::IT_default, "float", PathTo)); + I.Params.emplace_back(doc::TypeInfo("int", PathTo), "P"); + I.IsMethod = true; + I.Parent = Reference(EmptySID, "Parent", InfoType::IT_record); + + auto Err = G->generateDocForInfo(&I, Actual, CDCtx); + assert(!Err); + std::string Expected = R"raw(IT_Function +)raw"; + + // FIXME: Functions are not handled yet. + EXPECT_EQ(Expected, Actual.str()); +} + +TEST(HTMLMustacheGeneratorTest, emitEnumHTML) { + auto G = getHTMLMustacheGenerator(); + assert(G && "Could not find HTMLMustacheGenerator"); + ClangDocContext CDCtx = getClangDocContext(); + std::string Buffer; + llvm::raw_string_ostream Actual(Buffer); + + unittest::TempDir RootTestDirectory("emitEnumHTML", + /*Unique=*/true); + CDCtx.OutDirectory = RootTestDirectory.path(); + + getMustacheHtmlFiles(CLANG_DOC_TEST_ASSET_DIR, CDCtx); + + // FIXME: This is a terrible hack, since we can't initialize the templates + // directly. We'll need to update the interfaces so that we can call + // SetupTemplateFiles() from outsize of HTMLMustacheGenerator.cpp + EXPECT_THAT_ERROR(G->generateDocs(RootTestDirectory.path(), {}, CDCtx), + Succeeded()) + << "Failed to generate docs."; + + CDCtx.RepositoryUrl = "http://www.repository.com"; + + EnumInfo I; + I.Name = "e"; + I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace); + + I.DefLoc = Location(10, 10, "test.cpp", true); + I.Loc.emplace_back(12, 12, "test.cpp"); + + I.Members.emplace_back("X"); + I.Scoped = true; + + auto Err = G->generateDocForInfo(&I, Actual, CDCtx); + assert(!Err); + + std::string Expected = R"raw(IT_enum +)raw"; + + // FIXME: Enums are not handled yet. + EXPECT_EQ(Expected, Actual.str()); +} + +TEST(HTMLMustacheGeneratorTest, emitCommentHTML) { + auto G = getHTMLMustacheGenerator(); + assert(G && "Could not find HTMLMustacheGenerator"); + ClangDocContext CDCtx = getClangDocContext(); + std::string Buffer; + llvm::raw_string_ostream Actual(Buffer); + + unittest::TempDir RootTestDirectory("emitCommentHTML", + /*Unique=*/true); + CDCtx.OutDirectory = RootTestDirectory.path(); + + getMustacheHtmlFiles(CLANG_DOC_TEST_ASSET_DIR, CDCtx); + + // FIXME: This is a terrible hack, since we can't initialize the templates + // directly. We'll need to update the interfaces so that we can call + // SetupTemplateFiles() from outsize of HTMLMustacheGenerator.cpp + EXPECT_THAT_ERROR(G->generateDocs(RootTestDirectory.path(), {}, CDCtx), + Succeeded()) + << "Failed to generate docs."; + + CDCtx.RepositoryUrl = "http://www.repository.com"; + + FunctionInfo I; + I.Name = "f"; + I.DefLoc = Location(10, 10, "test.cpp", true); + I.ReturnType = doc::TypeInfo("void"); + I.Params.emplace_back(doc::TypeInfo("int"), "I"); + I.Params.emplace_back(doc::TypeInfo("int"), "J"); + I.Access = AccessSpecifier::AS_none; + + CommentInfo Top; + Top.Kind = "FullComment"; + + Top.Children.emplace_back(std::make_unique<CommentInfo>()); + CommentInfo *BlankLine = Top.Children.back().get(); + BlankLine->Kind = "ParagraphComment"; + BlankLine->Children.emplace_back(std::make_unique<CommentInfo>()); + BlankLine->Children.back()->Kind = "TextComment"; + + Top.Children.emplace_back(std::make_unique<CommentInfo>()); + CommentInfo *Brief = Top.Children.back().get(); + Brief->Kind = "ParagraphComment"; + Brief->Children.emplace_back(std::make_unique<CommentInfo>()); + Brief->Children.back()->Kind = "TextComment"; + Brief->Children.back()->Name = "ParagraphComment"; + Brief->Children.back()->Text = " Brief description."; + + Top.Children.emplace_back(std::make_unique<CommentInfo>()); + CommentInfo *Extended = Top.Children.back().get(); + Extended->Kind = "ParagraphComment"; + Extended->Children.emplace_back(std::make_unique<CommentInfo>()); + Extended->Children.back()->Kind = "TextComment"; + Extended->Children.back()->Text = " Extended description that"; + Extended->Children.emplace_back(std::make_unique<CommentInfo>()); + Extended->Children.back()->Kind = "TextComment"; + Extended->Children.back()->Text = " continues onto the next line."; + + Top.Children.emplace_back(std::make_unique<CommentInfo>()); + CommentInfo *Entities = Top.Children.back().get(); + Entities->Kind = "ParagraphComment"; + Entities->Children.emplace_back(std::make_unique<CommentInfo>()); + Entities->Children.back()->Kind = "TextComment"; + Entities->Children.back()->Name = "ParagraphComment"; + Entities->Children.back()->Text = + " Comment with html entities: &, <, >, \", \'."; + + I.Description.emplace_back(std::move(Top)); + + auto Err = G->generateDocForInfo(&I, Actual, CDCtx); + assert(!Err); + std::string Expected = R"raw(IT_Function +)raw"; - std::string Expected = R"raw()raw"; - EXPECT_THAT(Actual.str(), Eq(Expected)); + // FIXME: Functions are not handled yet. + EXPECT_EQ(Expected, Actual.str()); } _______________________________________________ llvm-branch-commits mailing list llvm-branch-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits