https://github.com/evelez7 created 
https://github.com/llvm/llvm-project/pull/142483

None

>From 497637bbc1c5b20fb60357fbbfda55514a8681c2 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvel...@gmail.com>
Date: Mon, 2 Jun 2025 12:53:36 -0700
Subject: [PATCH] [clang-doc] add a JSON generator

---
 clang-tools-extra/clang-doc/CMakeLists.txt    |   1 +
 clang-tools-extra/clang-doc/Generators.cpp    |   2 +
 clang-tools-extra/clang-doc/Generators.h      |   1 +
 clang-tools-extra/clang-doc/JSONGenerator.cpp | 258 ++++++++++++++++++
 .../clang-doc/tool/ClangDocMain.cpp           |   8 +-
 .../test/clang-doc/json/class.cpp             | 120 ++++++++
 6 files changed, 388 insertions(+), 2 deletions(-)
 create mode 100644 clang-tools-extra/clang-doc/JSONGenerator.cpp
 create mode 100644 clang-tools-extra/test/clang-doc/json/class.cpp

diff --git a/clang-tools-extra/clang-doc/CMakeLists.txt 
b/clang-tools-extra/clang-doc/CMakeLists.txt
index 79563c41435eb..5989e5fe60cf3 100644
--- a/clang-tools-extra/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/clang-doc/CMakeLists.txt
@@ -17,6 +17,7 @@ add_clang_library(clangDoc STATIC
   Serialize.cpp
   YAMLGenerator.cpp
   HTMLMustacheGenerator.cpp
+  JSONGenerator.cpp
 
   DEPENDS
   omp_gen
diff --git a/clang-tools-extra/clang-doc/Generators.cpp 
b/clang-tools-extra/clang-doc/Generators.cpp
index a3c2773412cff..3fb5b63c403a7 100644
--- a/clang-tools-extra/clang-doc/Generators.cpp
+++ b/clang-tools-extra/clang-doc/Generators.cpp
@@ -105,5 +105,7 @@ static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest =
     HTMLGeneratorAnchorSource;
 static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest =
     MHTMLGeneratorAnchorSource;
+static int LLVM_ATTRIBUTE_UNUSED JSONGeneratorAnchorDest =
+    JSONGeneratorAnchorSource;
 } // namespace doc
 } // namespace clang
diff --git a/clang-tools-extra/clang-doc/Generators.h 
b/clang-tools-extra/clang-doc/Generators.h
index aee04b9d58d9d..92d3006e6002d 100644
--- a/clang-tools-extra/clang-doc/Generators.h
+++ b/clang-tools-extra/clang-doc/Generators.h
@@ -58,6 +58,7 @@ extern volatile int YAMLGeneratorAnchorSource;
 extern volatile int MDGeneratorAnchorSource;
 extern volatile int HTMLGeneratorAnchorSource;
 extern volatile int MHTMLGeneratorAnchorSource;
+extern volatile int JSONGeneratorAnchorSource;
 
 } // namespace doc
 } // namespace clang
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp 
b/clang-tools-extra/clang-doc/JSONGenerator.cpp
new file mode 100644
index 0000000000000..70a56afd83659
--- /dev/null
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -0,0 +1,258 @@
+#include "Generators.h"
+#include "Representation.h"
+#include "clang/Basic/Specifiers.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/JSON.h"
+#include <optional>
+
+using namespace llvm;
+using namespace llvm::json;
+
+static llvm::ExitOnError ExitOnErr;
+
+namespace clang {
+namespace doc {
+
+class JSONGenerator : public Generator {
+public:
+  static const char *Format;
+
+  Error generateDocs(StringRef RootDir,
+                     llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
+                     const ClangDocContext &CDCtx) override;
+  Error createResources(ClangDocContext &CDCtx) override;
+  Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
+                           const ClangDocContext &CDCtx) override;
+};
+
+const char *JSONGenerator::Format = "json";
+
+static json::Object serializeLocation(const Location &Loc,
+                                      std::optional<StringRef> RepositoryUrl) {
+  Object LocationObj = Object();
+  LocationObj["LineNumber"] = Loc.StartLineNumber;
+  LocationObj["Filename"] = Loc.Filename;
+
+  if (!Loc.IsFileInRootDir || !RepositoryUrl)
+    return LocationObj;
+  SmallString<128> FileURL(*RepositoryUrl);
+  sys::path::append(FileURL, sys::path::Style::posix, Loc.Filename);
+  FileURL += "#" + std::to_string(Loc.StartLineNumber);
+  LocationObj["FileURL"] = FileURL;
+  return LocationObj;
+}
+
+static json::Value serializeComment(const CommentInfo &Comment) {
+  assert((Comment.Kind == "BlockCommandComment" ||
+          Comment.Kind == "FullComment" || Comment.Kind == "ParagraphComment" 
||
+          Comment.Kind == "TextComment") &&
+         "Unknown Comment type in CommentInfo.");
+
+  Object Obj = Object();
+  json::Value Child = Object();
+
+  // TextComment has no children, so return it.
+  if (Comment.Kind == "TextComment") {
+    Obj["TextComment"] = Comment.Text;
+    return Obj;
+  }
+
+  // BlockCommandComment needs to generate a Command key.
+  if (Comment.Kind == "BlockCommandComment")
+    Child.getAsObject()->insert({"Command", Comment.Name});
+
+  // Use the same handling for everything else.
+  // Only valid for:
+  //  - BlockCommandComment
+  //  - FullComment
+  //  - ParagraphComment
+  json::Value ChildArr = Array();
+  auto &CARef = *ChildArr.getAsArray();
+  CARef.reserve(Comment.Children.size());
+  for (const auto &C : Comment.Children)
+    CARef.emplace_back(serializeComment(*C));
+  Child.getAsObject()->insert({"Children", ChildArr});
+  Obj.insert({Comment.Kind, Child});
+  return Obj;
+}
+
+static void serializeCommonAttributes(const Info &I, json::Object &Obj,
+                                      std::optional<StringRef> RepositoryUrl) {
+  Obj["Name"] = I.Name.str();
+  Obj["USR"] = toHex(toStringRef(I.USR));
+
+  if (!I.Path.empty())
+    Obj["Path"] = I.Path.str();
+
+  if (!I.Namespace.empty()) {
+    Obj["Namespace"] = json::Array();
+    for (const auto &NS : I.Namespace)
+      Obj["Namespace"].getAsArray()->push_back(NS.Name.str());
+  }
+
+  if (!I.Description.empty()) {
+    json::Value DescArray = json::Array();
+    auto &DescArrayRef = *DescArray.getAsArray();
+    for (const auto &Comment : I.Description)
+      DescArrayRef.push_back(serializeComment(Comment));
+    Obj["Description"] = std::move(DescArray);
+  }
+
+  // Namespaces aren't SymbolInfos, so they dont have a DefLoc
+  if (I.IT != InfoType::IT_namespace) {
+    const auto *Symbol = static_cast<const SymbolInfo *>(&I);
+    if (Symbol->DefLoc.has_value())
+      Obj["Location"] =
+          serializeLocation(Symbol->DefLoc.value(), RepositoryUrl);
+  }
+}
+
+static void serializeInfo(const FunctionInfo &F, json::Object &Obj,
+                          std::optional<StringRef> RepositoryURL) {
+  serializeCommonAttributes(F, Obj, RepositoryURL);
+  Obj["IsStatic"] = F.IsStatic;
+
+  auto ReturnTypeObj = Object();
+  ReturnTypeObj["Name"] = F.ReturnType.Type.Name;
+  ReturnTypeObj["QualName"] = F.ReturnType.Type.QualName;
+  ReturnTypeObj["ID"] = toHex(toStringRef(F.ReturnType.Type.USR));
+  Obj["ReturnType"] = std::move(ReturnTypeObj);
+
+  if (!F.Params.empty()) {
+    json::Value ParamsArray = json::Array();
+    auto &ParamsArrayRef = *ParamsArray.getAsArray();
+    for (const auto &Param : F.Params) {
+      json::Object ParamObj;
+      ParamObj["Name"] = Param.Name;
+      ParamObj["Type"] = Param.Type.Name;
+      ParamsArrayRef.push_back(std::move(ParamObj));
+    }
+    Obj["Params"] = std::move(ParamsArray);
+  }
+
+  if (F.DefLoc.has_value())
+    Obj["Location"] = serializeLocation(F.DefLoc.value(), RepositoryURL);
+}
+
+static void serializeInfo(const RecordInfo &I, json::Object &Obj,
+                          std::optional<StringRef> RepositoryUrl) {
+  serializeCommonAttributes(I, Obj, RepositoryUrl);
+  Obj["FullName"] = I.Name.str();
+  Obj["TagType"] = getTagType(I.TagType);
+  Obj["IsTypedef"] = I.IsTypeDef;
+
+  if (!I.Children.Functions.empty()) {
+    json::Value PublicFunctionArr = Array();
+    json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray();
+    json::Value ProtectedFunctionArr = Array();
+    json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray();
+
+    for (const auto &Function : I.Children.Functions) {
+      json::Object FunctionObj;
+      serializeInfo(Function, FunctionObj, RepositoryUrl);
+      AccessSpecifier Access = Function.Access;
+      if (Access == AccessSpecifier::AS_public)
+        PublicFunctionARef.push_back(std::move(FunctionObj));
+      else if (Access == AccessSpecifier::AS_protected)
+        ProtectedFunctionARef.push_back(std::move(FunctionObj));
+    }
+
+    if (!PublicFunctionARef.empty())
+      Obj["PublicFunctions"] = std::move(PublicFunctionArr);
+    if (!ProtectedFunctionARef.empty())
+      Obj["ProtectedFunctions"] = std::move(ProtectedFunctionArr);
+  }
+
+  if (!I.Members.empty()) {
+    json::Value PublicMembers = Array();
+    json::Array &PubMemberRef = *PublicMembers.getAsArray();
+    json::Value ProtectedMembers = Array();
+    json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();
+
+    for (const MemberTypeInfo &Member : I.Members) {
+      json::Object MemberObj = Object();
+      MemberObj["Name"] = Member.Name;
+      MemberObj["Type"] = Member.Type.Name;
+
+      if (Member.Access == AccessSpecifier::AS_public)
+        PubMemberRef.push_back(std::move(MemberObj));
+      else if (Member.Access == AccessSpecifier::AS_protected)
+        ProtMemberRef.push_back(std::move(MemberObj));
+    }
+
+    if (!PubMemberRef.empty())
+      Obj["PublicMembers"] = std::move(PublicMembers);
+    if (!ProtMemberRef.empty())
+      Obj["ProtectedMembers"] = std::move(ProtectedMembers);
+  }
+}
+
+Error JSONGenerator::generateDocs(
+    StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
+    const ClangDocContext &CDCtx) {
+  StringSet<> CreatedDirs;
+  StringMap<std::vector<doc::Info *>> FileToInfos;
+  for (const auto &Group : Infos) {
+    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 Err = sys::fs::create_directories(Path);
+          Err != std::error_code())
+        ExitOnErr(createFileError(Twine(Path), Err));
+      CreatedDirs.insert(Path);
+    }
+
+    sys::path::append(Path, Info->getFileBaseName() + ".json");
+    FileToInfos[Path].push_back(Info);
+  }
+
+  for (const auto &Group : FileToInfos) {
+    std::error_code FileErr;
+    raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_Text);
+    if (FileErr)
+      ExitOnErr(createFileError("cannot open file " + Group.getKey(), 
FileErr));
+
+    for (const auto &Info : Group.getValue())
+      if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
+        return Err;
+  }
+
+  return Error::success();
+}
+
+Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
+                                        const ClangDocContext &CDCtx) {
+  json::Object Obj = Object();
+
+  switch (I->IT) {
+  case InfoType::IT_namespace:
+    break;
+  case InfoType::IT_record:
+    serializeInfo(*static_cast<RecordInfo *>(I), Obj, CDCtx.RepositoryUrl);
+    break;
+  case InfoType::IT_enum:
+    break;
+  case InfoType::IT_function:
+    break;
+  case InfoType::IT_typedef:
+    break;
+  case InfoType::IT_default:
+    ExitOnErr(
+        createStringError(inconvertibleErrorCode(), "unexpected info type"));
+  }
+  OS << llvm::formatv("{0:2}", llvm::json::Value(std::move(Obj)));
+  return Error::success();
+}
+
+Error JSONGenerator::createResources(ClangDocContext &CDCtx) {
+  return Error::success();
+}
+
+static GeneratorRegistry::Add<JSONGenerator> JSON(JSONGenerator::Format,
+                                                  "Generator for JSON 
output.");
+volatile int JSONGeneratorAnchorSource = 0;
+} // namespace doc
+} // namespace clang
diff --git a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp 
b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
index 8253ef298db4d..2d0cc4a32fc50 100644
--- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
+++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp
@@ -104,7 +104,7 @@ static llvm::cl::opt<std::string> RepositoryCodeLinePrefix(
     llvm::cl::desc("Prefix of line code for repository."),
     llvm::cl::cat(ClangDocCategory));
 
-enum OutputFormatTy { md, yaml, html, mustache };
+enum OutputFormatTy { md, yaml, html, mustache, json };
 
 static llvm::cl::opt<OutputFormatTy> FormatEnum(
     "format", llvm::cl::desc("Format for outputted docs."),
@@ -115,7 +115,9 @@ static llvm::cl::opt<OutputFormatTy> FormatEnum(
                      clEnumValN(OutputFormatTy::html, "html",
                                 "Documentation in HTML format."),
                      clEnumValN(OutputFormatTy::mustache, "mustache",
-                                "Documentation in mustache HTML format")),
+                                "Documentation in mustache HTML format"),
+                     clEnumValN(OutputFormatTy::json, "json",
+                                "Documentation in JSON format")),
     llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory));
 
 static llvm::ExitOnError ExitOnErr;
@@ -130,6 +132,8 @@ static std::string getFormatString() {
     return "html";
   case OutputFormatTy::mustache:
     return "mustache";
+  case OutputFormatTy::json:
+    return "json";
   }
   llvm_unreachable("Unknown OutputFormatTy");
 }
diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp 
b/clang-tools-extra/test/clang-doc/json/class.cpp
new file mode 100644
index 0000000000000..29eabb42d1105
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -0,0 +1,120 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: clang-doc --output=%t --format=json --executor=standalone %s
+// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+
+struct Foo;
+
+// This is a nice class.
+// It has some nice methods and fields.
+// @brief This is a brief description.
+struct MyClass {
+  int PublicField;
+
+  int myMethod(int MyParam);
+  static void staticMethod();
+  const int& getConst();
+protected:
+  int protectedMethod();
+
+  int ProtectedField;
+};
+
+// CHECK:       {
+// CHECK-NEXT:    "Description": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:        "FullComment": {
+// CHECK-NEXT:          "Children": [
+// CHECK-NEXT:            {
+// CHECK-NEXT:              "ParagraphComment": {
+// CHECK-NEXT:                "Children": [
+// CHECK-NEXT:                  {
+// CHECK-NEXT:                    "TextComment": " This is a nice class."
+// CHECK-NEXT:                  },
+// CHECK-NEXT:                  {
+// CHECK-NEXT:                    "TextComment": " It has some nice methods 
and fields."
+// CHECK-NEXT:                  },
+// CHECK-NEXT:                  {
+// CHECK-NEXT:                    "TextComment": ""
+// CHECK-NEXT:                  }
+// CHECK-NEXT:                ]
+// CHECK:                 {
+// CHECK-NEXT:              "BlockCommandComment": {
+// CHECK-NEXT:                "Children": [
+// CHECK-NEXT:                  {
+// CHECK-NEXT:                    "ParagraphComment": {
+// CHECK-NEXT:                      "Children": [
+// CHECK-NEXT:                        { 
+// CHECK-NEXT:                          "TextComment": " This is a brief 
description." 
+// CHECK-NEXT:                        }
+// CHECK:                     "Command": "brief"
+// CHECK:         "FullName": "MyClass",
+// CHECK-NEXT:    "IsTypedef": false,
+// CHECK-NEXT:    "Location": {
+// CHECK-NEXT:      "Filename": "{{.*}}class.cpp",
+// CHECK-NEXT:      "LineNumber": 10
+// CHECK-NEXT:    },
+// CHECK-NEXT:    "Name": "MyClass",
+// CHECK-NEXT:    "Namespace": [
+// CHECK-NEXT:      "GlobalNamespace"
+// CHECK-NEXT:    ],
+// CHECK-NEXT:    "Path": "GlobalNamespace",
+// CHECK-NEXT:    "ProtectedFunctions": [
+// CHECK-NEXT:      {
+// CHECK-NEXT:       "IsStatic": false,
+// CHECK-NEXT:       "Name": "protectedMethod",
+// CHECK-NEXT:       "Namespace": [
+// CHECK-NEXT:         "MyClass",
+// CHECK-NEXT:         "GlobalNamespace"
+// CHECK-NEXT:       ],
+// CHECK-NEXT:       "ReturnType": {
+// CHECK-NEXT:         "ID": "{{[0-9A-F]*}}",
+// CHECK-NEXT:         "Name": "int",
+// CHECK-NEXT:         "QualName": "int"
+// CHECK-NEXT:       },
+// CHECK-NEXT:       "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:     }
+// CHECK-NEXT:  ],
+// CHECK-NEXT:  "ProtectedMembers": [
+// CHECK-NEXT:    {
+// CHECK-NEXT:       "Name": "ProtectedField",
+// CHECK-NEXT:       "Type": "int"
+// CHECK-NEXT:    }
+// CHECK-NEXT:  ],
+// CHECK-NEXT:  "PublicFunctions": [
+// CHECK-NEXT:    {
+// CHECK-NEXT:      "IsStatic": false,
+// CHECK-NEXT:      "Name": "myMethod",
+// CHECK-NEXT:      "Namespace": [
+// CHECK-NEXT:        "MyClass",
+// CHECK-NEXT:        "GlobalNamespace"
+// CHECK-NEXT:      ],
+// CHECK-NEXT:      "Params": [
+// CHECK-NEXT:        {
+// CHECK-NEXT:          "Name": "MyParam",
+// CHECK-NEXT:          "Type": "int"
+// CHECK-NEXT:        }
+// CHECK-NEXT:      ],
+// CHECK-NEXT:     "ReturnType": {
+// CHECK-NEXT:       "ID": "{{[0-9A-F]*}}",
+// CHECK-NEXT:       "Name": "int",
+// CHECK-NEXT:       "QualName": "int"
+// CHECK-NEXT:     },
+// CHECK-NEXT:     "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT:    },
+// CHECK:           "IsStatic": true,
+// CHECk:           "IsStatic": false,
+// CHECK:          "Name": "getConst",
+// CHECK:          "ReturnType": {
+// CHECK-NEXT:       "ID": "{{[0-9A-F]*}}",
+// CHECK-NEXT:       "Name": "const int &",
+// CHECK-NEXT:       "QualName": "const int &"
+// CHECK-NEXT:     },
+// CHECK:      "PublicMembers": [
+// CHECK-NEXT:   {
+// CHECK-NEXT:     "Name": "PublicField",
+// CHECK-NEXT:     "Type": "int"
+// CHECK-NEXT:   }
+// CHECK-NEXT: ],
+// CHECK:       "TagType": "struct",
+// CHECK-NEXT:  "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT: }

_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to