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..086da09165329 --- /dev/null +++ b/clang-tools-extra/clang-doc/JSONGenerator.cpp @@ -0,0 +1,508 @@ +#include "Generators.h" +#include "clang/Basic/Specifiers.h" +#include "llvm/Support/JSON.h" + +using namespace llvm; +using namespace llvm::json; + +namespace clang { +namespace doc { + +class JSONGenerator : public Generator { +public: + static const char *Format; + + Error generateDocs(StringRef RootDir, + llvm::StringMap> 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 void serializeInfo(const TypedefInfo &I, json::Object &Obj, + std::optional RepositoryUrl); +static void serializeInfo(const EnumInfo &I, json::Object &Obj, + std::optional RepositoryUrl); + +static json::Object serializeLocation(const Location &Loc, + std::optional 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 &I) { + // taken from PR #142273 + Object Obj = Object(); + + json::Value ChildVal = Object(); + Object &Child = *ChildVal.getAsObject(); + + json::Value ChildArr = Array(); + auto &CARef = *ChildArr.getAsArray(); + CARef.reserve(I.Children.size()); + for (const auto &C : I.Children) + CARef.emplace_back(serializeComment(*C)); + + switch (I.Kind) { + case CommentKind::CK_TextComment: { + Obj.insert({commentKindToString(I.Kind), I.Text}); + return Obj; + } + + case CommentKind::CK_BlockCommandComment: { + Child.insert({"Command", I.Name}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_InlineCommandComment: { + json::Value ArgsArr = Array(); + auto &ARef = *ArgsArr.getAsArray(); + ARef.reserve(I.Args.size()); + for (const auto &Arg : I.Args) + ARef.emplace_back(Arg); + Child.insert({"Command", I.Name}); + Child.insert({"Args", ArgsArr}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_ParamCommandComment: + case CommentKind::CK_TParamCommandComment: { + Child.insert({"ParamName", I.ParamName}); + Child.insert({"Direction", I.Direction}); + Child.insert({"Explicit", I.Explicit}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_VerbatimBlockComment: { + Child.insert({"Text", I.Text}); + if (!I.CloseName.empty()) + Child.insert({"CloseName", I.CloseName}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_VerbatimBlockLineComment: + case CommentKind::CK_VerbatimLineComment: { + Child.insert({"Text", I.Text}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_HTMLStartTagComment: { + json::Value AttrKeysArray = json::Array(); + json::Value AttrValuesArray = json::Array(); + auto &KeyArr = *AttrKeysArray.getAsArray(); + auto &ValArr = *AttrValuesArray.getAsArray(); + KeyArr.reserve(I.AttrKeys.size()); + ValArr.reserve(I.AttrValues.size()); + for (const auto &K : I.AttrKeys) + KeyArr.emplace_back(K); + for (const auto &V : I.AttrValues) + ValArr.emplace_back(V); + Child.insert({"Name", I.Name}); + Child.insert({"SelfClosing", I.SelfClosing}); + Child.insert({"AttrKeys", AttrKeysArray}); + Child.insert({"AttrValues", AttrValuesArray}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_HTMLEndTagComment: { + Child.insert({"Name", I.Name}); + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_FullComment: + case CommentKind::CK_ParagraphComment: { + Child.insert({"Children", ChildArr}); + Obj.insert({commentKindToString(I.Kind), ChildVal}); + return Obj; + } + + case CommentKind::CK_Unknown: { + Obj.insert({commentKindToString(I.Kind), I.Text}); + return Obj; + } + } + llvm_unreachable("Unknown comment kind encountered."); +} + +static void serializeCommonAttributes(const Info &I, json::Object &Obj, + std::optional RepositoryUrl) { + Obj["Name"] = I.Name; + Obj["USR"] = toHex(toStringRef(I.USR)); + + if (!I.Path.empty()) + 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); + } + + 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; + } + + // Namespaces aren't SymbolInfos, so they dont have a DefLoc + if (I.IT != InfoType::IT_namespace) { + const auto *Symbol = static_cast(&I); + if (Symbol->DefLoc) + Obj["Location"] = + serializeLocation(Symbol->DefLoc.value(), RepositoryUrl); + } +} + +static void serializeReference(const Reference &Ref, Object &ReferenceObj) { + ReferenceObj["Path"] = Ref.Path; + ReferenceObj["Name"] = Ref.Name; + ReferenceObj["QualName"] = Ref.QualName; + ReferenceObj["USR"] = toHex(toStringRef(Ref.USR)); +} + +static void serializeReference(const SmallVector &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(); + serializeReference(Reference, ReferenceObj); + ReferencesArrayRef.push_back(ReferenceVal); + } + Obj[Key] = 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, + json::Object &Obj, + std::optional RepositoryUrl) { + 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(); + serializeInfo(Enum, EnumObj, RepositoryUrl); + EnumsArrayRef.push_back(EnumVal); + } + Obj["Enums"] = EnumsArray; + } + + 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(); + serializeInfo(Typedef, TypedefObj, RepositoryUrl); + TypedefsArrayRef.push_back(TypedefVal); + } + Obj["Typedefs"] = TypedefsArray; + } + + 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(); + serializeReference(Record, RecordObj); + RecordsArrayRef.push_back(RecordVal); + } + 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(); + ParamsArrayRef.reserve(Template.Specialization->Params.size()); + 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(); + ParamsArrayRef.reserve(Template.Params.size()); + 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["USR"] = toHex(toStringRef(I.Type.USR)); + Obj["IsTemplate"] = I.IsTemplate; + Obj["IsBuiltIn"] = I.IsBuiltIn; +} + +static void serializeInfo(const FunctionInfo &F, json::Object &Obj, + std::optional RepositoryURL) { + serializeCommonAttributes(F, Obj, RepositoryURL); + Obj["IsStatic"] = F.IsStatic; + + auto ReturnTypeObj = Object(); + serializeInfo(F.ReturnType, ReturnTypeObj); + Obj["ReturnType"] = std::move(ReturnTypeObj); + + 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(); + ParamObj["Name"] = Param.Name; + ParamObj["Type"] = Param.Type.Name; + ParamsArrayRef.push_back(ParamVal); + } + Obj["Params"] = ParamsArray; + } + + if (F.Template) + serializeInfo(F.Template.value(), Obj); +} + +static void serializeInfo(const EnumInfo &I, json::Object &Obj, + std::optional RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + Obj["Scoped"] = I.Scoped; + + if (I.BaseType) { + json::Value BaseTypeVal = Object(); + auto &BaseTypeObj = *BaseTypeVal.getAsObject(); + BaseTypeObj["Name"] = I.BaseType->Type.Name; + BaseTypeObj["QualName"] = I.BaseType->Type.QualName; + BaseTypeObj["USR"] = toHex(toStringRef(I.BaseType->Type.USR)); + Obj["BaseType"] = BaseTypeVal; + } + + 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(); + MemberObj["Name"] = Member.Name; + if (!Member.ValueExpr.empty()) + MemberObj["ValueExpr"] = Member.ValueExpr; + else + MemberObj["Value"] = Member.Value; + MembersArrayRef.push_back(MemberVal); + } + Obj["Members"] = MembersArray; + } +} + +static void serializeInfo(const TypedefInfo &I, json::Object &Obj, + std::optional RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + Obj["TypeDeclaration"] = I.TypeDeclaration; + Obj["IsUsing"] = I.IsUsing; + json::Value TypeVal = Object(); + auto &TypeObj = *TypeVal.getAsObject(); + serializeInfo(I.Underlying, TypeObj); + Obj["Underlying"] = TypeVal; +} + +static void serializeInfo(const RecordInfo &I, json::Object &Obj, + std::optional RepositoryUrl) { + serializeCommonAttributes(I, Obj, RepositoryUrl); + Obj["FullName"] = I.FullName; + Obj["TagType"] = getTagType(I.TagType); + Obj["IsTypedef"] = I.IsTypeDef; + + if (!I.Children.Functions.empty()) { + 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::Value FunctionVal = Object(); + auto &FunctionObj = *FunctionVal.getAsObject(); + serializeInfo(Function, FunctionObj, RepositoryUrl); + AccessSpecifier Access = Function.Access; + if (Access == AccessSpecifier::AS_public) + PubFunctionsArrayRef.push_back(FunctionVal); + else if (Access == AccessSpecifier::AS_protected) + ProtFunctionsArrayRef.push_back(FunctionVal); + } + + if (!PubFunctionsArrayRef.empty()) + Obj["PublicFunctions"] = PubFunctionsArray; + if (!ProtFunctionsArrayRef.empty()) + Obj["ProtectedFunctions"] = ProtFunctionsArray; + } + + if (!I.Members.empty()) { + 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::Value MemberVal = Object(); + auto &MemberObj = *MemberVal.getAsObject(); + MemberObj["Name"] = Member.Name; + MemberObj["Type"] = Member.Type.Name; + + if (Member.Access == AccessSpecifier::AS_public) + PubMembersArrayRef.push_back(MemberVal); + else if (Member.Access == AccessSpecifier::AS_protected) + ProtMembersArrayRef.push_back(MemberVal); + } + + 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(); + BasesArrayRef.reserve(I.Bases.size()); + for (const auto &BaseInfo : I.Bases) { + 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(BaseInfoVal); + } + Obj["Bases"] = BasesArray; + } + + if (!I.Parents.empty()) + serializeReference(I.Parents, Obj, "Parents"); + + if (!I.VirtualParents.empty()) + serializeReference(I.VirtualParents, Obj, "VirtualParents"); + + if (I.Template) + serializeInfo(I.Template.value(), Obj); + + serializeCommonChildren(I.Children, Obj, RepositoryUrl); +} + +Error JSONGenerator::generateDocs( + StringRef RootDir, llvm::StringMap> Infos, + const ClangDocContext &CDCtx) { + StringSet<> CreatedDirs; + StringMap> 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()) + return 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) + return 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(I), Obj, CDCtx.RepositoryUrl); + break; + case InfoType::IT_enum: + case InfoType::IT_function: + case InfoType::IT_typedef: + break; + case InfoType::IT_default: + return 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 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 FormatEnum( "format", llvm::cl::desc("Format for outputted docs."), @@ -121,7 +121,9 @@ static llvm::cl::opt 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-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 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 new file mode 100644 index 0000000000000..9ae1f14e2d2a6 --- /dev/null +++ b/clang-tools-extra/test/clang-doc/json/class.cpp @@ -0,0 +1,193 @@ +// 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; + + class NestedClass; +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: ], +// COM: FIXME: FullName is not emitted correctly. +// CHECK-NEXT: "FullName": "", +// 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: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "int", +// CHECK-NEXT: "QualName": "int", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// 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: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "int", +// CHECK-NEXT: "QualName": "int", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK: "IsStatic": true, +// CHECK: "Name": "getConst", +// CHECK: "ReturnType": { +// CHECK-NEXT: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "const int &", +// CHECK-NEXT: "QualName": "const int &", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// CHECK-NEXT: }, +// CHECK: "PublicMembers": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "PublicField", +// CHECK-NEXT: "Type": "int" +// CHECK-NEXT: } +// CHECK-NEXT: ], +// CHECK-NEXT: "Records": [ +// CHECK-NEXT: { +// CHECK-NEXT: "Name": "NestedClass", +// CHECK-NEXT: "Path": "GlobalNamespace{{[\/]+}}MyClass", +// CHECK-NEXT: "QualName": "NestedClass", +// CHECK-NEXT: "USR": "{{[0-9A-F]*}}" +// 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: "IsBuiltIn": false, +// CHECK-NEXT: "IsTemplate": false, +// CHECK-NEXT: "Name": "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..c51a2706d1c22 --- /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 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]*}}" 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..24516cbc34a5a --- /dev/null +++ b/clang-tools-extra/unittests/clang-doc/JSONGeneratorTest.cpp @@ -0,0 +1,175 @@ +#include "ClangDocTest.h" +#include "Generators.h" +#include "Representation.h" +#include "gtest/gtest.h" + +namespace clang { +namespace doc { + +static std::unique_ptr getJSONGenerator() { + auto G = doc::findGeneratorByName("json"); + if (!G) + return nullptr; + return std::move(G.get()); +} + +TEST(JSONGeneratorTest, emitRecordJSON) { + RecordInfo I; + I.Name = "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; + 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": "", + "IsParent": true, + "IsTypedef": false, + "IsVirtual": true, + "Name": "F", + "Path": "path/to/F", + "PublicFunctions": [ + { + "IsStatic": false, + "Name": "InheritedFunctionOne", + "ReturnType": { + "IsBuiltIn": false, + "IsTemplate": false, + "Name": "", + "QualName": "", + "USR": "0000000000000000000000000000000000000000" + }, + "USR": "0000000000000000000000000000000000000000" + } + ], + "PublicMembers": [ + { + "Name": "N", + "Type": "int" + } + ], + "TagType": "struct", + "USR": "0000000000000000000000000000000000000000" + } + ], + "Enums": [ + { + "Members": [ + { + "Name": "RED", + "Value": "0" + } + ], + "Name": "Color", + "Scoped": false, + "USR": "0000000000000000000000000000000000000000" + } + ], + "FullName": "", + "IsTypedef": false, + "Location": { + "Filename": "main.cpp", + "LineNumber": 1 + }, + "Name": "Foo", + "Namespace": [ + "GlobalNamespace" + ], + "Parents": [ + { + "Name": "F", + "Path": "", + "QualName": "", + "USR": "0000000000000000000000000000000000000000" + } + ], + "Path": "GlobalNamespace", + "ProtectedMembers": [ + { + "Name": "X", + "Type": "int" + } + ], + "PublicFunctions": [ + { + "IsStatic": false, + "Name": "OneFunction", + "ReturnType": { + "IsBuiltIn": false, + "IsTemplate": false, + "Name": "", + "QualName": "", + "USR": "0000000000000000000000000000000000000000" + }, + "USR": "0000000000000000000000000000000000000000" + } + ], + "Records": [ + { + "Name": "ChildStruct", + "Path": "path/to/A/r", + "QualName": "path::to::A::r::ChildStruct", + "USR": "0000000000000000000000000000000000000000" + } + ], + "TagType": "class", + "Template": { + "Parameters": [ + "class T" + ] + }, + "USR": "0000000000000000000000000000000000000000", + "VirtualParents": [ + { + "Name": "G", + "Path": "path/to/G", + "QualName": "path::to::G::G", + "USR": "0000000000000000000000000000000000000000" + } + ] +})raw"; + EXPECT_EQ(Expected, Actual.str()); +} +} // namespace doc +} // namespace clang