Skip to content

[clang-doc] integrate JSON as the source for Mustache templates #149589

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 23, 2025

Conversation

evelez7
Copy link
Member

@evelez7 evelez7 commented Jul 18, 2025

This patch integrates JSON as the source to generate HTML Mustache templates. The Mustache generator calls the JSON generator and reads JSON files on the disk to produce HTML serially.

@llvmbot
Copy link
Member

llvmbot commented Jul 18, 2025

@llvm/pr-subscribers-clang-tools-extra

Author: Erick Velez (evelez7)

Changes

This patch integrates JSON as the source to generate HTML Mustache templates. The Mustache generator calls the JSON generator and reads JSON files on the disk to produce HTML serially.


Patch is 48.74 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/149589.diff

9 Files Affected:

  • (modified) clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp (+78-409)
  • (modified) clang-tools-extra/clang-doc/assets/class-template.mustache (+32-30)
  • (modified) clang-tools-extra/clang-doc/assets/enum-template.mustache (+9-3)
  • (modified) clang-tools-extra/clang-doc/assets/function-template.mustache (+1-1)
  • (modified) clang-tools-extra/clang-doc/assets/namespace-template.mustache (+25-20)
  • (modified) clang-tools-extra/test/clang-doc/basic-project.mustache.test (+13-57)
  • (modified) clang-tools-extra/test/clang-doc/mustache-index.cpp (+9-5)
  • (modified) clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp (+6-2)
  • (modified) clang-tools-extra/unittests/clang-doc/HTMLMustacheGeneratorTest.cpp (-129)
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
index 7aeaa1b7cf67d..98e2935a8aada 100644
--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp
@@ -27,6 +27,9 @@ using namespace llvm::mustache;
 
 namespace clang {
 namespace doc {
+static Error generateDocForJSON(json::Value &JSON, StringRef Filename,
+                                StringRef Path, raw_fd_ostream &OS,
+                                const ClangDocContext &CDCtx);
 
 static Error createFileOpenError(StringRef FileName, std::error_code EC) {
   return createFileError("cannot open file " + FileName, EC);
@@ -132,404 +135,65 @@ Error MustacheHTMLGenerator::generateDocs(
       return Err;
   }
 
-  // Track which directories we already tried to create.
-  StringSet<> CreatedDirs;
-  // Collect all output by file name and create the necessary directories.
-  StringMap<std::vector<doc::Info *>> FileToInfos;
-  for (const auto &Group : Infos) {
-    llvm::TimeTraceScope TS("setup directories");
-    doc::Info *Info = Group.getValue().get();
-
-    SmallString<128> Path;
-    sys::path::native(RootDir, Path);
-    sys::path::append(Path, Info->getRelativeFilePath(""));
-    if (!CreatedDirs.contains(Path)) {
-      if (std::error_code EC = sys::fs::create_directories(Path))
-        return createStringError(EC, "failed to create directory '%s'.",
-                                 Path.c_str());
-      CreatedDirs.insert(Path);
-    }
-
-    sys::path::append(Path, Info->getFileBaseName() + ".html");
-    FileToInfos[Path].push_back(Info);
+  {
+    llvm::TimeTraceScope TS("Generate JSON for Mustache");
+    if (auto JSONGenerator = findGeneratorByName("json")) {
+      if (Error Err = JSONGenerator.get()->generateDocs(
+              RootDir, std::move(Infos), CDCtx))
+        return Err;
+    } else
+      return JSONGenerator.takeError();
   }
 
+  StringMap<json::Value> JSONFileMap;
   {
-    llvm::TimeTraceScope TS("Generate Docs");
-    for (const auto &Group : FileToInfos) {
-      llvm::TimeTraceScope TS("Info to Doc");
+    llvm::TimeTraceScope TS("Iterate JSON files");
+    std::error_code EC;
+    sys::fs::directory_iterator JSONIter(RootDir, EC);
+    std::vector<json::Value> JSONFiles;
+    JSONFiles.reserve(Infos.size());
+    if (EC)
+      return createStringError("Failed to create directory iterator.");
+
+    while (JSONIter != sys::fs::directory_iterator()) {
+      if (EC)
+        return createStringError(EC, "Failed to iterate directory");
+
+      auto Path = StringRef(JSONIter->path());
+      if (!Path.ends_with(".json")) {
+        JSONIter.increment(EC);
+        continue;
+      }
+
+      auto File = MemoryBuffer::getFile(Path);
+      if ((EC = File.getError()))
+        continue;
+
+      auto Parsed = json::parse((*File)->getBuffer());
+      if (!Parsed)
+        return Parsed.takeError();
+
       std::error_code FileErr;
-      raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_None);
+      SmallString<16> HTMLPath(Path.begin(), Path.end());
+      sys::path::replace_extension(HTMLPath, "html");
+      raw_fd_ostream InfoOS(HTMLPath, FileErr, sys::fs::OF_None);
       if (FileErr)
-        return createFileOpenError(Group.getKey(), FileErr);
+        return createFileOpenError(Path, FileErr);
 
-      for (const auto &Info : Group.getValue())
-        if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
-          return Err;
+      if (Error Err = generateDocForJSON(*Parsed, sys::path::stem(HTMLPath),
+                                         HTMLPath, InfoOS, CDCtx))
+        return Err;
+      JSONIter.increment(EC);
     }
   }
-  return Error::success();
-}
-
-static json::Value
-extractValue(const Location &L,
-             std::optional<StringRef> RepositoryUrl = std::nullopt) {
-  Object Obj = Object();
-  // TODO: Consider using both Start/End line numbers to improve location report
-  Obj.insert({"LineNumber", L.StartLineNumber});
-  Obj.insert({"Filename", L.Filename});
-
-  if (!L.IsFileInRootDir || !RepositoryUrl)
-    return Obj;
-  SmallString<128> FileURL(*RepositoryUrl);
-  sys::path::append(FileURL, sys::path::Style::posix, L.Filename);
-  FileURL += "#" + std::to_string(L.StartLineNumber);
-  Obj.insert({"FileURL", FileURL});
-
-  return Obj;
-}
-
-static json::Value extractValue(const Reference &I,
-                                StringRef CurrentDirectory) {
-  SmallString<64> Path = I.getRelativeFilePath(CurrentDirectory);
-  sys::path::append(Path, I.getFileBaseName() + ".html");
-  sys::path::native(Path, sys::path::Style::posix);
-  Object Obj = Object();
-  Obj.insert({"Link", Path});
-  Obj.insert({"Name", I.Name});
-  Obj.insert({"QualName", I.QualName});
-  Obj.insert({"ID", toHex(toStringRef(I.USR))});
-  return Obj;
-}
-
-static json::Value extractValue(const TypedefInfo &I) {
-  // Not Supported
-  return nullptr;
-}
-
-static json::Value extractValue(const CommentInfo &I) {
-  Object Obj = Object();
-
-  json::Value ChildVal = Object();
-  Object &Child = *ChildVal.getAsObject();
-
-  json::Value ChildArr = Array();
-  auto &CARef = *ChildArr.getAsArray();
-  CARef.reserve(I.Children.size());
-  for (const auto &C : I.Children)
-    CARef.emplace_back(extractValue(*C));
-
-  switch (I.Kind) {
-  case CommentKind::CK_TextComment: {
-    Obj.insert({commentKindToString(I.Kind), I.Text});
-    return Obj;
-  }
-
-  case CommentKind::CK_BlockCommandComment: {
-    Child.insert({"Command", I.Name});
-    Child.insert({"Children", ChildArr});
-    Obj.insert({commentKindToString(I.Kind), ChildVal});
-    return Obj;
-  }
-
-  case CommentKind::CK_InlineCommandComment: {
-    json::Value ArgsArr = Array();
-    auto &ARef = *ArgsArr.getAsArray();
-    ARef.reserve(I.Args.size());
-    for (const auto &Arg : I.Args)
-      ARef.emplace_back(Arg);
-    Child.insert({"Command", I.Name});
-    Child.insert({"Args", ArgsArr});
-    Child.insert({"Children", ChildArr});
-    Obj.insert({commentKindToString(I.Kind), ChildVal});
-    return Obj;
-  }
-
-  case CommentKind::CK_ParamCommandComment:
-  case CommentKind::CK_TParamCommandComment: {
-    Child.insert({"ParamName", I.ParamName});
-    Child.insert({"Direction", I.Direction});
-    Child.insert({"Explicit", I.Explicit});
-    Child.insert({"Children", ChildArr});
-    Obj.insert({commentKindToString(I.Kind), ChildVal});
-    return Obj;
-  }
-
-  case CommentKind::CK_VerbatimBlockComment: {
-    Child.insert({"Text", I.Text});
-    if (!I.CloseName.empty())
-      Child.insert({"CloseName", I.CloseName});
-    Child.insert({"Children", ChildArr});
-    Obj.insert({commentKindToString(I.Kind), ChildVal});
-    return Obj;
-  }
-
-  case CommentKind::CK_VerbatimBlockLineComment:
-  case CommentKind::CK_VerbatimLineComment: {
-    Child.insert({"Text", I.Text});
-    Child.insert({"Children", ChildArr});
-    Obj.insert({commentKindToString(I.Kind), ChildVal});
-    return Obj;
-  }
-
-  case CommentKind::CK_HTMLStartTagComment: {
-    json::Value AttrKeysArray = json::Array();
-    json::Value AttrValuesArray = json::Array();
-    auto &KeyArr = *AttrKeysArray.getAsArray();
-    auto &ValArr = *AttrValuesArray.getAsArray();
-    KeyArr.reserve(I.AttrKeys.size());
-    ValArr.reserve(I.AttrValues.size());
-    for (const auto &K : I.AttrKeys)
-      KeyArr.emplace_back(K);
-    for (const auto &V : I.AttrValues)
-      ValArr.emplace_back(V);
-    Child.insert({"Name", I.Name});
-    Child.insert({"SelfClosing", I.SelfClosing});
-    Child.insert({"AttrKeys", AttrKeysArray});
-    Child.insert({"AttrValues", AttrValuesArray});
-    Child.insert({"Children", ChildArr});
-    Obj.insert({commentKindToString(I.Kind), ChildVal});
-    return Obj;
-  }
-
-  case CommentKind::CK_HTMLEndTagComment: {
-    Child.insert({"Name", I.Name});
-    Child.insert({"Children", ChildArr});
-    Obj.insert({commentKindToString(I.Kind), ChildVal});
-    return Obj;
-  }
-
-  case CommentKind::CK_FullComment:
-  case CommentKind::CK_ParagraphComment: {
-    Child.insert({"Children", ChildArr});
-    Obj.insert({commentKindToString(I.Kind), ChildVal});
-    return Obj;
-  }
-
-  case CommentKind::CK_Unknown: {
-    Obj.insert({commentKindToString(I.Kind), I.Text});
-    return Obj;
-  }
-  }
-  llvm_unreachable("Unknown comment kind encountered.");
-}
-
-static void maybeInsertLocation(std::optional<Location> Loc,
-                                const ClangDocContext &CDCtx, Object &Obj) {
-  if (!Loc)
-    return;
-  Location L = *Loc;
-  Obj.insert({"Location", extractValue(L, CDCtx.RepositoryUrl)});
-}
-
-static void extractDescriptionFromInfo(ArrayRef<CommentInfo> Descriptions,
-                                       json::Object &EnumValObj) {
-  if (Descriptions.empty())
-    return;
-  json::Value DescArr = Array();
-  json::Array &DescARef = *DescArr.getAsArray();
-  DescARef.reserve(Descriptions.size());
-  for (const CommentInfo &Child : Descriptions)
-    DescARef.emplace_back(extractValue(Child));
-  EnumValObj.insert({"EnumValueComments", DescArr});
-}
-
-static json::Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir,
-                                const ClangDocContext &CDCtx) {
-  Object Obj = Object();
-  Obj.insert({"Name", I.Name});
-  Obj.insert({"ID", toHex(toStringRef(I.USR))});
-  Obj.insert({"Access", getAccessSpelling(I.Access).str()});
-  Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)});
-
-  json::Value ParamArr = Array();
-  json::Array &ParamARef = *ParamArr.getAsArray();
-  ParamARef.reserve(I.Params.size());
-  for (const auto Val : enumerate(I.Params)) {
-    json::Value V = Object();
-    auto &VRef = *V.getAsObject();
-    VRef.insert({"Name", Val.value().Name});
-    VRef.insert({"Type", Val.value().Type.Name});
-    VRef.insert({"End", Val.index() + 1 == I.Params.size()});
-    ParamARef.emplace_back(V);
-  }
-  Obj.insert({"Params", ParamArr});
 
