[clang-doc] add a JSON generator (#142483)

Adds a JSON generator backend to emit mapped information as JSON. This will enable a better testing format for upcoming changes. It can also potentially serve to feed our other backend generators in the future, like Mustache which already serializes information to JSON before emitting as HTML.

This patch contains functionality to emit classes and provides most of the basis of the generator.
This commit is contained in:
Erick Velez 2025-06-10 08:39:42 -07:00 committed by GitHub
parent 610d0572c6
commit 1c3320cdde
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 956 additions and 2 deletions

View File

@ -17,6 +17,7 @@ add_clang_library(clangDoc STATIC
Serialize.cpp Serialize.cpp
YAMLGenerator.cpp YAMLGenerator.cpp
HTMLMustacheGenerator.cpp HTMLMustacheGenerator.cpp
JSONGenerator.cpp
DEPENDS DEPENDS
omp_gen omp_gen

View File

@ -105,5 +105,7 @@ static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest =
HTMLGeneratorAnchorSource; HTMLGeneratorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest = static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest =
MHTMLGeneratorAnchorSource; MHTMLGeneratorAnchorSource;
static int LLVM_ATTRIBUTE_UNUSED JSONGeneratorAnchorDest =
JSONGeneratorAnchorSource;
} // namespace doc } // namespace doc
} // namespace clang } // namespace clang

View File

@ -58,6 +58,7 @@ extern volatile int YAMLGeneratorAnchorSource;
extern volatile int MDGeneratorAnchorSource; extern volatile int MDGeneratorAnchorSource;
extern volatile int HTMLGeneratorAnchorSource; extern volatile int HTMLGeneratorAnchorSource;
extern volatile int MHTMLGeneratorAnchorSource; extern volatile int MHTMLGeneratorAnchorSource;
extern volatile int JSONGeneratorAnchorSource;
} // namespace doc } // namespace doc
} // namespace clang } // namespace clang

View File

@ -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<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 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();
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<StringRef> 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<const SymbolInfo *>(&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<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();
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<StringRef> 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<StringRef> 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<StringRef> 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<StringRef> 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<StringRef> 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<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())
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<RecordInfo *>(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<JSONGenerator> JSON(JSONGenerator::Format,
"Generator for JSON output.");
volatile int JSONGeneratorAnchorSource = 0;
} // namespace doc
} // namespace clang

View File

@ -110,7 +110,7 @@ Turn on time profiler. Generates clang-doc-tracing.json)"),
llvm::cl::init(false), llvm::cl::init(false),
llvm::cl::cat(ClangDocCategory)); llvm::cl::cat(ClangDocCategory));
enum OutputFormatTy { md, yaml, html, mustache }; enum OutputFormatTy { md, yaml, html, mustache, json };
static llvm::cl::opt<OutputFormatTy> FormatEnum( static llvm::cl::opt<OutputFormatTy> FormatEnum(
"format", llvm::cl::desc("Format for outputted docs."), "format", llvm::cl::desc("Format for outputted docs."),
@ -121,7 +121,9 @@ static llvm::cl::opt<OutputFormatTy> FormatEnum(
clEnumValN(OutputFormatTy::html, "html", clEnumValN(OutputFormatTy::html, "html",
"Documentation in HTML format."), "Documentation in HTML format."),
clEnumValN(OutputFormatTy::mustache, "mustache", 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)); llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory));
static llvm::ExitOnError ExitOnErr; static llvm::ExitOnError ExitOnErr;
@ -136,6 +138,8 @@ static std::string getFormatString() {
return "html"; return "html";
case OutputFormatTy::mustache: case OutputFormatTy::mustache:
return "mustache"; return "mustache";
case OutputFormatTy::json:
return "json";
} }
llvm_unreachable("Unknown OutputFormatTy"); llvm_unreachable("Unknown OutputFormatTy");
} }

View File

@ -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: ]

View File

@ -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: }

View File

@ -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]*}}"

View File

@ -31,6 +31,7 @@ add_extra_unittest(ClangDocTests
MergeTest.cpp MergeTest.cpp
SerializeTest.cpp SerializeTest.cpp
YAMLGeneratorTest.cpp YAMLGeneratorTest.cpp
JSONGeneratorTest.cpp
) )
clang_target_link_libraries(ClangDocTests clang_target_link_libraries(ClangDocTests

View File

@ -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<Generator> 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