[llvm-branch-commits] [clang-tools-extra] [clang-doc] Implement setupTemplateValue for HTMLMustacheGenerator (PR #138064)
Paul Kirth via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri May 16 16:38:50 PDT 2025
https://github.com/ilovepi updated https://github.com/llvm/llvm-project/pull/138064
>From fa0b1fbf9af85f5c8f55957c46eb42c878f28a2a Mon Sep 17 00:00:00 2001
From: Paul Kirth <paulkirth at 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.chou at mail.utoronto.ca>
---
.../clang-doc/HTMLMustacheGenerator.cpp | 27 +-
.../clang-doc/HTMLMustacheGeneratorTest.cpp | 416 +++++++++++++++++-
2 files changed, 434 insertions(+), 9 deletions(-)
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index 801f54670fb89..46e3e331db03d 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -391,7 +391,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();
@@ -425,8 +425,28 @@ 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::Style::posix,
+ 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::Style::posix,
+ 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,
@@ -437,6 +457,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());
}
More information about the llvm-branch-commits
mailing list