-  maybeInsertLocation(I.DefLoc, CDCtx, Obj);
-  return Obj;
-}
-
-static json::Value extractValue(const EnumInfo &I,
-                                const ClangDocContext &CDCtx) {
-  Object Obj = Object();
-  std::string EnumType = I.Scoped ? "enum class " : "enum ";
-  EnumType += I.Name;
-  bool HasComment = llvm::any_of(
-      I.Members, [](const EnumValueInfo &M) { return !M.Description.empty(); });
-  Obj.insert({"EnumName", EnumType});
-  Obj.insert({"HasComment", HasComment});
-  Obj.insert({"ID", toHex(toStringRef(I.USR))});
-  json::Value EnumArr = Array();
-  json::Array &EnumARef = *EnumArr.getAsArray();
-  EnumARef.reserve(I.Members.size());
-  for (const EnumValueInfo &M : I.Members) {
-    json::Value EnumValue = Object();
-    auto &EnumValObj = *EnumValue.getAsObject();
-    EnumValObj.insert({"Name", M.Name});
-    if (!M.ValueExpr.empty())
-      EnumValObj.insert({"ValueExpr", M.ValueExpr});
-    else
-      EnumValObj.insert({"Value", M.Value});
-
-    extractDescriptionFromInfo(M.Description, EnumValObj);
-    EnumARef.emplace_back(EnumValue);
-  }
-  Obj.insert({"EnumValues", EnumArr});
-
-  extractDescriptionFromInfo(I.Description, Obj);
-  maybeInsertLocation(I.DefLoc, CDCtx, Obj);
-
-  return Obj;
-}
-
-static void extractScopeChildren(const ScopeChildren &S, Object &Obj,
-                                 StringRef ParentInfoDir,
-                                 const ClangDocContext &CDCtx) {
-  json::Value NamespaceArr = Array();
-  json::Array &NamespaceARef = *NamespaceArr.getAsArray();
-  NamespaceARef.reserve(S.Namespaces.size());
-  for (const Reference &Child : S.Namespaces)
-    NamespaceARef.emplace_back(extractValue(Child, ParentInfoDir));
-
-  if (!NamespaceARef.empty())
-    Obj.insert({"Namespace", Object{{"Links", NamespaceArr}}});
-
-  json::Value RecordArr = Array();
-  json::Array &RecordARef = *RecordArr.getAsArray();
-  RecordARef.reserve(S.Records.size());
-  for (const Reference &Child : S.Records)
-    RecordARef.emplace_back(extractValue(Child, ParentInfoDir));
-
-  if (!RecordARef.empty())
-    Obj.insert({"Record", Object{{"Links", RecordArr}}});
-
-  json::Value FunctionArr = Array();
-  json::Array &FunctionARef = *FunctionArr.getAsArray();
-  FunctionARef.reserve(S.Functions.size());
-
-  json::Value PublicFunctionArr = Array();
-  json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray();
-  PublicFunctionARef.reserve(S.Functions.size());
-
-  json::Value ProtectedFunctionArr = Array();
-  json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray();
-  ProtectedFunctionARef.reserve(S.Functions.size());
-
-  for (const FunctionInfo &Child : S.Functions) {
-    json::Value F = extractValue(Child, ParentInfoDir, CDCtx);
-    AccessSpecifier Access = Child.Access;
-    if (Access == AccessSpecifier::AS_public)
-      PublicFunctionARef.emplace_back(F);
-    else if (Access == AccessSpecifier::AS_protected)
-      ProtectedFunctionARef.emplace_back(F);
-    else
-      FunctionARef.emplace_back(F);
-  }
-
-  if (!FunctionARef.empty())
-    Obj.insert({"Function", Object{{"Obj", FunctionArr}}});
-
-  if (!PublicFunctionARef.empty())
-    Obj.insert({"PublicFunction", Object{{"Obj", PublicFunctionArr}}});
-
-  if (!ProtectedFunctionARef.empty())
-    Obj.insert({"ProtectedFunction", Object{{"Obj", ProtectedFunctionArr}}});
-
-  json::Value EnumArr = Array();
-  auto &EnumARef = *EnumArr.getAsArray();
-  EnumARef.reserve(S.Enums.size());
-  for (const EnumInfo &Child : S.Enums)
-    EnumARef.emplace_back(extractValue(Child, CDCtx));
-
-  if (!EnumARef.empty())
-    Obj.insert({"Enums", Object{{"Obj", EnumArr}}});
-
-  json::Value TypedefArr = Array();
-  auto &TypedefARef = *TypedefArr.getAsArray();
-  TypedefARef.reserve(S.Typedefs.size());
-  for (const TypedefInfo &Child : S.Typedefs)
-    TypedefARef.emplace_back(extractValue(Child));
-
-  if (!TypedefARef.empty())
-    Obj.insert({"Typedefs", Object{{"Obj", TypedefArr}}});
-}
-
-static json::Value extractValue(const NamespaceInfo &I,
-                                const ClangDocContext &CDCtx) {
-  Object NamespaceValue = Object();
-  std::string InfoTitle = I.Name.empty() ? "Global Namespace"
-                                         : (Twine("namespace ") + I.Name).str();
-
-  SmallString<64> BasePath = I.getRelativeFilePath("");
-  NamespaceValue.insert({"NamespaceTitle", InfoTitle});
-  NamespaceValue.insert({"NamespacePath", BasePath});
-
-  extractDescriptionFromInfo(I.Description, NamespaceValue);
-  extractScopeChildren(I.Children, NamespaceValue, BasePath, CDCtx);
-  return NamespaceValue;
-}
-
-static json::Value extractValue(const RecordInfo &I,
-                                const ClangDocContext &CDCtx) {
-  Object RecordValue = Object();
-  extractDescriptionFromInfo(I.Description, RecordValue);
-  RecordValue.insert({"Name", I.Name});
-  RecordValue.insert({"FullName", I.FullName});
-  RecordValue.insert({"RecordType", getTagType(I.TagType)});
-
-  maybeInsertLocation(I.DefLoc, CDCtx, RecordValue);
-
-  SmallString<64> BasePath = I.getRelativeFilePath("");
-  extractScopeChildren(I.Children, RecordValue, BasePath, CDCtx);
-  json::Value PublicMembers = Array();
-  json::Array &PubMemberRef = *PublicMembers.getAsArray();
-  PubMemberRef.reserve(I.Members.size());
-  json::Value ProtectedMembers = Array();
-  json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();
-  ProtMemberRef.reserve(I.Members.size());
-  json::Value PrivateMembers = Array();
-  json::Array &PrivMemberRef = *PrivateMembers.getAsArray();
-  PrivMemberRef.reserve(I.Members.size());
-  for (const MemberTypeInfo &Member : I.Members) {
-    json::Value MemberValue = Object();
-    auto &MVRef = *MemberValue.getAsObject();
-    MVRef.insert({"Name", Member.Name});
-    MVRef.insert({"Type", Member.Type.Name});
-    extractDescriptionFromInfo(Member.Description, MVRef);
-
-    if (Member.Access == AccessSpecifier::AS_public)
-      PubMemberRef.emplace_back(MemberValue);
-    else if (Member.Access == AccessSpecifier::AS_protected)
-      ProtMemberRef.emplace_back(MemberValue);
-    else if (Member.Access == AccessSpecifier::AS_private)
-      ProtMemberRef.emplace_back(MemberValue);
-  }
-  if (!PubMemberRef.empty())
-    RecordValue.insert({"PublicMembers", Object{{"Obj", PublicMembers}}});
-  if (!ProtMemberRef.empty())
-    RecordValue.insert({"ProtectedMembers", Object{{"Obj", ProtectedMembers}}});
-  if (!PrivMemberRef.empty())
-    RecordValue.insert({"PrivateMembers", Object{{"Obj", PrivateMembers}}});
-
-  return RecordValue;
+  return Error::success();
 }
 
