[clang-tools-extra] [clang-doc] Serialize repository URL in Mustache templates (PR #174914)
Erick Velez via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 8 21:07:23 PST 2026
https://github.com/evelez7 updated https://github.com/llvm/llvm-project/pull/174914
>From 085d7a3311937873fe7d4fed9aaf271d10f91e58 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Fri, 2 Jan 2026 16:19:17 -0800
Subject: [PATCH] add line prefix from CDCtx, fix extra tag
---
clang-tools-extra/clang-doc/JSONGenerator.cpp | 119 +++++++++++-------
.../clang-doc/assets/alias-template.mustache | 2 +-
.../clang-doc/assets/class-template.mustache | 4 +-
.../clang-doc/assets/enum-template.mustache | 4 +-
.../assets/function-template.mustache | 2 +-
.../assets/namespace-template.mustache | 4 +-
clang-tools-extra/test/clang-doc/enum.cpp | 10 +-
.../test/clang-doc/mustache-index.cpp | 4 +-
.../test/clang-doc/templates.cpp | 3 +-
9 files changed, 88 insertions(+), 64 deletions(-)
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index ad76c40d49db8..12d439c6acbe4 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -25,7 +25,8 @@ const char *JSONGenerator::Format = "json";
static void serializeInfo(const ConstraintInfo &I, Object &Obj);
static void serializeInfo(const RecordInfo &I, Object &Obj,
- const std::optional<StringRef> &RepositoryUrl);
+ const std::optional<StringRef> &RepositoryUrl,
+ const std::optional<StringRef> &RepositoryLinePrefix);
static void serializeReference(const Reference &Ref, Object &ReferenceObj);
@@ -75,7 +76,8 @@ static std::string infoTypeToString(InfoType IT) {
static json::Object
serializeLocation(const Location &Loc,
- const std::optional<StringRef> RepositoryUrl) {
+ const std::optional<StringRef> RepositoryUrl,
+ const std::optional<StringRef> RepositoryLinePrefix) {
Object LocationObj = Object();
LocationObj["LineNumber"] = Loc.StartLineNumber;
LocationObj["Filename"] = Loc.Filename;
@@ -84,7 +86,14 @@ serializeLocation(const Location &Loc,
return LocationObj;
SmallString<128> FileURL(*RepositoryUrl);
sys::path::append(FileURL, sys::path::Style::posix, Loc.Filename);
- FileURL += "#" + std::to_string(Loc.StartLineNumber);
+
+ std::string LinePrefix;
+ if (!RepositoryLinePrefix)
+ LinePrefix = "#L";
+ else
+ LinePrefix = *RepositoryLinePrefix;
+
+ FileURL += LinePrefix + std::to_string(Loc.StartLineNumber);
LocationObj["FileURL"] = FileURL;
return LocationObj;
}
@@ -338,7 +347,8 @@ static void generateContext(const Info &I, Object &Obj) {
static void
serializeCommonAttributes(const Info &I, json::Object &Obj,
- const std::optional<StringRef> RepositoryUrl) {
+ const std::optional<StringRef> RepositoryUrl,
+ const std::optional<StringRef> RepositoryLinePrefix) {
insertNonEmpty("Name", I.Name, Obj);
Obj["USR"] = toHex(toStringRef(I.USR));
Obj["InfoType"] = infoTypeToString(I.IT);
@@ -378,8 +388,8 @@ serializeCommonAttributes(const Info &I, json::Object &Obj,
if (I.IT != InfoType::IT_namespace) {
const auto *Symbol = static_cast<const SymbolInfo *>(&I);
if (Symbol->DefLoc)
- Obj["Location"] =
- serializeLocation(Symbol->DefLoc.value(), RepositoryUrl);
+ Obj["Location"] = serializeLocation(Symbol->DefLoc.value(), RepositoryUrl,
+ RepositoryLinePrefix);
}
if (!I.Contexts.empty())
@@ -405,11 +415,12 @@ static void serializeReference(const Reference &Ref, Object &ReferenceObj) {
// differently. Only enums, records, and typedefs are handled here.
static void
serializeCommonChildren(const ScopeChildren &Children, json::Object &Obj,
- const std::optional<StringRef> RepositoryUrl) {
- static auto SerializeInfo = [RepositoryUrl](const auto &Info,
- Object &Object) {
- serializeInfo(Info, Object, RepositoryUrl);
- };
+ const std::optional<StringRef> RepositoryUrl,
+ const std::optional<StringRef> RepositoryLinePrefix) {
+ static auto SerializeInfo =
+ [RepositoryUrl, RepositoryLinePrefix](const auto &Info, Object &Object) {
+ serializeInfo(Info, Object, RepositoryUrl, RepositoryLinePrefix);
+ };
if (!Children.Enums.empty()) {
serializeArray(Children.Enums, Obj, "Enums", SerializeInfo);
@@ -492,8 +503,9 @@ static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
}
static void serializeInfo(const ConceptInfo &I, Object &Obj,
- const std::optional<StringRef> &RepositoryUrl) {
- serializeCommonAttributes(I, Obj, RepositoryUrl);
+ const std::optional<StringRef> &RepositoryUrl,
+ const std::optional<StringRef> &RepositoryLine) {
+ serializeCommonAttributes(I, Obj, RepositoryUrl, RepositoryLine);
Obj["IsType"] = I.IsType;
Obj["ConstraintExpression"] = I.ConstraintExpression;
serializeInfo(I.Template, Obj);
@@ -517,8 +529,9 @@ static void serializeInfo(const FieldTypeInfo &I, Object &Obj) {
}
static void serializeInfo(const FunctionInfo &F, json::Object &Obj,
- const std::optional<StringRef> RepositoryURL) {
- serializeCommonAttributes(F, Obj, RepositoryURL);
+ const std::optional<StringRef> RepositoryURL,
+ const std::optional<StringRef> RepositoryLine) {
+ serializeCommonAttributes(F, Obj, RepositoryURL, RepositoryLine);
Obj["IsStatic"] = F.IsStatic;
auto ReturnTypeObj = Object();
@@ -541,8 +554,9 @@ static void serializeInfo(const EnumValueInfo &I, Object &Obj) {
}
static void serializeInfo(const EnumInfo &I, json::Object &Obj,
- const std::optional<StringRef> &RepositoryUrl) {
- serializeCommonAttributes(I, Obj, RepositoryUrl);
+ const std::optional<StringRef> &RepositoryUrl,
+ const std::optional<StringRef> &RepositoryLine) {
+ serializeCommonAttributes(I, Obj, RepositoryUrl, RepositoryLine);
Obj["Scoped"] = I.Scoped;
if (I.BaseType) {
@@ -558,9 +572,11 @@ static void serializeInfo(const EnumInfo &I, json::Object &Obj,
serializeArray(I.Members, Obj, "Members", SerializeInfoLambda);
}
-static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
- const std::optional<StringRef> &RepositoryUrl) {
- serializeCommonAttributes(I, Obj, RepositoryUrl);
+static void
+serializeInfo(const TypedefInfo &I, json::Object &Obj,
+ const std::optional<StringRef> &RepositoryUrl,
+ const std::optional<StringRef> &RepositoryLinePrefix) {
+ serializeCommonAttributes(I, Obj, RepositoryUrl, RepositoryLinePrefix);
Obj["TypeDeclaration"] = I.TypeDeclaration;
Obj["IsUsing"] = I.IsUsing;
json::Value TypeVal = Object();
@@ -571,9 +587,12 @@ static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
serializeInfo(I.Template.value(), Obj);
}
-static void serializeInfo(const BaseRecordInfo &I, Object &Obj,
- const std::optional<StringRef> &RepositoryUrl) {
- serializeInfo(static_cast<const RecordInfo &>(I), Obj, RepositoryUrl);
+static void
+serializeInfo(const BaseRecordInfo &I, Object &Obj,
+ const std::optional<StringRef> &RepositoryUrl,
+ const std::optional<StringRef> &RepositoryLinePrefix) {
+ serializeInfo(static_cast<const RecordInfo &>(I), Obj, RepositoryUrl,
+ RepositoryLinePrefix);
Obj["IsVirtual"] = I.IsVirtual;
Obj["Access"] = getAccessSpelling(I.Access);
Obj["IsParent"] = I.IsParent;
@@ -593,7 +612,7 @@ static void serializeInfo(const FriendInfo &I, Object &Obj) {
serializeInfo(I.ReturnType.value(), ReturnTypeObj);
Obj["ReturnType"] = std::move(ReturnTypeObj);
}
- serializeCommonAttributes(I, Obj, std::nullopt);
+ serializeCommonAttributes(I, Obj, std::nullopt, std::nullopt);
}
static void insertArray(Object &Obj, json::Value &Array, StringRef Key) {
@@ -601,9 +620,11 @@ static void insertArray(Object &Obj, json::Value &Array, StringRef Key) {
Obj["Has" + Key.str()] = true;
}
-static void serializeInfo(const RecordInfo &I, json::Object &Obj,
- const std::optional<StringRef> &RepositoryUrl) {
- serializeCommonAttributes(I, Obj, RepositoryUrl);
+static void
+serializeInfo(const RecordInfo &I, json::Object &Obj,
+ const std::optional<StringRef> &RepositoryUrl,
+ const std::optional<StringRef> &RepositoryLinePrefix) {
+ serializeCommonAttributes(I, Obj, RepositoryUrl, RepositoryLinePrefix);
Obj["TagType"] = getTagType(I.TagType);
Obj["IsTypedef"] = I.IsTypeDef;
Obj["MangledName"] = I.MangledName;
@@ -617,7 +638,7 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
for (const auto &Function : I.Children.Functions) {
json::Value FunctionVal = Object();
auto &FunctionObj = *FunctionVal.getAsObject();
- serializeInfo(Function, FunctionObj, RepositoryUrl);
+ serializeInfo(Function, FunctionObj, RepositoryUrl, RepositoryLinePrefix);
AccessSpecifier Access = Function.Access;
if (Access == AccessSpecifier::AS_public)
PubFunctionsArrayRef.push_back(FunctionVal);
@@ -663,11 +684,12 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
}
if (!I.Bases.empty())
- serializeArray(
- I.Bases, Obj, "Bases",
- [&RepositoryUrl](const BaseRecordInfo &Base, Object &BaseObj) {
- serializeInfo(Base, BaseObj, RepositoryUrl);
- });
+ serializeArray(I.Bases, Obj, "Bases",
+ [&RepositoryUrl, &RepositoryLinePrefix](
+ const BaseRecordInfo &Base, Object &BaseObj) {
+ serializeInfo(Base, BaseObj, RepositoryUrl,
+ RepositoryLinePrefix);
+ });
if (!I.Parents.empty()) {
serializeArray(I.Parents, Obj, "Parents", SerializeReferenceLambda);
@@ -688,12 +710,14 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
Obj["HasFriends"] = true;
}
- serializeCommonChildren(I.Children, Obj, RepositoryUrl);
+ serializeCommonChildren(I.Children, Obj, RepositoryUrl, RepositoryLinePrefix);
}
-static void serializeInfo(const VarInfo &I, json::Object &Obj,
- const std::optional<StringRef> RepositoryUrl) {
- serializeCommonAttributes(I, Obj, RepositoryUrl);
+static void
+serializeInfo(const VarInfo &I, json::Object &Obj,
+ const std::optional<StringRef> RepositoryUrl,
+ const std::optional<StringRef> RepositoryUrlLinePrefix) {
+ serializeCommonAttributes(I, Obj, RepositoryUrl, RepositoryUrlLinePrefix);
Obj["IsStatic"] = I.IsStatic;
auto TypeObj = Object();
serializeInfo(I.Type, TypeObj);
@@ -701,8 +725,9 @@ static void serializeInfo(const VarInfo &I, json::Object &Obj,
}
static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
- const std::optional<StringRef> RepositoryUrl) {
- serializeCommonAttributes(I, Obj, RepositoryUrl);
+ const std::optional<StringRef> RepositoryUrl,
+ const std::optional<StringRef> RepositoryLinePrefix) {
+ serializeCommonAttributes(I, Obj, RepositoryUrl, RepositoryLinePrefix);
if (I.USR == GlobalNamespaceID)
Obj["Name"] = "Global Namespace";
@@ -712,10 +737,10 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
Obj["HasNamespaces"] = true;
}
- static auto SerializeInfo = [RepositoryUrl](const auto &Info,
- Object &Object) {
- serializeInfo(Info, Object, RepositoryUrl);
- };
+ static auto SerializeInfo =
+ [RepositoryUrl, RepositoryLinePrefix](const auto &Info, Object &Object) {
+ serializeInfo(Info, Object, RepositoryUrl, RepositoryLinePrefix);
+ };
if (!I.Children.Functions.empty()) {
serializeArray(I.Children.Functions, Obj, "Functions", SerializeInfo);
@@ -730,7 +755,7 @@ static void serializeInfo(const NamespaceInfo &I, json::Object &Obj,
if (!I.Children.Variables.empty())
serializeArray(I.Children.Variables, Obj, "Variables", SerializeInfo);
- serializeCommonChildren(I.Children, Obj, RepositoryUrl);
+ serializeCommonChildren(I.Children, Obj, RepositoryUrl, RepositoryLinePrefix);
}
static SmallString<16> determineFileName(Info *I, SmallString<128> &Path) {
@@ -864,10 +889,12 @@ Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
switch (I->IT) {
case InfoType::IT_namespace:
- serializeInfo(*static_cast<NamespaceInfo *>(I), Obj, CDCtx.RepositoryUrl);
+ serializeInfo(*static_cast<NamespaceInfo *>(I), Obj, CDCtx.RepositoryUrl,
+ CDCtx.RepositoryLinePrefix);
break;
case InfoType::IT_record:
- serializeInfo(*static_cast<RecordInfo *>(I), Obj, CDCtx.RepositoryUrl);
+ serializeInfo(*static_cast<RecordInfo *>(I), Obj, CDCtx.RepositoryUrl,
+ CDCtx.RepositoryLinePrefix);
break;
case InfoType::IT_concept:
case InfoType::IT_enum:
diff --git a/clang-tools-extra/clang-doc/assets/alias-template.mustache b/clang-tools-extra/clang-doc/assets/alias-template.mustache
index 9b32f132e9ef3..835d9aab1417e 100644
--- a/clang-tools-extra/clang-doc/assets/alias-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/alias-template.mustache
@@ -7,6 +7,6 @@
{{>Comments}}
{{/Description}}
{{#Location}}
- <p>Defined at line {{LineNumber}} of file {{Filename}}</p>
+ <p>Defined at line {{LineNumber}} of file {{^FileURL}}{{Filename}}{{/FileURL}}{{#FileURL}}<a href="{{FileURL}}">{{Filename}}</a>{{/FileURL}}</p>
{{/Location}}
</div>
diff --git a/clang-tools-extra/clang-doc/assets/class-template.mustache b/clang-tools-extra/clang-doc/assets/class-template.mustache
index ef5a7572a3f48..71ed1abd1016d 100644
--- a/clang-tools-extra/clang-doc/assets/class-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/class-template.mustache
@@ -153,7 +153,9 @@
{{/Template}}
<div class="hero__title">
<h1 class="hero__title-large">{{TagType}} {{Name}}</h1>
- <p>Defined at line {{Location.LineNumber}} of file {{Location.Filename}}</p>
+ {{#Location}}
+ <p>Defined at line {{LineNumber}} of file {{^FileURL}}{{Filename}}{{/FileURL}}{{#FileURL}}<a href="{{FileURL}}">{{Filename}}</a>{{/FileURL}}</p>
+ {{/Location}}
{{#Description}}
<div class="doc-card">
{{>Comments}}
diff --git a/clang-tools-extra/clang-doc/assets/enum-template.mustache b/clang-tools-extra/clang-doc/assets/enum-template.mustache
index af1364c4d37f6..22661c69f9c22 100644
--- a/clang-tools-extra/clang-doc/assets/enum-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/enum-template.mustache
@@ -42,8 +42,6 @@
</div>
{{/EnumComments}}
{{#Location}}
- <div>
- Defined at line {{LineNumber}} of file {{Filename}}
- </div>
+ <p>Defined at line {{LineNumber}} of file {{^FileURL}}{{Filename}}{{/FileURL}}{{#FileURL}}<a href="{{FileURL}}">{{Filename}}</a>{{/FileURL}}</p>
{{/Location}}
</div>
diff --git a/clang-tools-extra/clang-doc/assets/function-template.mustache b/clang-tools-extra/clang-doc/assets/function-template.mustache
index 354e26b7b05b0..510219a63d379 100644
--- a/clang-tools-extra/clang-doc/assets/function-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/function-template.mustache
@@ -18,6 +18,6 @@
</div>
{{/Description}}
{{#Location}}
- <p>Defined at line {{LineNumber}} of file {{Filename}}</p>
+ <p>Defined at line {{LineNumber}} of file {{^FileURL}}{{Filename}}{{/FileURL}}{{#FileURL}}<a href="{{FileURL}}">{{Filename}}</a>{{/FileURL}}</p>
{{/Location}}
</div>
diff --git a/clang-tools-extra/clang-doc/assets/namespace-template.mustache b/clang-tools-extra/clang-doc/assets/namespace-template.mustache
index 567af626714ef..fc0b9e2c388d9 100644
--- a/clang-tools-extra/clang-doc/assets/namespace-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/namespace-template.mustache
@@ -176,7 +176,9 @@
{{#Description}}
{{>Comments}}
{{/Description}}
- <p>Defined at line {{Location.LineNumber}} of file {{Location.Filename}}</p>
+ {{#Location}}
+ <p>Defined at line {{LineNumber}} of file {{^FileURL}}{{Filename}}{{/FileURL}}{{#FileURL}}<a href="{{FileURL}}">{{Filename}}</a>{{/FileURL}}</p>
+ {{/Location}}
</div>
{{/Concepts}}
</section>
diff --git a/clang-tools-extra/test/clang-doc/enum.cpp b/clang-tools-extra/test/clang-doc/enum.cpp
index ce844ec072564..f10d8808f185e 100644
--- a/clang-tools-extra/test/clang-doc/enum.cpp
+++ b/clang-tools-extra/test/clang-doc/enum.cpp
@@ -21,7 +21,7 @@
*/
enum Color {
// MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]*
- // HTML-INDEX-LINE-NOT: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
+ // HTML-INDEX-LINE: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
Red, ///< Comment 1
Green, ///< Comment 2
Blue ///< Comment 3
@@ -64,7 +64,7 @@ enum Color {
*/
enum class Shapes {
// MD-INDEX-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]*
- // HTML-INDEX-LINE-NOT: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
+ // HTML-INDEX-LINE: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
/// Comment 1
Circle,
@@ -115,7 +115,7 @@ class Animals {
*/
enum AnimalType {
// MD-ANIMAL-LINE: *Defined at {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp#[[@LINE-1]]*
- // HTML-ANIMAL-LINE-NOT: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
+ // HTML-ANIMAL-LINE: <p>Defined at line [[@LINE-2]] of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}enum.cpp</p>
Dog, ///< Man's best friend
Cat, ///< Man's other best friend
Iguana ///< A lizard
@@ -148,9 +148,7 @@ class Animals {
// HTML-ANIMAL-NEXT: </tr>
// HTML-ANIMAL-NEXT: </tbody>
// HTML-ANIMAL-NEXT: </table>
-// HTML-ANIMAL-NEXT: <div>
-// HTML-ANIMAL-NEXT: Defined at line 116 of file {{.*}}enum.cpp
-// HTML-ANIMAL-NEXT: </div>
+// HTML-ANIMAL-NEXT: <p>Defined at line 116 of file {{.*}}enum.cpp</p>
// HTML-ANIMAL-NEXT: </div>
// HTML-ANIMAL-NEXT: </section>
diff --git a/clang-tools-extra/test/clang-doc/mustache-index.cpp b/clang-tools-extra/test/clang-doc/mustache-index.cpp
index 9706307287f9b..caaa932d3e02f 100644
--- a/clang-tools-extra/test/clang-doc/mustache-index.cpp
+++ b/clang-tools-extra/test/clang-doc/mustache-index.cpp
@@ -54,9 +54,7 @@ class Foo;
// CHECK-NEXT: </tr>
// CHECK-NEXT: </tbody>
// CHECK-NEXT: </table>
-// CHECK-NEXT: <div>
-// CHECK-NEXT: Defined at line 5 of file {{.*}}mustache-index.cpp
-// CHECK-NEXT: </div>
+// CHECK-NEXT: <p>Defined at line 5 of file {{.*}}mustache-index.cpp</p>
// CHECK-NEXT: </div>
// CHECK-NEXT: </div>
// CHECK-NEXT: </section>
diff --git a/clang-tools-extra/test/clang-doc/templates.cpp b/clang-tools-extra/test/clang-doc/templates.cpp
index 27fff6ed5a09d..8c9b0b53a8b3d 100644
--- a/clang-tools-extra/test/clang-doc/templates.cpp
+++ b/clang-tools-extra/test/clang-doc/templates.cpp
@@ -78,8 +78,7 @@ void ParamPackFunction(T... args);
// JSON-NEXT: },
// HTML: <pre><code class="language-cpp code-clang-doc">template <class... T></code></pre>
-// HTML-NEXT: <pre><code class="language-cpp code-clang-doc">void ParamPackFunction (T... args)</code></pre>
-// HTML-NEXT: </div>
+// HTML-NEXT: <pre><code class="language-cpp code-clang-doc">void ParamPackFunction (T... args)</code></pre>
template <typename T, int U = 1>
void function(T x) {}
More information about the cfe-commits
mailing list