Skip to content

Commit cf68952

Browse files
committed
[clang-doc] add a JSON generator
1 parent 18e5131 commit cf68952

File tree

6 files changed

+509
-2
lines changed

6 files changed

+509
-2
lines changed

clang-tools-extra/clang-doc/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ add_clang_library(clangDoc STATIC
1717
Serialize.cpp
1818
YAMLGenerator.cpp
1919
HTMLMustacheGenerator.cpp
20+
JSONGenerator.cpp
2021

2122
DEPENDS
2223
omp_gen

clang-tools-extra/clang-doc/Generators.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,5 +105,7 @@ static int LLVM_ATTRIBUTE_UNUSED HTMLGeneratorAnchorDest =
105105
HTMLGeneratorAnchorSource;
106106
static int LLVM_ATTRIBUTE_UNUSED MHTMLGeneratorAnchorDest =
107107
MHTMLGeneratorAnchorSource;
108+
static int LLVM_ATTRIBUTE_UNUSED JSONGeneratorAnchorDest =
109+
JSONGeneratorAnchorSource;
108110
} // namespace doc
109111
} // namespace clang

clang-tools-extra/clang-doc/Generators.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ extern volatile int YAMLGeneratorAnchorSource;
5858
extern volatile int MDGeneratorAnchorSource;
5959
extern volatile int HTMLGeneratorAnchorSource;
6060
extern volatile int MHTMLGeneratorAnchorSource;
61+
extern volatile int JSONGeneratorAnchorSource;
6162

6263
} // namespace doc
6364
} // namespace clang
Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
#include "Generators.h"
2+
#include "llvm/Support/JSON.h"
3+
4+
using namespace llvm;
5+
using namespace llvm::json;
6+
7+
static llvm::ExitOnError ExitOnErr;
8+
9+
namespace clang {
10+
namespace doc {
11+
12+
class JSONGenerator : public Generator {
13+
public:
14+
static const char *Format;
15+
16+
Error generateDocs(StringRef RootDir,
17+
llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
18+
const ClangDocContext &CDCtx) override;
19+
Error createResources(ClangDocContext &CDCtx) override;
20+
Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
21+
const ClangDocContext &CDCtx) override;
22+
};
23+
24+
const char *JSONGenerator::Format = "json";
25+
26+
static json::Object serializeLocation(const Location &Loc,
27+
std::optional<StringRef> RepositoryUrl) {
28+
Object LocationObj = Object();
29+
LocationObj["LineNumber"] = Loc.StartLineNumber;
30+
LocationObj["Filename"] = Loc.Filename;
31+
32+
if (!Loc.IsFileInRootDir || !RepositoryUrl)
33+
return LocationObj;
34+
SmallString<128> FileURL(*RepositoryUrl);
35+
sys::path::append(FileURL, sys::path::Style::posix, Loc.Filename);
36+
FileURL += "#" + std::to_string(Loc.StartLineNumber);
37+
LocationObj["FileURL"] = FileURL;
38+
return LocationObj;
39+
}
40+
41+
static json::Value serializeComment(const CommentInfo &Comment) {
42+
assert((Comment.Kind == "BlockCommandComment" ||
43+
Comment.Kind == "FullComment" || Comment.Kind == "ParagraphComment" ||
44+
Comment.Kind == "TextComment") &&
45+
"Unknown Comment type in CommentInfo.");
46+
47+
Object Obj = Object();
48+
json::Value Child = Object();
49+
50+
// TextComment has no children, so return it.
51+
if (Comment.Kind == "TextComment") {
52+
Obj["TextComment"] = Comment.Text;
53+
return Obj;
54+
}
55+
56+
// BlockCommandComment needs to generate a Command key.
57+
if (Comment.Kind == "BlockCommandComment")
58+
Child.getAsObject()->insert({"Command", Comment.Name});
59+
60+
// Use the same handling for everything else.
61+
// Only valid for:
62+
// - BlockCommandComment
63+
// - FullComment
64+
// - ParagraphComment
65+
json::Value ChildArr = Array();
66+
auto &CARef = *ChildArr.getAsArray();
67+
CARef.reserve(Comment.Children.size());
68+
for (const auto &C : Comment.Children)
69+
CARef.emplace_back(serializeComment(*C));
70+
Child.getAsObject()->insert({"Children", ChildArr});
71+
Obj.insert({Comment.Kind, Child});
72+
return Obj;
73+
}
74+
75+
static void serializeCommonAttributes(const Info &I, json::Object &Obj,
76+
std::optional<StringRef> RepositoryUrl) {
77+
Obj["Name"] = I.Name.str();
78+
Obj["USR"] = toHex(toStringRef(I.USR));
79+
80+
if (!I.Path.empty())
81+
Obj["Path"] = I.Path.str();
82+
83+
if (!I.Namespace.empty()) {
84+
Obj["Namespace"] = json::Array();
85+
for (const auto &NS : I.Namespace)
86+
Obj["Namespace"].getAsArray()->push_back(NS.Name.str());
87+
}
88+
89+
if (!I.Description.empty()) {
90+
json::Value DescArray = json::Array();
91+
auto &DescArrayRef = *DescArray.getAsArray();
92+
for (const auto &Comment : I.Description)
93+
DescArrayRef.push_back(serializeComment(Comment));
94+
Obj["Description"] = std::move(DescArray);
95+
}
96+
97+
// Namespaces aren't SymbolInfos, so they dont have a DefLoc
98+
if (I.IT != InfoType::IT_namespace) {
99+
const auto *Symbol = static_cast<const SymbolInfo *>(&I);
100+
if (Symbol->DefLoc)
101+
Obj["Location"] =
102+
serializeLocation(Symbol->DefLoc.value(), RepositoryUrl);
103+
}
104+
}
105+
106+
static void serializeTypeInfo(const TypeInfo &I, Object &Obj) {
107+
Obj["Name"] = I.Type.Name;
108+
Obj["QualName"] = I.Type.QualName;
109+
Obj["ID"] = toHex(toStringRef(I.Type.USR));
110+
Obj["IsTemplate"] = I.IsTemplate;
111+
Obj["IsBuiltIn"] = I.IsBuiltIn;
112+
}
113+
114+
static void serializeInfo(const FunctionInfo &F, json::Object &Obj,
115+
std::optional<StringRef> RepositoryURL) {
116+
serializeCommonAttributes(F, Obj, RepositoryURL);
117+
Obj["IsStatic"] = F.IsStatic;
118+
119+
auto ReturnTypeObj = Object();
120+
serializeTypeInfo(F.ReturnType, ReturnTypeObj);
121+
Obj["ReturnType"] = std::move(ReturnTypeObj);
122+
123+
if (!F.Params.empty()) {
124+
json::Value ParamsArray = json::Array();
125+
auto &ParamsArrayRef = *ParamsArray.getAsArray();
126+
for (const auto &Param : F.Params) {
127+
json::Object ParamObj;
128+
ParamObj["Name"] = Param.Name;
129+
ParamObj["Type"] = Param.Type.Name;
130+
ParamsArrayRef.push_back(std::move(ParamObj));
131+
}
132+
Obj["Params"] = std::move(ParamsArray);
133+
}
134+
}
135+
136+
static void serializeInfo(const EnumInfo &I, json::Object &Obj,
137+
std::optional<StringRef> RepositoryUrl) {
138+
serializeCommonAttributes(I, Obj, RepositoryUrl);
139+
Obj["Scoped"] = I.Scoped;
140+
141+
if (I.BaseType) {
142+
json::Object BaseTypeObj;
143+
BaseTypeObj["Name"] = I.BaseType->Type.Name;
144+
BaseTypeObj["QualName"] = I.BaseType->Type.QualName;
145+
BaseTypeObj["ID"] = toHex(toStringRef(I.BaseType->Type.USR));
146+
Obj["BaseType"] = std::move(BaseTypeObj);
147+
}
148+
149+
if (!I.Members.empty()) {
150+
json::Value MembersArray = Array();
151+
auto &MembersArrayRef = *MembersArray.getAsArray();
152+
for (const auto &Member : I.Members) {
153+
json::Object MemberObj;
154+
MemberObj["Name"] = Member.Name;
155+
if (!Member.ValueExpr.empty())
156+
MemberObj["ValueExpr"] = Member.ValueExpr;
157+
else
158+
MemberObj["Value"] = Member.Value;
159+
MembersArrayRef.push_back(std::move(MemberObj));
160+
}
161+
Obj["Members"] = std::move(MembersArray);
162+
}
163+
}
164+
165+
static void serializeInfo(const TypedefInfo &I, json::Object &Obj,
166+
std::optional<StringRef> RepositoryUrl) {
167+
serializeCommonAttributes(I, Obj, RepositoryUrl);
168+
Obj["TypeDeclaration"] = I.TypeDeclaration;
169+
Obj["IsUsing"] = I.IsUsing;
170+
Object TypeObj = Object();
171+
serializeTypeInfo(I.Underlying, TypeObj);
172+
Obj["Underlying"] = std::move(TypeObj);
173+
}
174+
175+
static void serializeInfo(const RecordInfo &I, json::Object &Obj,
176+
std::optional<StringRef> RepositoryUrl) {
177+
serializeCommonAttributes(I, Obj, RepositoryUrl);
178+
Obj["FullName"] = I.Name.str();
179+
Obj["TagType"] = getTagType(I.TagType);
180+
Obj["IsTypedef"] = I.IsTypeDef;
181+
182+
if (!I.Children.Functions.empty()) {
183+
json::Value PublicFunctionArr = Array();
184+
json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray();
185+
json::Value ProtectedFunctionArr = Array();
186+
json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray();
187+
188+
for (const auto &Function : I.Children.Functions) {
189+
json::Object FunctionObj;
190+
serializeInfo(Function, FunctionObj, RepositoryUrl);
191+
AccessSpecifier Access = Function.Access;
192+
if (Access == AccessSpecifier::AS_public)
193+
PublicFunctionARef.push_back(std::move(FunctionObj));
194+
else if (Access == AccessSpecifier::AS_protected)
195+
ProtectedFunctionARef.push_back(std::move(FunctionObj));
196+
}
197+
198+
if (!PublicFunctionARef.empty())
199+
Obj["PublicFunctions"] = std::move(PublicFunctionArr);
200+
if (!ProtectedFunctionARef.empty())
201+
Obj["ProtectedFunctions"] = std::move(ProtectedFunctionArr);
202+
}
203+
204+
if (!I.Members.empty()) {
205+
json::Value PublicMembers = Array();
206+
json::Array &PubMemberRef = *PublicMembers.getAsArray();
207+
json::Value ProtectedMembers = Array();
208+
json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();
209+
210+
for (const MemberTypeInfo &Member : I.Members) {
211+
json::Object MemberObj = Object();
212+
MemberObj["Name"] = Member.Name;
213+
MemberObj["Type"] = Member.Type.Name;
214+
215+
if (Member.Access == AccessSpecifier::AS_public)
216+
PubMemberRef.push_back(std::move(MemberObj));
217+
else if (Member.Access == AccessSpecifier::AS_protected)
218+
ProtMemberRef.push_back(std::move(MemberObj));
219+
}
220+
221+
if (!PubMemberRef.empty())
222+
Obj["PublicMembers"] = std::move(PublicMembers);
223+
if (!ProtMemberRef.empty())
224+
Obj["ProtectedMembers"] = std::move(ProtectedMembers);
225+
}
226+
227+
if (!I.Children.Enums.empty()) {
228+
json::Value EnumsArray = Array();
229+
auto &EnumsArrayRef = *EnumsArray.getAsArray();
230+
for (const auto &Enum : I.Children.Enums) {
231+
json::Object EnumObj;
232+
serializeInfo(Enum, EnumObj, RepositoryUrl);
233+
EnumsArrayRef.push_back(std::move(EnumObj));
234+
}
235+
Obj["Enums"] = std::move(EnumsArray);
236+
}
237+
238+
if (!I.Children.Typedefs.empty()) {
239+
json::Value TypedefsArray = Array();
240+
auto &TypedefsArrayRef = *TypedefsArray.getAsArray();
241+
for (const auto &Typedef : I.Children.Typedefs) {
242+
json::Object TypedefObj;
243+
serializeInfo(Typedef, TypedefObj, RepositoryUrl);
244+
TypedefsArrayRef.push_back(std::move(TypedefObj));
245+
}
246+
Obj["Typedefs"] = std::move(TypedefsArray);
247+
}
248+
}
249+
250+
Error JSONGenerator::generateDocs(
251+
StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
252+
const ClangDocContext &CDCtx) {
253+
StringSet<> CreatedDirs;
254+
StringMap<std::vector<doc::Info *>> FileToInfos;
255+
for (const auto &Group : Infos) {
256+
Info *Info = Group.getValue().get();
257+
258+
SmallString<128> Path;
259+
sys::path::native(RootDir, Path);
260+
sys::path::append(Path, Info->getRelativeFilePath(""));
261+
if (!CreatedDirs.contains(Path)) {
262+
if (std::error_code Err = sys::fs::create_directories(Path);
263+
Err != std::error_code())
264+
ExitOnErr(createFileError(Twine(Path), Err));
265+
CreatedDirs.insert(Path);
266+
}
267+
268+
sys::path::append(Path, Info->getFileBaseName() + ".json");
269+
FileToInfos[Path].push_back(Info);
270+
}
271+
272+
for (const auto &Group : FileToInfos) {
273+
std::error_code FileErr;
274+
raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_Text);
275+
if (FileErr)
276+
ExitOnErr(createFileError("cannot open file " + Group.getKey(), FileErr));
277+
278+
for (const auto &Info : Group.getValue())
279+
if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
280+
return Err;
281+
}
282+
283+
return Error::success();
284+
}
285+
286+
Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
287+
const ClangDocContext &CDCtx) {
288+
json::Object Obj = Object();
289+
290+
switch (I->IT) {
291+
case InfoType::IT_namespace:
292+
break;
293+
case InfoType::IT_record:
294+
serializeInfo(*static_cast<RecordInfo *>(I), Obj, CDCtx.RepositoryUrl);
295+
break;
296+
case InfoType::IT_enum:
297+
case InfoType::IT_function:
298+
case InfoType::IT_typedef:
299+
break;
300+
case InfoType::IT_default:
301+
ExitOnErr(
302+
createStringError(inconvertibleErrorCode(), "unexpected info type"));
303+
}
304+
OS << llvm::formatv("{0:2}", llvm::json::Value(std::move(Obj)));
305+
return Error::success();
306+
}
307+
308+
Error JSONGenerator::createResources(ClangDocContext &CDCtx) {
309+
return Error::success();
310+
}
311+
312+
static GeneratorRegistry::Add<JSONGenerator> JSON(JSONGenerator::Format,
313+
"Generator for JSON output.");
314+
volatile int JSONGeneratorAnchorSource = 0;
315+
} // namespace doc
316+
} // namespace clang

clang-tools-extra/clang-doc/tool/ClangDocMain.cpp

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ static llvm::cl::opt<std::string> RepositoryCodeLinePrefix(
104104
llvm::cl::desc("Prefix of line code for repository."),
105105
llvm::cl::cat(ClangDocCategory));
106106

107-
enum OutputFormatTy { md, yaml, html, mustache };
107+
enum OutputFormatTy { md, yaml, html, mustache, json };
108108

109109
static llvm::cl::opt<OutputFormatTy> FormatEnum(
110110
"format", llvm::cl::desc("Format for outputted docs."),
@@ -115,7 +115,9 @@ static llvm::cl::opt<OutputFormatTy> FormatEnum(
115115
clEnumValN(OutputFormatTy::html, "html",
116116
"Documentation in HTML format."),
117117
clEnumValN(OutputFormatTy::mustache, "mustache",
118-
"Documentation in mustache HTML format")),
118+
"Documentation in mustache HTML format"),
119+
clEnumValN(OutputFormatTy::json, "json",
120+
"Documentation in JSON format")),
119121
llvm::cl::init(OutputFormatTy::yaml), llvm::cl::cat(ClangDocCategory));
120122

121123
static llvm::ExitOnError ExitOnErr;
@@ -130,6 +132,8 @@ static std::string getFormatString() {
130132
return "html";
131133
case OutputFormatTy::mustache:
132134
return "mustache";
135+
case OutputFormatTy::json:
136+
return "json";
133137
}
134138
llvm_unreachable("Unknown OutputFormatTy");
135139
}

0 commit comments

Comments
 (0)