-static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V,
-                                Info *I) {
+static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V) {
   V.getAsObject()->insert({"ProjectName", CDCtx.ProjectName});
   json::Value StylesheetArr = Array();
-  auto InfoPath = I->getRelativeFilePath("");
-  SmallString<128> RelativePath = computeRelativePath("", InfoPath);
+  SmallString<128> RelativePath("./");
   sys::path::native(RelativePath, sys::path::Style::posix);
 
   auto *SSA = StylesheetArr.getAsArray();
@@ -555,38 +219,43 @@ static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V,
   return Error::success();
 }
 
-Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
-                                                const ClangDocContext &CDCtx) {
-  switch (I->IT) {
-  case InfoType::IT_namespace: {
-    json::Value V =
-        extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);
-    if (auto Err = setupTemplateValue(CDCtx, V, I))
+static Error generateDocForJSON(json::Value &JSON, StringRef Filename,
+                                StringRef Path, raw_fd_ostream &OS,
+                                const ClangDocContext &CDCtx) {
+  auto StrValue = (*JSON.getAsObject())["InfoType"];
+  if (StrValue.kind() != json::Value::Kind::String)
+    return createStringError(
+        "JSON file '%s' does not contain 'InfoType' field.",
+        Filename.str().c_str());
+  auto ObjTypeStr = StrValue.getAsString();
+  if (!ObjTypeStr.has_value())
+    return createStringError(
+        "JSON file '%s' does not contain 'InfoType' field as a string.",
+        Filename.str().c_str());
+
+  if (ObjTypeStr.value() == "namespace") {
+    if (auto Err = setupTemplateValue(CDCtx, JSON))
       return Err;
     assert(NamespaceTemplate && "NamespaceTemplate is nullptr.");
-    NamespaceTemplate->render(V, OS);
-    break;
-  }
-  case InfoType::IT_record: {
-    json::Value V =
-        extractValue(*static_cast<clang::doc::RecordInfo *>(I), CDCtx);
-    if (auto Err = setupTemplateValue(CDCtx, V, I))
+    NamespaceTemplate->render(JSON, OS);
+  } else if (ObjTypeStr.value() == "record") {
+    if (auto Err = setupTemplateValue(CDCtx, JSON))
       return Err;
-    // Serialize the JSON value to the output stream in a readable format.
-    RecordTemplate->render(V, OS);
-    break;
+    assert(RecordTemplate && "RecordTemplate is nullptr.");
+    RecordTemplate->render(JSON, OS);
   }
+  return Error::success();
+}
+
+Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
+                                                const ClangDocContext &CDCtx) {
+  switch (I->IT) {
   case InfoType::IT_enum:
-    OS << "IT_enum\n";
-    break;
   case InfoType::IT_function:
-    OS << "IT_Function\n";
-    break;
   case InfoType::IT_typedef:
-    OS << "IT_typedef\n";
-    break;
+  case InfoType::IT_namespace:
+  case InfoType::IT_record:
   case InfoType::IT_concept:
-    break;
   case InfoType::IT_variable:
   case InfoType::IT_friend:
     break;
diff --git a/clang-tools-extra/clang-doc/assets/class-template.mustache b/clang-tools-extra/clang-doc/assets/class-template.mustache
index f9e78f5cd6bc9..a4077323f29e2 100644
--- a/clang-tools-extra/clang-doc/assets/class-template.mustache
+++ b/clang-tools-extra/clang-doc/assets/class-template.mustache
@@ -44,20 +44,20 @@
 <main>
     <div class="container">
         <div class="sidebar">
-            <h2>{{RecordType}} {{Name}}</h2>
+            <h2>{{TagType}} {{Name}}</h2>
             <ul>
-                {{#PublicMembers}}
+                {{#HasPublicMembers}}
                 <li class="sidebar-section">
-                    <a class="sidebar-item" href="#PublicMethods">Public Members</a>
+                    <a class="sid...
[truncated]

@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-integrate-json-to-mustache branch from b36a2d4 to c9f121a Compare July 21, 2025 16:45
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-integrate-json-to-mustache branch from c9f121a to 2476174 Compare July 21, 2025 17:58
Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall seems like a nice set of changes, and lots of removed code is always nice to see. I left a couple Q's inline, but once those are addressed, this is probably good to land.

Comment on lines 169 to 170
if ((EC = File.getError()))
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if ((EC = File.getError()))
continue;
if (EC = File.getError(); EC)
continue;

But, independent of the code style, why is this continue? If EC is an error, then on the next iteration you'll report an error due to failing to iterate the directory, but this looks like a different error kind to me. What's the logic here?

Copy link
Member Author

@evelez7 evelez7 Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah, my original intent was to reuse the error reporting at the beginning of the loop but that doesn't work because of the message. The error can just return here.

Another thought reading this though, should the entire generation fail if one file fails? Would it better to report the file failing and then continue with the generation?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably. You could buffer the error reporting in a global vector and report at the end. There's probably a better way to issue diagnostics if we hooked into the diagnostics reporting mechanisms in clang or llvm. That's a bit involved though, so maybe just do something simple (like log a warning for now?) and file an issue upstream under clang-doc to incorporate proper diagnostics reporting via the diagnostics engine.

Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM once you're satisfied the diagnostics are in order.

@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-integrate-json-to-mustache branch from 2476174 to bc443f0 Compare July 22, 2025 17:01
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-refactor-json-for-mustache branch from 25f5252 to a2851dd Compare July 22, 2025 17:01
Base automatically changed from users/evelez7/clang-doc-refactor-json-for-mustache to main July 23, 2025 19:53
@evelez7 evelez7 force-pushed the users/evelez7/clang-doc-integrate-json-to-mustache branch from bc443f0 to 7ecaad5 Compare July 23, 2025 19:55
Copy link
Member Author

evelez7 commented Jul 23, 2025

Merge activity

  • Jul 23, 10:18 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 23, 10:20 PM UTC: @evelez7 merged this pull request with Graphite.

@evelez7 evelez7 merged commit 6d90715 into main Jul 23, 2025
9 checks passed
@evelez7 evelez7 deleted the users/evelez7/clang-doc-integrate-json-to-mustache branch July 23, 2025 22:20
mahesh-attarde pushed a commit to mahesh-attarde/llvm-project that referenced this pull request Jul 28, 2025
…#149589)

This patch integrates JSON as the source to generate HTML Mustache templates. The Mustache generator calls the JSON generator and reads JSON files on the disk to produce HTML serially.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants