https://github.com/evelez7 updated https://github.com/llvm/llvm-project/pull/142483
>From 806c2c1f1846879f0217398f81f36a8e49447bdf Mon Sep 17 00:00:00 2001 From: Erick Velez <erickvel...@gmail.com> Date: Mon, 2 Jun 2025 12:53:36 -0700 Subject: [PATCH 1/6] [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 15de031aa6091..3bb67baf65739 100644 --- a/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp +++ b/clang-tools-extra/clang-doc/tool/ClangDocMain.cpp @@ -110,7 +110,7 @@ Turn on time profiler. Generates clang-doc-tracing.json)"), llvm::cl::init(false), 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."), @@ -121,7 +121,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; @@ -136,6 +138,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 b90d2266827df4d4c76268ac4f5e717c865c4564 Mon Sep 17 00:00:00 2001 From: Erick Velez <erickvel...@gmail.com> Date: Tue, 3 Jun 2025 12:35:26 -0700 Subject: [PATCH 2/6] 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 cf3c9315d76a3af45fcd34fbfc83e3c92be6fee3 Mon Sep 17 00:00:00 2001 From: Erick Velez <erickvel...@gmail.com> Date: Thu, 5 Jun 2025 15:18:50 -0700 Subject: [PATCH 3/6] 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 0f109e9211a9b317e0afd5b8fc1d0d45909c6fb0 Mon Sep 17 00:00:00 2001 From: Erick Velez <erickvel...@gmail.com> Date: Fri, 6 Jun 2025 11:26:07 -0700 Subject: [PATCH 4/6] 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 36ec0b59315619cbe4fbe20d315b2d14e03ec000 Mon Sep 17 00:00:00 2001 From: Erick Velez <erickvel...@gmail.com> Date: Fri, 6 Jun 2025 13:45:27 -0700 Subject: [PATCH 5/6] 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]*}}" >From 8becb2375c9c661b956725ed50b49a660cae327f Mon Sep 17 00:00:00 2001 From: Erick Velez <erickvel...@gmail.com> Date: Fri, 6 Jun 2025 14:21:37 -0700 Subject: [PATCH 6/6] fix formatting and CHECK typo --- clang-tools-extra/clang-doc/JSONGenerator.cpp | 2 +- clang-tools-extra/test/clang-doc/json/class.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clang-tools-extra/clang-doc/JSONGenerator.cpp b/clang-tools-extra/clang-doc/JSONGenerator.cpp index 77303121154d6..0459d061cd138 100644 --- a/clang-tools-extra/clang-doc/JSONGenerator.cpp +++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp @@ -124,7 +124,7 @@ static void serializeReference(const SmallVector<Reference, 4> &References, json::Value ReferencesArray = Array(); json::Array &ReferencesArrayRef = *ReferencesArray.getAsArray(); ReferencesArrayRef.reserve(References.size()); - for (const auto& Reference : References) { + for (const auto &Reference : References) { json::Value ReferenceVal = Object(); auto &ReferenceObj = *ReferenceVal.getAsObject(); auto BasePath = Reference.getRelativeFilePath(""); diff --git a/clang-tools-extra/test/clang-doc/json/class.cpp b/clang-tools-extra/test/clang-doc/json/class.cpp index 8cb8b1e5d3c06..148378e2f7904 100644 --- a/clang-tools-extra/test/clang-doc/json/class.cpp +++ b/clang-tools-extra/test/clang-doc/json/class.cpp @@ -146,7 +146,7 @@ struct MyClass { // CHECK-NEXT: "USR": "{{[0-9A-F]*}}" // CHECK-NEXT: }, // CHECK: "IsStatic": true, -// CHECk: "IsStatic": false, +// CHECK: "IsStatic": false, // CHECK: "Name": "getConst", // CHECK: "ReturnType": { // CHECK-NEXT: "IsBuiltIn": false, _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits