[clang-tools-extra] [clang-doc] add a JSON generator (PR #142483)
Erick Velez via cfe-commits
cfe-commits at lists.llvm.org
Fri Jun 6 13:52:37 PDT 2025
https://github.com/evelez7 updated https://github.com/llvm/llvm-project/pull/142483
>From fa8b80f9bfe2b7faf765ed4cf60fb8cec30e1d48 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Mon, 2 Jun 2025 12:53:36 -0700
Subject: [PATCH 1/5] [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 | 316 ++++++++++++++++++
.../clang-doc/tool/ClangDocMain.cpp | 8 +-
.../test/clang-doc/json/class.cpp | 183 ++++++++++
6 files changed, 509 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..499ca4dd05e6e
--- /dev/null
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -0,0 +1,316 @@
+#include "Generators.h"
+#include "llvm/Support/JSON.h"
+
+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)
+ Obj["Location"] =
+ serializeLocation(Symbol->DefLoc.value(), RepositoryUrl);
+ }
+}
+
+static void serializeTypeInfo(const TypeInfo &I, Object &Obj) {
+ Obj["Name"] = I.Type.Name;
+ Obj["QualName"] = I.Type.QualName;
+ Obj["ID"] = toHex(toStringRef(I.Type.USR));
+ Obj["IsTemplate"] = I.IsTemplate;
+ Obj["IsBuiltIn"] = I.IsBuiltIn;
+}
+
+static void serializeInfo(const FunctionInfo &F, json::Object &Obj,
+ std::optional<StringRef> RepositoryURL) {
+ serializeCommonAttributes(F, Obj, RepositoryURL);
+ Obj["IsStatic"] = F.IsStatic;
+
+ auto ReturnTypeObj = Object();
+ serializeTypeInfo(F.ReturnType, ReturnTypeObj);
+ 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);
+ }
+}
+
+static void serializeInfo(const EnumInfo &I, json::Object &Obj,
+ std::optional<StringRef> RepositoryUrl) {
+ serializeCommonAttributes(I, Obj, RepositoryUrl);
+ Obj["Scoped"] = I.Scoped;
+
+ if (I.BaseType) {
+ json::Object BaseTypeObj;
+ BaseTypeObj["Name"] = I.BaseType->Type.Name;
+ BaseTypeObj["QualName"] = I.BaseType->Type.QualName;
+ BaseTypeObj["ID"] = toHex(toStringRef(I.BaseType->Type.USR));
+ Obj["BaseType"] = std::move(BaseTypeObj);
+ }
+
+ if (!I.Members.empty()) {
+ json::Value MembersArray = Array();
+ auto &MembersArrayRef = *MembersArray.getAsArray();
+ for (const auto &Member : I.Members) {
+ json::Object MemberObj;
+ MemberObj["Name"] = Member.Name;
+ if (!Member.ValueExpr.empty())
+ MemberObj["ValueExpr"] = Member.ValueExpr;
+ else
+ MemberObj["Value"] = Member.Value;
+ MembersArrayRef.push_back(std::move(MemberObj));
+ }
+ Obj["Members"] = std::move(MembersArray);
+ }
+}
+
+static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
+ std::optional<StringRef> RepositoryUrl) {
+ serializeCommonAttributes(I, Obj, RepositoryUrl);
+ Obj["TypeDeclaration"] = I.TypeDeclaration;
+ Obj["IsUsing"] = I.IsUsing;
+ Object TypeObj = Object();
+ serializeTypeInfo(I.Underlying, TypeObj);
+ Obj["Underlying"] = std::move(TypeObj);
+}
+
+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);
+ }
+
+ if (!I.Children.Enums.empty()) {
+ json::Value EnumsArray = Array();
+ auto &EnumsArrayRef = *EnumsArray.getAsArray();
+ for (const auto &Enum : I.Children.Enums) {
+ json::Object EnumObj;
+ serializeInfo(Enum, EnumObj, RepositoryUrl);
+ EnumsArrayRef.push_back(std::move(EnumObj));
+ }
+ Obj["Enums"] = std::move(EnumsArray);
+ }
+
+ if (!I.Children.Typedefs.empty()) {
+ json::Value TypedefsArray = Array();
+ auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
+ for (const auto &Typedef : I.Children.Typedefs) {
+ json::Object TypedefObj;
+ serializeInfo(Typedef, TypedefObj, RepositoryUrl);
+ TypedefsArrayRef.push_back(std::move(TypedefObj));
+ }
+ Obj["Typedefs"] = std::move(TypedefsArray);
+ }
+}
+
+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:
+ case InfoType::IT_function:
+ 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..3126fc689ffae
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -0,0 +1,183 @@
+// 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();
+
+ enum Color {
+ RED,
+ GREEN,
+ BLUE = 5
+ };
+
+ typedef int MyTypedef;
+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: "Enums": [
+// CHECK-NEXT: {
+// CHECK-NEXT: "Location": {
+// CHECK-NEXT: "Filename": "{{.*}}class.cpp",
+// CHECK-NEXT: "LineNumber": 17
+// CHECK-NEXT: },
+// CHECK-NEXT: "Members": [
+// CHECK-NEXT: {
+// CHECK-NEXT: "Name": "RED",
+// CHECK-NEXT: "Value": "0"
+// CHECK-NEXT: },
+// CHECK-NEXT: {
+// CHECK-NEXT: "Name": "GREEN",
+// CHECK-NEXT: "Value": "1"
+// CHECK-NEXT: },
+// CHECK-NEXT: {
+// CHECK-NEXT: "Name": "BLUE",
+// CHECK-NEXT: "ValueExpr": "5"
+// CHECK-NEXT: }
+// CHECK-NEXT: ],
+// CHECK-NEXT: "Name": "Color",
+// CHECK-NEXT: "Namespace": [
+// CHECK-NEXT: "MyClass",
+// CHECK-NEXT: "GlobalNamespace"
+// CHECK-NEXT: ],
+// CHECK-NEXT: "Scoped": false,
+// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT: }
+// CHECK-NEXT: ],
+// CHECK-NEXT: "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: "IsBuiltIn": false,
+// CHECK-NEXT: "IsTemplate": false,
+// 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: "IsBuiltIn": false,
+// CHECK-NEXT: "IsTemplate": false,
+// 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: "IsBuiltIn": false,
+// CHECK-NEXT: "IsTemplate": false,
+// 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-NEXT: "TagType": "struct",
+// CHECK-NEXT: "Typedefs": [
+// CHECK-NEXT: {
+// CHECK-NEXT: "IsUsing": false,
+// CHECK-NEXT: "Location": {
+// CHECK-NEXT: "Filename": "{{.*}}class.cpp",
+// CHECK-NEXT: "LineNumber": 23
+// CHECK-NEXT: },
+// CHECK-NEXT: "Name": "MyTypedef",
+// CHECK-NEXT: "Namespace": [
+// CHECK-NEXT: "MyClass",
+// CHECK-NEXT: "GlobalNamespace"
+// CHECK-NEXT: ],
+// CHECK-NEXT: "TypeDeclaration": "",
+// CHECK-NEXT: "USR": "{{[0-9A-F]*}}",
+// CHECK-NEXT: "Underlying": {
+// CHECK-NEXT: "ID": "0000000000000000000000000000000000000000",
+// CHECK-NEXT: "IsBuiltIn": false,
+// CHECK-NEXT: "IsTemplate": false,
+// CHECK-NEXT: "Name": "int",
+// CHECK-NEXT: "QualName": "int"
+// CHECK: "USR": "{{[0-9A-F]*}}"
+// CHECK-NEXT: }
>From f427d3aa9d43b37e6318677f96262aab73545d57 Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Tue, 3 Jun 2025 12:35:26 -0700
Subject: [PATCH 2/5] move common children serialization to a new function
---
clang-tools-extra/clang-doc/JSONGenerator.cpp | 84 +++++++++++++------
.../test/clang-doc/json/class.cpp | 10 +++
2 files changed, 70 insertions(+), 24 deletions(-)
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 499ca4dd05e6e..2c4cc18643e1c 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -23,6 +23,11 @@ class JSONGenerator : public Generator {
const char *JSONGenerator::Format = "json";
+static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
+ std::optional<StringRef> RepositoryUrl);
+static void serializeInfo(const EnumInfo &I, json::Object &Obj,
+ std::optional<StringRef> RepositoryUrl);
+
static json::Object serializeLocation(const Location &Loc,
std::optional<StringRef> RepositoryUrl) {
Object LocationObj = Object();
@@ -103,7 +108,58 @@ static void serializeCommonAttributes(const Info &I, json::Object &Obj,
}
}
-static void serializeTypeInfo(const TypeInfo &I, Object &Obj) {
+static void serializeReference(const Reference &Ref, Object &ReferenceObj,
+ SmallString<64> CurrentDirectory) {
+ SmallString<64> Path = Ref.getRelativeFilePath(CurrentDirectory);
+ sys::path::append(Path, Ref.getFileBaseName() + ".json");
+ sys::path::native(Path, sys::path::Style::posix);
+ ReferenceObj["Link"] = Path;
+ ReferenceObj["Name"] = Ref.Name;
+ ReferenceObj["QualName"] = Ref.QualName;
+ ReferenceObj["ID"] = toHex(toStringRef(Ref.USR));
+}
+
+// Although namespaces and records both have ScopeChildren, they serialize them
+// differently. Only enums, records, and typedefs are handled here.
+static void serializeCommonChildren(const ScopeChildren &Children,
+ json::Object &Obj,
+ std::optional<StringRef> RepositoryUrl) {
+ if (!Children.Enums.empty()) {
+ json::Value EnumsArray = Array();
+ auto &EnumsArrayRef = *EnumsArray.getAsArray();
+ for (const auto &Enum : Children.Enums) {
+ json::Object EnumObj;
+ serializeInfo(Enum, EnumObj, RepositoryUrl);
+ EnumsArrayRef.push_back(std::move(EnumObj));
+ }
+ Obj["Enums"] = std::move(EnumsArray);
+ }
+
+ if (!Children.Typedefs.empty()) {
+ json::Value TypedefsArray = Array();
+ auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
+ for (const auto &Typedef : Children.Typedefs) {
+ json::Object TypedefObj;
+ serializeInfo(Typedef, TypedefObj, RepositoryUrl);
+ TypedefsArrayRef.push_back(std::move(TypedefObj));
+ }
+ Obj["Typedefs"] = std::move(TypedefsArray);
+ }
+
+ if (!Children.Records.empty()) {
+ json::Value RecordsArray = Array();
+ auto &RecordsArrayRef = *RecordsArray.getAsArray();
+ for (const auto &Record : Children.Records) {
+ json::Object RecordObj;
+ SmallString<64> BasePath = Record.getRelativeFilePath("");
+ serializeReference(Record, RecordObj, BasePath);
+ RecordsArrayRef.push_back(std::move(RecordObj));
+ }
+ Obj["Records"] = std::move(RecordsArray);
+ }
+}
+
+static void serializeInfo(const TypeInfo &I, Object &Obj) {
Obj["Name"] = I.Type.Name;
Obj["QualName"] = I.Type.QualName;
Obj["ID"] = toHex(toStringRef(I.Type.USR));
@@ -117,7 +173,7 @@ static void serializeInfo(const FunctionInfo &F, json::Object &Obj,
Obj["IsStatic"] = F.IsStatic;
auto ReturnTypeObj = Object();
- serializeTypeInfo(F.ReturnType, ReturnTypeObj);
+ serializeInfo(F.ReturnType, ReturnTypeObj);
Obj["ReturnType"] = std::move(ReturnTypeObj);
if (!F.Params.empty()) {
@@ -168,7 +224,7 @@ static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
Obj["TypeDeclaration"] = I.TypeDeclaration;
Obj["IsUsing"] = I.IsUsing;
Object TypeObj = Object();
- serializeTypeInfo(I.Underlying, TypeObj);
+ serializeInfo(I.Underlying, TypeObj);
Obj["Underlying"] = std::move(TypeObj);
}
@@ -224,27 +280,7 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
Obj["ProtectedMembers"] = std::move(ProtectedMembers);
}
- if (!I.Children.Enums.empty()) {
- json::Value EnumsArray = Array();
- auto &EnumsArrayRef = *EnumsArray.getAsArray();
- for (const auto &Enum : I.Children.Enums) {
- json::Object EnumObj;
- serializeInfo(Enum, EnumObj, RepositoryUrl);
- EnumsArrayRef.push_back(std::move(EnumObj));
- }
- Obj["Enums"] = std::move(EnumsArray);
- }
-
- if (!I.Children.Typedefs.empty()) {
- json::Value TypedefsArray = Array();
- auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
- for (const auto &Typedef : I.Children.Typedefs) {
- json::Object TypedefObj;
- serializeInfo(Typedef, TypedefObj, RepositoryUrl);
- TypedefsArrayRef.push_back(std::move(TypedefObj));
- }
- Obj["Typedefs"] = std::move(TypedefsArray);
- }
+ serializeCommonChildren(I.Children, Obj, RepositoryUrl);
}
Error JSONGenerator::generateDocs(
diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp b/clang-tools-extra/test/clang-doc/json/class.cpp
index 3126fc689ffae..c50dcfc8f7369 100644
--- a/clang-tools-extra/test/clang-doc/json/class.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -21,6 +21,8 @@ struct MyClass {
};
typedef int MyTypedef;
+
+ class NestedClass;
protected:
int protectedMethod();
@@ -158,6 +160,14 @@ struct MyClass {
// CHECK-NEXT: "Type": "int"
// CHECK-NEXT: }
// CHECK-NEXT: ],
+// CHECK-NEXT: "Records": [
+// CHECK-NEXT: {
+// CHECK-NEXT: "ID": "{{[0-9A-F]*}}",
+// CHECK-NEXT: "Link": "NestedClass.json",
+// CHECK-NEXT: "Name": "NestedClass",
+// CHECK-NEXT: "QualName": "NestedClass"
+// CHECK-NEXT: }
+// CHECK-NEXT: ]
// CHECK-NEXT: "TagType": "struct",
// CHECK-NEXT: "Typedefs": [
// CHECK-NEXT: {
>From 7c8324bf6fd8d7f82f8598bb53d1ca9aa51d7acd Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Thu, 5 Jun 2025 15:18:50 -0700
Subject: [PATCH 3/5] return errors, add unit tests
---
clang-tools-extra/clang-doc/JSONGenerator.cpp | 42 ++++-
.../unittests/clang-doc/CMakeLists.txt | 1 +
.../unittests/clang-doc/JSONGeneratorTest.cpp | 166 ++++++++++++++++++
3 files changed, 203 insertions(+), 6 deletions(-)
create mode 100644 clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 2c4cc18643e1c..035277f6af6c5 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -1,11 +1,10 @@
#include "Generators.h"
+#include "clang/Basic/Specifiers.h"
#include "llvm/Support/JSON.h"
using namespace llvm;
using namespace llvm::json;
-static llvm::ExitOnError ExitOnErr;
-
namespace clang {
namespace doc {
@@ -119,6 +118,18 @@ static void serializeReference(const Reference &Ref, Object &ReferenceObj,
ReferenceObj["ID"] = toHex(toStringRef(Ref.USR));
}
+static void serializeReference(Object &Obj, SmallVector<Reference, 4> References, std::string Key) {
+ json::Value ReferencesArray = Array();
+ json::Array &ReferencesArrayRef = *ReferencesArray.getAsArray();
+ for (const auto& Reference : References) {
+ Object ReferenceObject = Object();
+ auto BasePath = Reference.getRelativeFilePath("");
+ serializeReference(Reference, ReferenceObject, BasePath);
+ ReferencesArrayRef.push_back(std::move(ReferenceObject));
+ }
+ Obj[Key] = std::move(ReferencesArray);
+}
+
// Although namespaces and records both have ScopeChildren, they serialize them
// differently. Only enums, records, and typedefs are handled here.
static void serializeCommonChildren(const ScopeChildren &Children,
@@ -280,6 +291,26 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
Obj["ProtectedMembers"] = std::move(ProtectedMembers);
}
+ if (!I.Bases.empty()) {
+ json::Value BasesArray = Array();
+ json::Array &BasesArrayRef = *BasesArray.getAsArray();
+ for (const auto &BaseInfo : I.Bases) {
+ Object BaseInfoObj = Object();
+ serializeInfo(BaseInfo, BaseInfoObj, RepositoryUrl);
+ BaseInfoObj["IsVirtual"] = BaseInfo.IsVirtual;
+ BaseInfoObj["Access"] = getAccessSpelling(BaseInfo.Access);
+ BaseInfoObj["IsParent"] = BaseInfo.IsParent;
+ BasesArrayRef.push_back(std::move(BaseInfoObj));
+ }
+ Obj["Bases"] = std::move(BasesArray);
+ }
+
+ if (!I.Parents.empty())
+ serializeReference(Obj, I.Parents, "Parents");
+
+ if (!I.VirtualParents.empty())
+ serializeReference(Obj, I.VirtualParents, "VirtualParents");
+
serializeCommonChildren(I.Children, Obj, RepositoryUrl);
}
@@ -297,7 +328,7 @@ Error JSONGenerator::generateDocs(
if (!CreatedDirs.contains(Path)) {
if (std::error_code Err = sys::fs::create_directories(Path);
Err != std::error_code())
- ExitOnErr(createFileError(Twine(Path), Err));
+ return createFileError(Twine(Path), Err);
CreatedDirs.insert(Path);
}
@@ -309,7 +340,7 @@ Error JSONGenerator::generateDocs(
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));
+ return createFileError("cannot open file " + Group.getKey(), FileErr);
for (const auto &Info : Group.getValue())
if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
@@ -334,8 +365,7 @@ Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
case InfoType::IT_typedef:
break;
case InfoType::IT_default:
- ExitOnErr(
- createStringError(inconvertibleErrorCode(), "unexpected info type"));
+ return createStringError(inconvertibleErrorCode(), "unexpected info type");
}
OS << llvm::formatv("{0:2}", llvm::json::Value(std::move(Obj)));
return Error::success();
diff --git a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
index 59a856ed987dc..18166acf9bbca 100644
--- a/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
+++ b/clang-tools-extra/unittests/clang-doc/CMakeLists.txt
@@ -31,6 +31,7 @@ add_extra_unittest(ClangDocTests
MergeTest.cpp
SerializeTest.cpp
YAMLGeneratorTest.cpp
+ JSONGeneratorTest.cpp
)
clang_target_link_libraries(ClangDocTests
diff --git a/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
new file mode 100644
index 0000000000000..f319a89f06b5c
--- /dev/null
+++ b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
@@ -0,0 +1,166 @@
+#include "ClangDocTest.h"
+#include "Generators.h"
+#include "Representation.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace doc {
+
+static std::unique_ptr<Generator> getJSONGenerator() {
+ auto G = doc::findGeneratorByName("json");
+ if (!G)
+ return nullptr;
+ return std::move(G.get());
+}
+
+TEST(JSONGeneratorTest, emitRecordJSON) {
+ RecordInfo I;
+ I.Name = "Foo";
+ I.FullName = "Foo";
+ I.IsTypeDef = false;
+ I.Namespace.emplace_back(EmptySID, "GlobalNamespace", InfoType::IT_namespace);
+ I.Path = "GlobalNamespace";
+ I.DefLoc = Location(1, 1, "main.cpp");
+ I.TagType = TagTypeKind::Class;
+
+ I.Children.Enums.emplace_back();
+ I.Children.Enums.back().Name = "Color";
+ I.Children.Enums.back().Scoped = false;
+ I.Children.Enums.back().Members.emplace_back();
+ I.Children.Enums.back().Members.back().Name = "RED";
+ I.Children.Enums.back().Members.back().Value = "0";
+
+ I.Members.emplace_back(TypeInfo("int"), "X", AccessSpecifier::AS_protected);
+
+ I.Bases.emplace_back(EmptySID, "F", "path/to/F", true,
+ AccessSpecifier::AS_public, true);
+ I.Bases.back().Children.Functions.emplace_back();
+ I.Bases.back().Children.Functions.back().Name = "InheritedFunctionOne";
+ I.Bases.back().Members.emplace_back(TypeInfo("int"), "N",
+ AccessSpecifier::AS_public);
+
+ // F is in the global namespace
+ I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, "");
+ I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record,
+ "path::to::G::G", "path/to/G");
+
+ I.Children.Records.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
+ "path::to::A::r::ChildStruct", "path/to/A/r");
+ I.Children.Functions.emplace_back();
+ I.Children.Functions.back().Name = "OneFunction";
+
+ auto G = getJSONGenerator();
+ assert(G);
+ std::string Buffer;
+ llvm::raw_string_ostream Actual(Buffer);
+ auto Err = G->generateDocForInfo(&I, Actual, ClangDocContext());
+ assert(!Err);
+ std::string Expected = R"raw({
+ "Bases": [
+ {
+ "Access": "public",
+ "FullName": "F",
+ "IsParent": true,
+ "IsTypedef": false,
+ "IsVirtual": true,
+ "Name": "F",
+ "Path": "path/to/F",
+ "PublicFunctions": [
+ {
+ "IsStatic": false,
+ "Name": "InheritedFunctionOne",
+ "ReturnType": {
+ "ID": "0000000000000000000000000000000000000000",
+ "IsBuiltIn": false,
+ "IsTemplate": false,
+ "Name": "",
+ "QualName": ""
+ },
+ "USR": "0000000000000000000000000000000000000000"
+ }
+ ],
+ "PublicMembers": [
+ {
+ "Name": "N",
+ "Type": "int"
+ }
+ ],
+ "TagType": "struct",
+ "USR": "0000000000000000000000000000000000000000"
+ }
+ ],
+ "Enums": [
+ {
+ "Members": [
+ {
+ "Name": "RED",
+ "Value": "0"
+ }
+ ],
+ "Name": "Color",
+ "Scoped": false,
+ "USR": "0000000000000000000000000000000000000000"
+ }
+ ],
+ "FullName": "Foo",
+ "IsTypedef": false,
+ "Location": {
+ "Filename": "main.cpp",
+ "LineNumber": 1
+ },
+ "Name": "Foo",
+ "Namespace": [
+ "GlobalNamespace"
+ ],
+ "Parents": [
+ {
+ "ID": "0000000000000000000000000000000000000000",
+ "Link": "F.json",
+ "Name": "F",
+ "QualName": ""
+ }
+ ],
+ "Path": "GlobalNamespace",
+ "ProtectedMembers": [
+ {
+ "Name": "X",
+ "Type": "int"
+ }
+ ],
+ "PublicFunctions": [
+ {
+ "IsStatic": false,
+ "Name": "OneFunction",
+ "ReturnType": {
+ "ID": "0000000000000000000000000000000000000000",
+ "IsBuiltIn": false,
+ "IsTemplate": false,
+ "Name": "",
+ "QualName": ""
+ },
+ "USR": "0000000000000000000000000000000000000000"
+ }
+ ],
+ "Records": [
+ {
+ "ID": "0000000000000000000000000000000000000000",
+ "Link": "ChildStruct.json",
+ "Name": "ChildStruct",
+ "QualName": "path::to::A::r::ChildStruct"
+ }
+ ],
+ "TagType": "class",
+ "USR": "0000000000000000000000000000000000000000",
+ "VirtualParents": [
+ {
+ "ID": "0000000000000000000000000000000000000000",
+ "Link": "G.json",
+ "Name": "G",
+ "QualName": "path::to::G::G"
+ }
+ ]
+})raw";
+ EXPECT_EQ(Expected, Actual.str());
+}
+} // namespace doc
+} // namespace clang
>From 9995fe0d8bae198b05a6d5f329de614c671ed8bf Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Fri, 6 Jun 2025 11:26:07 -0700
Subject: [PATCH 4/5] add templates, use JSON values
---
clang-tools-extra/clang-doc/JSONGenerator.cpp | 168 +++++++++++-------
.../test/clang-doc/json/class-template.cpp | 29 +++
.../test/clang-doc/json/class.cpp | 23 +--
.../test/clang-doc/json/method-template.cpp | 40 +++++
.../unittests/clang-doc/JSONGeneratorTest.cpp | 35 ++--
5 files changed, 211 insertions(+), 84 deletions(-)
create mode 100644 clang-tools-extra/test/clang-doc/json/class-template.cpp
create mode 100644 clang-tools-extra/test/clang-doc/json/method-template.cpp
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index 035277f6af6c5..d874505c783ee 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -78,16 +78,16 @@ static json::Value serializeComment(const CommentInfo &Comment) {
static void serializeCommonAttributes(const Info &I, json::Object &Obj,
std::optional<StringRef> RepositoryUrl) {
- Obj["Name"] = I.Name.str();
+ Obj["Name"] = I.Name;
Obj["USR"] = toHex(toStringRef(I.USR));
if (!I.Path.empty())
- Obj["Path"] = I.Path.str();
+ Obj["Path"] = I.Path;
if (!I.Namespace.empty()) {
Obj["Namespace"] = json::Array();
for (const auto &NS : I.Namespace)
- Obj["Namespace"].getAsArray()->push_back(NS.Name.str());
+ Obj["Namespace"].getAsArray()->push_back(NS.Name);
}
if (!I.Description.empty()) {
@@ -95,7 +95,7 @@ static void serializeCommonAttributes(const Info &I, json::Object &Obj,
auto &DescArrayRef = *DescArray.getAsArray();
for (const auto &Comment : I.Description)
DescArrayRef.push_back(serializeComment(Comment));
- Obj["Description"] = std::move(DescArray);
+ Obj["Description"] = DescArray;
}
// Namespaces aren't SymbolInfos, so they dont have a DefLoc
@@ -115,19 +115,21 @@ static void serializeReference(const Reference &Ref, Object &ReferenceObj,
ReferenceObj["Link"] = Path;
ReferenceObj["Name"] = Ref.Name;
ReferenceObj["QualName"] = Ref.QualName;
- ReferenceObj["ID"] = toHex(toStringRef(Ref.USR));
+ ReferenceObj["USR"] = toHex(toStringRef(Ref.USR));
}
-static void serializeReference(Object &Obj, SmallVector<Reference, 4> References, std::string Key) {
+static void serializeReference(const SmallVector<Reference, 4> &References,
+ Object &Obj, std::string Key) {
json::Value ReferencesArray = Array();
json::Array &ReferencesArrayRef = *ReferencesArray.getAsArray();
for (const auto& Reference : References) {
- Object ReferenceObject = Object();
+ json::Value ReferenceVal = Object();
+ auto &ReferenceObj = *ReferenceVal.getAsObject();
auto BasePath = Reference.getRelativeFilePath("");
- serializeReference(Reference, ReferenceObject, BasePath);
- ReferencesArrayRef.push_back(std::move(ReferenceObject));
- }
- Obj[Key] = std::move(ReferencesArray);
+ serializeReference(Reference, ReferenceObj, BasePath);
+ ReferencesArrayRef.push_back(ReferenceVal);
+ }
+ Obj[Key] = ReferencesArray;
}
// Although namespaces and records both have ScopeChildren, they serialize them
@@ -139,41 +141,74 @@ static void serializeCommonChildren(const ScopeChildren &Children,
json::Value EnumsArray = Array();
auto &EnumsArrayRef = *EnumsArray.getAsArray();
for (const auto &Enum : Children.Enums) {
- json::Object EnumObj;
+ json::Value EnumVal = Object();
+ auto &EnumObj = *EnumVal.getAsObject();
serializeInfo(Enum, EnumObj, RepositoryUrl);
- EnumsArrayRef.push_back(std::move(EnumObj));
+ EnumsArrayRef.push_back(EnumVal);
}
- Obj["Enums"] = std::move(EnumsArray);
+ Obj["Enums"] = EnumsArray;
}
if (!Children.Typedefs.empty()) {
json::Value TypedefsArray = Array();
auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
for (const auto &Typedef : Children.Typedefs) {
- json::Object TypedefObj;
+ json::Value TypedefVal = Object();
+ auto &TypedefObj = *TypedefVal.getAsObject();
serializeInfo(Typedef, TypedefObj, RepositoryUrl);
- TypedefsArrayRef.push_back(std::move(TypedefObj));
+ TypedefsArrayRef.push_back(TypedefVal);
}
- Obj["Typedefs"] = std::move(TypedefsArray);
+ Obj["Typedefs"] = TypedefsArray;
}
if (!Children.Records.empty()) {
json::Value RecordsArray = Array();
auto &RecordsArrayRef = *RecordsArray.getAsArray();
for (const auto &Record : Children.Records) {
- json::Object RecordObj;
+ json::Value RecordVal = Object();
+ auto &RecordObj = *RecordVal.getAsObject();
SmallString<64> BasePath = Record.getRelativeFilePath("");
serializeReference(Record, RecordObj, BasePath);
- RecordsArrayRef.push_back(std::move(RecordObj));
+ RecordsArrayRef.push_back(RecordVal);
}
- Obj["Records"] = std::move(RecordsArray);
+ Obj["Records"] = RecordsArray;
}
}
+static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
+ json::Value TemplateVal = Object();
+ auto &TemplateObj = *TemplateVal.getAsObject();
+
+ if (Template.Specialization) {
+ json::Value TemplateSpecializationVal = Object();
+ auto &TemplateSpecializationObj = *TemplateSpecializationVal.getAsObject();
+ TemplateSpecializationObj["SpecializationOf"] =
+ toHex(toStringRef(Template.Specialization->SpecializationOf));
+ if (!Template.Specialization->Params.empty()) {
+ json::Value ParamsArray = Array();
+ auto &ParamsArrayRef = *ParamsArray.getAsArray();
+ for (const auto &Param : Template.Specialization->Params)
+ ParamsArrayRef.push_back(Param.Contents);
+ TemplateSpecializationObj["Parameters"] = ParamsArray;
+ }
+ TemplateObj["Specialization"] = TemplateSpecializationVal;
+ }
+
+ if (!Template.Params.empty()) {
+ json::Value ParamsArray = Array();
+ auto &ParamsArrayRef = *ParamsArray.getAsArray();
+ for (const auto &Param : Template.Params)
+ ParamsArrayRef.push_back(Param.Contents);
+ TemplateObj["Parameters"] = ParamsArray;
+ }
+
+ Obj["Template"] = TemplateVal;
+}
+
static void serializeInfo(const TypeInfo &I, Object &Obj) {
Obj["Name"] = I.Type.Name;
Obj["QualName"] = I.Type.QualName;
- Obj["ID"] = toHex(toStringRef(I.Type.USR));
+ Obj["USR"] = toHex(toStringRef(I.Type.USR));
Obj["IsTemplate"] = I.IsTemplate;
Obj["IsBuiltIn"] = I.IsBuiltIn;
}
@@ -191,13 +226,17 @@ static void serializeInfo(const FunctionInfo &F, json::Object &Obj,
json::Value ParamsArray = json::Array();
auto &ParamsArrayRef = *ParamsArray.getAsArray();
for (const auto &Param : F.Params) {
- json::Object ParamObj;
+ json::Value ParamVal = Object();
+ auto &ParamObj = *ParamVal.getAsObject();
ParamObj["Name"] = Param.Name;
ParamObj["Type"] = Param.Type.Name;
- ParamsArrayRef.push_back(std::move(ParamObj));
+ ParamsArrayRef.push_back(ParamVal);
}
- Obj["Params"] = std::move(ParamsArray);
+ Obj["Params"] = ParamsArray;
}
+
+ if (F.Template)
+ serializeInfo(F.Template.value(), Obj);
}
static void serializeInfo(const EnumInfo &I, json::Object &Obj,
@@ -206,26 +245,28 @@ static void serializeInfo(const EnumInfo &I, json::Object &Obj,
Obj["Scoped"] = I.Scoped;
if (I.BaseType) {
- json::Object BaseTypeObj;
+ json::Value BaseTypeVal = Object();
+ auto &BaseTypeObj = *BaseTypeVal.getAsObject();
BaseTypeObj["Name"] = I.BaseType->Type.Name;
BaseTypeObj["QualName"] = I.BaseType->Type.QualName;
- BaseTypeObj["ID"] = toHex(toStringRef(I.BaseType->Type.USR));
- Obj["BaseType"] = std::move(BaseTypeObj);
+ BaseTypeObj["USR"] = toHex(toStringRef(I.BaseType->Type.USR));
+ Obj["BaseType"] = BaseTypeVal;
}
if (!I.Members.empty()) {
json::Value MembersArray = Array();
auto &MembersArrayRef = *MembersArray.getAsArray();
for (const auto &Member : I.Members) {
- json::Object MemberObj;
+ json::Value MemberVal = Object();
+ auto &MemberObj = *MemberVal.getAsObject();
MemberObj["Name"] = Member.Name;
if (!Member.ValueExpr.empty())
MemberObj["ValueExpr"] = Member.ValueExpr;
else
MemberObj["Value"] = Member.Value;
- MembersArrayRef.push_back(std::move(MemberObj));
+ MembersArrayRef.push_back(MemberVal);
}
- Obj["Members"] = std::move(MembersArray);
+ Obj["Members"] = MembersArray;
}
}
@@ -234,82 +275,89 @@ static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
serializeCommonAttributes(I, Obj, RepositoryUrl);
Obj["TypeDeclaration"] = I.TypeDeclaration;
Obj["IsUsing"] = I.IsUsing;
- Object TypeObj = Object();
+ json::Value TypeVal = Object();
+ auto &TypeObj = *TypeVal.getAsObject();
serializeInfo(I.Underlying, TypeObj);
- Obj["Underlying"] = std::move(TypeObj);
+ Obj["Underlying"] = TypeVal;
}
static void serializeInfo(const RecordInfo &I, json::Object &Obj,
std::optional<StringRef> RepositoryUrl) {
serializeCommonAttributes(I, Obj, RepositoryUrl);
- Obj["FullName"] = I.Name.str();
+ Obj["FullName"] = I.FullName;
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();
+ json::Value PubFunctionsArray = Array();
+ json::Array &PubFunctionsArrayRef = *PubFunctionsArray.getAsArray();
+ json::Value ProtFunctionsArray = Array();
+ json::Array &ProtFunctionsArrayRef = *ProtFunctionsArray.getAsArray();
for (const auto &Function : I.Children.Functions) {
- json::Object FunctionObj;
+ json::Value FunctionVal = Object();
+ auto &FunctionObj = *FunctionVal.getAsObject();
serializeInfo(Function, FunctionObj, RepositoryUrl);
AccessSpecifier Access = Function.Access;
if (Access == AccessSpecifier::AS_public)
- PublicFunctionARef.push_back(std::move(FunctionObj));
+ PubFunctionsArrayRef.push_back(FunctionVal);
else if (Access == AccessSpecifier::AS_protected)
- ProtectedFunctionARef.push_back(std::move(FunctionObj));
+ ProtFunctionsArrayRef.push_back(FunctionVal);
}
- if (!PublicFunctionARef.empty())
- Obj["PublicFunctions"] = std::move(PublicFunctionArr);
- if (!ProtectedFunctionARef.empty())
- Obj["ProtectedFunctions"] = std::move(ProtectedFunctionArr);
+ if (!PubFunctionsArrayRef.empty())
+ Obj["PublicFunctions"] = PubFunctionsArray;
+ if (!ProtFunctionsArrayRef.empty())
+ Obj["ProtectedFunctions"] = ProtFunctionsArray;
}
if (!I.Members.empty()) {
- json::Value PublicMembers = Array();
- json::Array &PubMemberRef = *PublicMembers.getAsArray();
- json::Value ProtectedMembers = Array();
- json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();
+ json::Value PublicMembersArray = Array();
+ json::Array &PubMembersArrayRef = *PublicMembersArray.getAsArray();
+ json::Value ProtectedMembersArray = Array();
+ json::Array &ProtMembersArrayRef = *ProtectedMembersArray.getAsArray();
for (const MemberTypeInfo &Member : I.Members) {
- json::Object MemberObj = Object();
+ json::Value MemberVal = Object();
+ auto &MemberObj = *MemberVal.getAsObject();
MemberObj["Name"] = Member.Name;
MemberObj["Type"] = Member.Type.Name;
if (Member.Access == AccessSpecifier::AS_public)
- PubMemberRef.push_back(std::move(MemberObj));
+ PubMembersArrayRef.push_back(MemberVal);
else if (Member.Access == AccessSpecifier::AS_protected)
- ProtMemberRef.push_back(std::move(MemberObj));
+ ProtMembersArrayRef.push_back(MemberVal);
}
- if (!PubMemberRef.empty())
- Obj["PublicMembers"] = std::move(PublicMembers);
- if (!ProtMemberRef.empty())
- Obj["ProtectedMembers"] = std::move(ProtectedMembers);
+ if (!PubMembersArrayRef.empty())
+ Obj["PublicMembers"] = PublicMembersArray;
+ if (!ProtMembersArrayRef.empty())
+ Obj["ProtectedMembers"] = ProtectedMembersArray;
}
if (!I.Bases.empty()) {
json::Value BasesArray = Array();
json::Array &BasesArrayRef = *BasesArray.getAsArray();
for (const auto &BaseInfo : I.Bases) {
- Object BaseInfoObj = Object();
+ json::Value BaseInfoVal = Object();
+ auto &BaseInfoObj = *BaseInfoVal.getAsObject();
serializeInfo(BaseInfo, BaseInfoObj, RepositoryUrl);
BaseInfoObj["IsVirtual"] = BaseInfo.IsVirtual;
BaseInfoObj["Access"] = getAccessSpelling(BaseInfo.Access);
BaseInfoObj["IsParent"] = BaseInfo.IsParent;
- BasesArrayRef.push_back(std::move(BaseInfoObj));
+ BasesArrayRef.push_back(BaseInfoVal);
}
- Obj["Bases"] = std::move(BasesArray);
+ Obj["Bases"] = BasesArray;
}
if (!I.Parents.empty())
- serializeReference(Obj, I.Parents, "Parents");
+ serializeReference(I.Parents, Obj, "Parents");
if (!I.VirtualParents.empty())
- serializeReference(Obj, I.VirtualParents, "VirtualParents");
+ serializeReference(I.VirtualParents, Obj, "VirtualParents");
+
+ if (I.Template)
+ serializeInfo(I.Template.value(), Obj);
serializeCommonChildren(I.Children, Obj, RepositoryUrl);
}
diff --git a/clang-tools-extra/test/clang-doc/json/class-template.cpp b/clang-tools-extra/test/clang-doc/json/class-template.cpp
new file mode 100644
index 0000000000000..e3ca086d1d9a4
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/class-template.cpp
@@ -0,0 +1,29 @@
+// RUN: rm -rf %t && mkdir -p %t
+// RUN: clang-doc --output=%t --format=json --executor=standalone %s
+// RUN: FileCheck %s < %t/GlobalNamespace/MyClass.json
+
+template<typename T> struct MyClass {
+ T MemberTemplate;
+ T method(T Param);
+};
+
+// CHECK: "Name": "MyClass",
+// CHECK: "Name": "method",
+// CHECK: "Params": [
+// CHECK-NEXT: {
+// CHECK-NEXT: "Name": "Param",
+// CHECK-NEXT: "Type": "T"
+// CHECK-NEXT: }
+// CHECK-NEXT: ],
+// CHECK-NEXT: "ReturnType": {
+// CHECK-NEXT: "IsBuiltIn": false,
+// CHECK-NEXT: "IsTemplate": false,
+// CHECK-NEXT: "Name": "T",
+// CHECK-NEXT: "QualName": "T"
+// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
+// CHECK: "Name": "MemberTemplate",
+// CHECK: "Type": "T"
+// CHECK: "Template": {
+// CHECK-NEXT: "Parameters": [
+// CHECK-NEXT: "typename T"
+// CHECK-NEXT: ]
diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp b/clang-tools-extra/test/clang-doc/json/class.cpp
index c50dcfc8f7369..8cb8b1e5d3c06 100644
--- a/clang-tools-extra/test/clang-doc/json/class.cpp
+++ b/clang-tools-extra/test/clang-doc/json/class.cpp
@@ -86,7 +86,8 @@ struct MyClass {
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ],
-// CHECK-NEXT: "FullName": "MyClass",
+// COM: FIXME: FullName is not emitted correctly.
+// CHECK-NEXT: "FullName": "",
// CHECK-NEXT: "IsTypedef": false,
// CHECK-NEXT: "Location": {
// CHECK-NEXT: "Filename": "{{.*}}class.cpp",
@@ -106,11 +107,11 @@ struct MyClass {
// CHECK-NEXT: "GlobalNamespace"
// CHECK-NEXT: ],
// CHECK-NEXT: "ReturnType": {
-// CHECK-NEXT: "ID": "{{[0-9A-F]*}}",
// CHECK-NEXT: "IsBuiltIn": false,
// CHECK-NEXT: "IsTemplate": false,
// CHECK-NEXT: "Name": "int",
-// CHECK-NEXT: "QualName": "int"
+// CHECK-NEXT: "QualName": "int",
+// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
@@ -136,11 +137,11 @@ struct MyClass {
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "ReturnType": {
-// CHECK-NEXT: "ID": "{{[0-9A-F]*}}",
// CHECK-NEXT: "IsBuiltIn": false,
// CHECK-NEXT: "IsTemplate": false,
// CHECK-NEXT: "Name": "int",
-// CHECK-NEXT: "QualName": "int"
+// CHECK-NEXT: "QualName": "int",
+// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
@@ -148,11 +149,11 @@ struct MyClass {
// CHECk: "IsStatic": false,
// CHECK: "Name": "getConst",
// CHECK: "ReturnType": {
-// CHECK-NEXT: "ID": "{{[0-9A-F]*}}",
// CHECK-NEXT: "IsBuiltIn": false,
// CHECK-NEXT: "IsTemplate": false,
// CHECK-NEXT: "Name": "const int &",
-// CHECK-NEXT: "QualName": "const int &"
+// CHECK-NEXT: "QualName": "const int &",
+// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: },
// CHECK: "PublicMembers": [
// CHECK-NEXT: {
@@ -162,10 +163,10 @@ struct MyClass {
// CHECK-NEXT: ],
// CHECK-NEXT: "Records": [
// CHECK-NEXT: {
-// CHECK-NEXT: "ID": "{{[0-9A-F]*}}",
// CHECK-NEXT: "Link": "NestedClass.json",
// CHECK-NEXT: "Name": "NestedClass",
-// CHECK-NEXT: "QualName": "NestedClass"
+// CHECK-NEXT: "QualName": "NestedClass",
+// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
// CHECK-NEXT: ]
// CHECK-NEXT: "TagType": "struct",
@@ -184,10 +185,10 @@ struct MyClass {
// CHECK-NEXT: "TypeDeclaration": "",
// CHECK-NEXT: "USR": "{{[0-9A-F]*}}",
// CHECK-NEXT: "Underlying": {
-// CHECK-NEXT: "ID": "0000000000000000000000000000000000000000",
// CHECK-NEXT: "IsBuiltIn": false,
// CHECK-NEXT: "IsTemplate": false,
// CHECK-NEXT: "Name": "int",
-// CHECK-NEXT: "QualName": "int"
+// CHECK-NEXT: "QualName": "int",
+// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
// CHECK: "USR": "{{[0-9A-F]*}}"
// CHECK-NEXT: }
diff --git a/clang-tools-extra/test/clang-doc/json/method-template.cpp b/clang-tools-extra/test/clang-doc/json/method-template.cpp
new file mode 100644
index 0000000000000..4c10275dd4293
--- /dev/null
+++ b/clang-tools-extra/test/clang-doc/json/method-template.cpp
@@ -0,0 +1,40 @@
+// 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 MyClass {
+ template<class T> T methodTemplate(T param) {
+ }
+};
+
+// CHECK: "PublicFunctions": [
+// CHECK-NEXT: {
+// CHECK-NEXT: "IsStatic": false,
+// CHECK-NEXT: "Location": {
+// CHECK-NEXT: "Filename": "{{.*}}method-template.cpp",
+// CHECK-NEXT: "LineNumber": 6
+// CHECK-NEXT: },
+// CHECK-NEXT: "Name": "methodTemplate",
+// CHECK-NEXT: "Namespace": [
+// CHECK-NEXT: "MyClass",
+// CHECK-NEXT: "GlobalNamespace"
+// CHECK-NEXT: ],
+// CHECK-NEXT: "Params": [
+// CHECK-NEXT: {
+// CHECK-NEXT: "Name": "param",
+// CHECK-NEXT: "Type": "T"
+// CHECK-NEXT: }
+// CHECK-NEXT: ],
+// CHECK-NEXT: "ReturnType": {
+// CHECK-NEXT: "IsBuiltIn": false,
+// CHECK-NEXT: "IsTemplate": false,
+// CHECK-NEXT: "Name": "T",
+// CHECK-NEXT: "QualName": "T",
+// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
+// CHECK-NEXT: },
+// CHECK-NEXT: "Template": {
+// CHECK-NEXT: "Parameters": [
+// CHECK-NEXT: "class T"
+// CHECK-NEXT: ]
+// CHECK-NEXT: },
+// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
\ No newline at end of file
diff --git a/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
index f319a89f06b5c..8e56c6fe3bae8 100644
--- a/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
+++ b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp
@@ -16,13 +16,17 @@ static std::unique_ptr<Generator> getJSONGenerator() {
TEST(JSONGeneratorTest, emitRecordJSON) {
RecordInfo I;
I.Name = "Foo";
- I.FullName = "Foo";
+ // FIXME: FullName is not emitted correctly.
+ I.FullName = "";
I.IsTypeDef = false;
I.Namespace.emplace_back(EmptySID, "GlobalNamespace", InfoType::IT_namespace);
I.Path = "GlobalNamespace";
I.DefLoc = Location(1, 1, "main.cpp");
I.TagType = TagTypeKind::Class;
+ I.Template = TemplateInfo();
+ I.Template->Params.emplace_back("class T");
+
I.Children.Enums.emplace_back();
I.Children.Enums.back().Name = "Color";
I.Children.Enums.back().Scoped = false;
@@ -59,7 +63,7 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
"Bases": [
{
"Access": "public",
- "FullName": "F",
+ "FullName": "",
"IsParent": true,
"IsTypedef": false,
"IsVirtual": true,
@@ -70,11 +74,11 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
"IsStatic": false,
"Name": "InheritedFunctionOne",
"ReturnType": {
- "ID": "0000000000000000000000000000000000000000",
"IsBuiltIn": false,
"IsTemplate": false,
"Name": "",
- "QualName": ""
+ "QualName": "",
+ "USR": "0000000000000000000000000000000000000000"
},
"USR": "0000000000000000000000000000000000000000"
}
@@ -102,7 +106,7 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
"USR": "0000000000000000000000000000000000000000"
}
],
- "FullName": "Foo",
+ "FullName": "",
"IsTypedef": false,
"Location": {
"Filename": "main.cpp",
@@ -114,10 +118,10 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
],
"Parents": [
{
- "ID": "0000000000000000000000000000000000000000",
"Link": "F.json",
"Name": "F",
- "QualName": ""
+ "QualName": "",
+ "USR": "0000000000000000000000000000000000000000"
}
],
"Path": "GlobalNamespace",
@@ -132,31 +136,36 @@ TEST(JSONGeneratorTest, emitRecordJSON) {
"IsStatic": false,
"Name": "OneFunction",
"ReturnType": {
- "ID": "0000000000000000000000000000000000000000",
"IsBuiltIn": false,
"IsTemplate": false,
"Name": "",
- "QualName": ""
+ "QualName": "",
+ "USR": "0000000000000000000000000000000000000000"
},
"USR": "0000000000000000000000000000000000000000"
}
],
"Records": [
{
- "ID": "0000000000000000000000000000000000000000",
"Link": "ChildStruct.json",
"Name": "ChildStruct",
- "QualName": "path::to::A::r::ChildStruct"
+ "QualName": "path::to::A::r::ChildStruct",
+ "USR": "0000000000000000000000000000000000000000"
}
],
"TagType": "class",
+ "Template": {
+ "Parameters": [
+ "class T"
+ ]
+ },
"USR": "0000000000000000000000000000000000000000",
"VirtualParents": [
{
- "ID": "0000000000000000000000000000000000000000",
"Link": "G.json",
"Name": "G",
- "QualName": "path::to::G::G"
+ "QualName": "path::to::G::G",
+ "USR": "0000000000000000000000000000000000000000"
}
]
})raw";
>From 2626305d92023db84ccf5039d085e2955e3c767d Mon Sep 17 00:00:00 2001
From: Erick Velez <erickvelez7 at gmail.com>
Date: Fri, 6 Jun 2025 13:45:27 -0700
Subject: [PATCH 5/5] address review comments
---
clang-tools-extra/clang-doc/JSONGenerator.cpp | 10 ++++++++++
.../test/clang-doc/json/method-template.cpp | 2 +-
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp
index d874505c783ee..77303121154d6 100644
--- a/clang-tools-extra/clang-doc/JSONGenerator.cpp
+++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp
@@ -93,6 +93,7 @@ static void serializeCommonAttributes(const Info &I, json::Object &Obj,
if (!I.Description.empty()) {
json::Value DescArray = json::Array();
auto &DescArrayRef = *DescArray.getAsArray();
+ DescArrayRef.reserve(I.Description.size());
for (const auto &Comment : I.Description)
DescArrayRef.push_back(serializeComment(Comment));
Obj["Description"] = DescArray;
@@ -122,6 +123,7 @@ static void serializeReference(const SmallVector<Reference, 4> &References,
Object &Obj, std::string Key) {
json::Value ReferencesArray = Array();
json::Array &ReferencesArrayRef = *ReferencesArray.getAsArray();
+ ReferencesArrayRef.reserve(References.size());
for (const auto& Reference : References) {
json::Value ReferenceVal = Object();
auto &ReferenceObj = *ReferenceVal.getAsObject();
@@ -140,6 +142,7 @@ static void serializeCommonChildren(const ScopeChildren &Children,
if (!Children.Enums.empty()) {
json::Value EnumsArray = Array();
auto &EnumsArrayRef = *EnumsArray.getAsArray();
+ EnumsArrayRef.reserve(Children.Enums.size());
for (const auto &Enum : Children.Enums) {
json::Value EnumVal = Object();
auto &EnumObj = *EnumVal.getAsObject();
@@ -152,6 +155,7 @@ static void serializeCommonChildren(const ScopeChildren &Children,
if (!Children.Typedefs.empty()) {
json::Value TypedefsArray = Array();
auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
+ TypedefsArrayRef.reserve(Children.Typedefs.size());
for (const auto &Typedef : Children.Typedefs) {
json::Value TypedefVal = Object();
auto &TypedefObj = *TypedefVal.getAsObject();
@@ -164,6 +168,7 @@ static void serializeCommonChildren(const ScopeChildren &Children,
if (!Children.Records.empty()) {
json::Value RecordsArray = Array();
auto &RecordsArrayRef = *RecordsArray.getAsArray();
+ RecordsArrayRef.reserve(Children.Records.size());
for (const auto &Record : Children.Records) {
json::Value RecordVal = Object();
auto &RecordObj = *RecordVal.getAsObject();
@@ -187,6 +192,7 @@ static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
if (!Template.Specialization->Params.empty()) {
json::Value ParamsArray = Array();
auto &ParamsArrayRef = *ParamsArray.getAsArray();
+ ParamsArrayRef.reserve(Template.Specialization->Params.size());
for (const auto &Param : Template.Specialization->Params)
ParamsArrayRef.push_back(Param.Contents);
TemplateSpecializationObj["Parameters"] = ParamsArray;
@@ -197,6 +203,7 @@ static void serializeInfo(const TemplateInfo &Template, Object &Obj) {
if (!Template.Params.empty()) {
json::Value ParamsArray = Array();
auto &ParamsArrayRef = *ParamsArray.getAsArray();
+ ParamsArrayRef.reserve(Template.Params.size());
for (const auto &Param : Template.Params)
ParamsArrayRef.push_back(Param.Contents);
TemplateObj["Parameters"] = ParamsArray;
@@ -225,6 +232,7 @@ static void serializeInfo(const FunctionInfo &F, json::Object &Obj,
if (!F.Params.empty()) {
json::Value ParamsArray = json::Array();
auto &ParamsArrayRef = *ParamsArray.getAsArray();
+ ParamsArrayRef.reserve(F.Params.size());
for (const auto &Param : F.Params) {
json::Value ParamVal = Object();
auto &ParamObj = *ParamVal.getAsObject();
@@ -256,6 +264,7 @@ static void serializeInfo(const EnumInfo &I, json::Object &Obj,
if (!I.Members.empty()) {
json::Value MembersArray = Array();
auto &MembersArrayRef = *MembersArray.getAsArray();
+ MembersArrayRef.reserve(I.Members.size());
for (const auto &Member : I.Members) {
json::Value MemberVal = Object();
auto &MemberObj = *MemberVal.getAsObject();
@@ -338,6 +347,7 @@ static void serializeInfo(const RecordInfo &I, json::Object &Obj,
if (!I.Bases.empty()) {
json::Value BasesArray = Array();
json::Array &BasesArrayRef = *BasesArray.getAsArray();
+ BasesArrayRef.reserve(I.Bases.size());
for (const auto &BaseInfo : I.Bases) {
json::Value BaseInfoVal = Object();
auto &BaseInfoObj = *BaseInfoVal.getAsObject();
diff --git a/clang-tools-extra/test/clang-doc/json/method-template.cpp b/clang-tools-extra/test/clang-doc/json/method-template.cpp
index 4c10275dd4293..c51a2706d1c22 100644
--- a/clang-tools-extra/test/clang-doc/json/method-template.cpp
+++ b/clang-tools-extra/test/clang-doc/json/method-template.cpp
@@ -37,4 +37,4 @@ struct MyClass {
// CHECK-NEXT: "class T"
// CHECK-NEXT: ]
// CHECK-NEXT: },
-// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
\ No newline at end of file
+// CHECK-NEXT: "USR": "{{[0-9A-F]*}}"
More information about the cfe-commits
mailing list