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 <erickvel...@gmail.com> Date: Mon, 2 Jun 2025 12:53:36 -0700 Subject: [PATCH 1/2] [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 <erickvel...@gmail.com> Date: Tue, 3 Jun 2025 12:35:26 -0700 Subject: [PATCH 2/2] 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: { _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits