clang-tools 23.0.0git
JSONGenerator.cpp
Go to the documentation of this file.
1#include "Generators.h"
2#include "clang/Basic/Specifiers.h"
3#include "llvm/ADT/ArrayRef.h"
4#include "llvm/Support/JSON.h"
5
6using namespace llvm;
7using namespace llvm::json;
8
9namespace clang {
10namespace doc {
11
12template <typename Container, typename SerializationFunc>
13static void serializeArray(
14 const Container &Records, Object &Obj, const StringRef Key,
15 SerializationFunc SerializeInfo, const StringRef EndKey = "End",
16 function_ref<void(Object &)> UpdateJson = [](Object &Obj) {});
17
18// TODO(issue URL): Wrapping logic for HTML should probably use a more
19// sophisticated heuristic than number of parameters.
20constexpr static unsigned getMaxParamWrapLimit() { return 2; }
21
22typedef std::function<void(const Reference &, Object &)> ReferenceFunc;
23
24class JSONGenerator : public Generator {
25 json::Object serializeLocation(const Location &Loc);
26 void serializeCommonAttributes(const Info &I, json::Object &Obj);
27 void serializeCommonChildren(
28 const ScopeChildren &Children, json::Object &Obj,
29 std::optional<ReferenceFunc> MDReferenceLambda = std::nullopt);
30 void serializeContexts(Info *I, llvm::StringMap<OwnedPtr<Info>> &Infos);
31 void serializeInfo(const ConstraintInfo &I, Object &Obj);
32 void serializeInfo(const TemplateInfo &Template, Object &Obj);
33 void serializeInfo(const ConceptInfo &I, Object &Obj);
34 void serializeInfo(const TypeInfo &I, Object &Obj);
35 void serializeInfo(const FieldTypeInfo &I, Object &Obj);
36 void serializeInfo(const FunctionInfo &F, json::Object &Obj);
37 void serializeInfo(const EnumValueInfo &I, Object &Obj);
38 void serializeInfo(const EnumInfo &I, json::Object &Obj);
39 void serializeInfo(const TypedefInfo &I, json::Object &Obj);
40 void serializeInfo(const BaseRecordInfo &I, Object &Obj);
41 void serializeInfo(const FriendInfo &I, Object &Obj);
42 void serializeInfo(const RecordInfo &I, json::Object &Obj);
43 void serializeInfo(const VarInfo &I, json::Object &Obj);
44 void serializeInfo(const NamespaceInfo &I, json::Object &Obj);
45 SmallString<16> determineFileName(Info *I, SmallString<128> &Path);
46 Error serializeIndex(StringRef RootDir);
47 void generateContext(const Info &I, Object &Obj);
48 void serializeReference(const Reference &Ref, Object &ReferenceObj);
49 Error serializeAllFiles(const ClangDocContext &CDCtx, StringRef RootDir);
50 void serializeMDReference(const Reference &Ref, Object &ReferenceObj,
51 StringRef BasePath);
52
53 // Convenience lambdas to pass to serializeArray.
54 auto serializeInfoLambda() {
55 return [this](const auto &Info, Object &Object) {
56 serializeInfo(Info, Object);
57 };
58 }
59 auto serializeReferenceLambda() {
60 return [this](const auto &Ref, Object &Object) {
61 serializeReference(Ref, Object);
62 };
63 }
64
65 llvm::DenseMap<const Info *, SmallVector<Context, 4>> ContextsMap;
66 const ClangDocContext *CDCtx;
67 bool Markdown;
68
69public:
70 static const char *Format;
71
72 Error generateDocumentation(StringRef RootDir,
73 llvm::StringMap<OwnedPtr<doc::Info>> Infos,
74 const ClangDocContext &CDCtx,
75 std::string DirName) override;
76 Error createResources(ClangDocContext &CDCtx) override;
77 // FIXME: Once legacy generators are removed, we can refactor the Generator
78 // interface to sto passing CDCtx here since we hold a pointer to it.
79 Error generateDocForInfo(Info *I, llvm::raw_ostream &OS,
80 const ClangDocContext &CDCtx) override;
81};
82
83const char *JSONGenerator::Format = "json";
84
85static void insertNonEmpty(StringRef Key, StringRef Value, Object &Obj) {
86 if (!Value.empty())
87 Obj[Key] = Value;
88}
89
90static std::string infoTypeToString(InfoType IT) {
91 switch (IT) {
93 return "default";
95 return "namespace";
97 return "record";
99 return "function";
101 return "enum";
103 return "typedef";
105 return "concept";
107 return "variable";
109 return "friend";
110 }
111 llvm_unreachable("Unknown InfoType encountered.");
112}
113
114json::Object JSONGenerator::serializeLocation(const Location &Loc) {
115 Object LocationObj = Object();
116 LocationObj["LineNumber"] = Loc.StartLineNumber;
117 LocationObj["Filename"] = Loc.Filename;
118
119 if (!Loc.IsFileInRootDir || !CDCtx->RepositoryUrl)
120 return LocationObj;
121 SmallString<128> FileURL(*CDCtx->RepositoryUrl);
122 sys::path::append(FileURL, sys::path::Style::posix, Loc.Filename);
123
124 std::string LinePrefix;
125 if (!CDCtx->RepositoryLinePrefix)
126 LinePrefix = "#L";
127 else
128 LinePrefix = *CDCtx->RepositoryLinePrefix;
129
130 FileURL += LinePrefix + std::to_string(Loc.StartLineNumber);
131 LocationObj["FileURL"] = FileURL;
132 return LocationObj;
133}
134
135/// Insert comments into a key in the Description object.
136///
137/// \param Comment Either an Object or Array, depending on the comment type
138/// \param Key The type (Brief, Code, etc.) of comment to be inserted
139static void insertComment(Object &Description, json::Value &Comment,
140 StringRef Key) {
141 // The comment has a Children array for the actual text, with meta attributes
142 // alongside it in the Object.
143 if (auto *Obj = Comment.getAsObject()) {
144 if (auto *Children = Obj->getArray("Children");
145 Children && Children->empty())
146 return;
147 }
148 // The comment is just an array of text comments.
149 else if (auto *Array = Comment.getAsArray(); Array && Array->empty()) {
150 return;
151 }
152
153 auto DescriptionIt = Description.find(Key);
154
155 if (DescriptionIt == Description.end()) {
156 auto CommentsArray = json::Array();
157 CommentsArray.push_back(Comment);
158 Description[Key] = std::move(CommentsArray);
159 Description["Has" + Key.str()] = true;
160 } else {
161 DescriptionIt->getSecond().getAsArray()->push_back(Comment);
162 }
163}
164
165/// Takes the nested "Children" array from a comment Object.
166///
167/// \return a json::Array of comments, possible json::Value::Kind::Null
168static json::Value extractTextComments(Object *ParagraphComment) {
169 if (!ParagraphComment)
170 return json::Value(nullptr);
171 json::Value *Children = ParagraphComment->get("Children");
172 if (!Children)
173 return json::Value(nullptr);
174 auto ChildrenArray = *Children->getAsArray();
175 auto ChildrenIt = ChildrenArray.begin();
176 while (ChildrenIt != ChildrenArray.end()) {
177 auto *ChildObj = ChildrenIt->getAsObject();
178 assert(ChildObj && "Invalid JSON object in Comment");
179 auto TextComment = ChildObj->getString("TextComment");
180 if (!TextComment || TextComment->empty()) {
181 ChildrenIt = ChildrenArray.erase(ChildrenIt);
182 continue;
183 }
184 ++ChildrenIt;
185 }
186 return ChildrenArray;
187}
188
189static json::Value extractVerbatimComments(json::Array VerbatimLines) {
190 json::Value TextArray = json::Array();
191 auto &TextArrayRef = *TextArray.getAsArray();
192 for (auto &Line : VerbatimLines)
193 TextArrayRef.push_back(*Line.getAsObject()
194 ->get("VerbatimBlockLineComment")
195 ->getAsObject()
196 ->get("Text"));
197
198 return TextArray;
199}
200
201static Object serializeComment(const CommentInfo &I, Object &Description) {
202 // taken from PR #142273
203 Object Obj = Object();
204
205 json::Value ChildVal = Object();
206 Object &Child = *ChildVal.getAsObject();
207
208 json::Value ChildArr = Array();
209 auto &CARef = *ChildArr.getAsArray();
210 CARef.reserve(I.Children.size());
211 for (const auto &C : I.Children)
212 CARef.emplace_back(serializeComment(C, Description));
213
214 switch (I.Kind) {
216 if (!I.Text.empty())
217 Obj.insert({commentKindToString(I.Kind), I.Text});
218 return Obj;
219 }
220
222 auto TextCommentsArray = extractTextComments(CARef.front().getAsObject());
223 if (I.Name == "brief")
224 insertComment(Description, TextCommentsArray, "BriefComments");
225 else if (I.Name == "return")
226 insertComment(Description, TextCommentsArray, "ReturnComments");
227 else if (I.Name == "throws" || I.Name == "throw") {
228 json::Value ThrowsVal = Object();
229 auto &ThrowsObj = *ThrowsVal.getAsObject();
230 ThrowsObj["Exception"] = I.Args.front();
231 ThrowsObj["Children"] = TextCommentsArray;
232 insertComment(Description, ThrowsVal, "ThrowsComments");
233 }
234 return Obj;
235 }
236
238 json::Value ArgsArr = Array();
239 auto &ARef = *ArgsArr.getAsArray();
240 ARef.reserve(I.Args.size());
241 for (const auto &Arg : I.Args)
242 ARef.emplace_back(Arg);
243 Child.insert({"Command", I.Name});
244 Child.insert({"Args", ArgsArr});
245 Child.insert({"Children", ChildArr});
246 Obj.insert({commentKindToString(I.Kind), ChildVal});
247 return Obj;
248 }
249
252 Child.insert({"ParamName", I.ParamName});
253 Child.insert({"Direction", I.Direction});
254 Child.insert({"Explicit", I.Explicit});
255 auto TextCommentsArray = extractTextComments(CARef.front().getAsObject());
256 Child.insert({"Children", TextCommentsArray});
258 insertComment(Description, ChildVal, "ParamComments");
260 insertComment(Description, ChildVal, "TParamComments");
261 return Obj;
262 }
263
265 if (I.CloseName == "endcode") {
266 // We don't support \code language specification
267 auto TextCommentsArray = extractVerbatimComments(CARef);
268 insertComment(Description, TextCommentsArray, "CodeComments");
269 } else if (I.CloseName == "endverbatim")
270 insertComment(Description, ChildVal, "VerbatimComments");
271 return Obj;
272 }
273
276 Child.insert({"Text", I.Text});
277 Child.insert({"Children", ChildArr});
278 Obj.insert({commentKindToString(I.Kind), ChildVal});
279 return Obj;
280 }
281
283 json::Value AttrKeysArray = json::Array();
284 json::Value AttrValuesArray = json::Array();
285 auto &KeyArr = *AttrKeysArray.getAsArray();
286 auto &ValArr = *AttrValuesArray.getAsArray();
287 KeyArr.reserve(I.AttrKeys.size());
288 ValArr.reserve(I.AttrValues.size());
289 for (const auto &K : I.AttrKeys)
290 KeyArr.emplace_back(K);
291 for (const auto &V : I.AttrValues)
292 ValArr.emplace_back(V);
293 Child.insert({"Name", I.Name});
294 Child.insert({"SelfClosing", I.SelfClosing});
295 Child.insert({"AttrKeys", AttrKeysArray});
296 Child.insert({"AttrValues", AttrValuesArray});
297 Child.insert({"Children", ChildArr});
298 Obj.insert({commentKindToString(I.Kind), ChildVal});
299 return Obj;
300 }
301
303 Child.insert({"Name", I.Name});
304 Child.insert({"Children", ChildArr});
305 Obj.insert({commentKindToString(I.Kind), ChildVal});
306 return Obj;
307 }
308
311 Child.insert({"Children", ChildArr});
312 Child["ParagraphComment"] = true;
313 return Child;
314 }
315
317 Obj.insert({commentKindToString(I.Kind), I.Text});
318 return Obj;
319 }
320 }
321 llvm_unreachable("Unknown comment kind encountered.");
322}
323
324/// Creates Contexts for namespaces and records to allow for navigation.
325void JSONGenerator::generateContext(const Info &I, Object &Obj) {
326 Obj["Contexts"] = json::Array();
327 Obj["HasContexts"] = true;
328
329 auto It = ContextsMap.find(&I);
330 if (It == ContextsMap.end() || It->second.empty())
331 return;
332
333 auto &ContextArrayRef = *Obj["Contexts"].getAsArray();
334 const auto &Contexts = It->second;
335 ContextArrayRef.reserve(Contexts.size());
336
337 std::string CurrentRelativePath;
338 bool PreviousRecord = false;
339 for (const auto &Current : Contexts) {
340 json::Value ContextVal = Object();
341 Object &Context = *ContextVal.getAsObject();
342 serializeReference(Current, Context);
343
344 if (ContextArrayRef.empty() && I.IT == InfoType::IT_record) {
345 if (Current.DocumentationFileName == "index") {
346 // If the record's immediate context is a namespace, then the
347 // "index.html" is in the same directory.
348 PreviousRecord = false;
349 Context["RelativePath"] = "./";
350 } else {
351 // If the immediate context is a record, then the file is one level
352 // above
353 PreviousRecord = true;
354 CurrentRelativePath += "../";
355 Context["RelativePath"] = CurrentRelativePath;
356 }
357 ContextArrayRef.push_back(ContextVal);
358 continue;
359 }
360
361 if (PreviousRecord && (Current.DocumentationFileName == "index")) {
362 // If the previous Context was a record then we already went up a level,
363 // so the current namespace index is in the same directory.
364 PreviousRecord = false;
365 } else if (Current.DocumentationFileName != "index") {
366 // If the current Context is a record but the previous wasn't a record,
367 // then the namespace index is located one level above.
368 PreviousRecord = true;
369 CurrentRelativePath += "../";
370 } else {
371 // The current Context is a namespace and so was the previous Context.
372 PreviousRecord = false;
373 CurrentRelativePath += "../";
374 // If this namespace is the global namespace, then its documentation
375 // name needs to be changed to link correctly.
376 if (Current.QualName == "GlobalNamespace" && Current.RelativePath != "./")
377 Context["DocumentationFileName"] =
378 SmallString<16>("GlobalNamespace/index");
379 }
380 Context["RelativePath"] = CurrentRelativePath;
381 ContextArrayRef.insert(ContextArrayRef.begin(), ContextVal);
382 }
383
384 ContextArrayRef.back().getAsObject()->insert({"End", true});
385}
386
387static void serializeDescription(const OwningVec<CommentInfo> &Description,
388 json::Object &Obj, StringRef Key = "") {
389 if (Description.empty())
390 return;
391
392 // Skip straight to the FullComment's children
393 auto &Comments = Description.front()->Children;
394 Object DescriptionObj = Object();
395 for (const auto &CommentInfo : Comments) {
396 json::Value Comment = serializeComment(CommentInfo, DescriptionObj);
397 // if a ParagraphComment is returned, then it is a top-level comment that
398 // needs to be inserted manually.
399 if (auto *ParagraphComment = Comment.getAsObject();
400 ParagraphComment->get("ParagraphComment")) {
401 auto TextCommentsArray = extractTextComments(ParagraphComment);
402 if (TextCommentsArray.kind() == json::Value::Null ||
403 TextCommentsArray.getAsArray()->empty())
404 continue;
405 insertComment(DescriptionObj, TextCommentsArray, "ParagraphComments");
406 }
407 }
408 Obj["Description"] = std::move(DescriptionObj);
409 if (!Key.empty())
410 Obj[Key] = true;
411}
412
413void JSONGenerator::serializeCommonAttributes(const Info &I,
414 json::Object &Obj) {
415 insertNonEmpty("Name", I.Name, Obj);
416 if (!(I.USR == GlobalNamespaceID))
417 Obj["USR"] = toHex(toStringRef(I.USR));
418 Obj["InfoType"] = infoTypeToString(I.IT);
419 // Conditionally insert fields.
420 // Empty properties are omitted because Mustache templates use existence
421 // to conditionally render content.
422 insertNonEmpty("DocumentationFileName", I.DocumentationFileName, Obj);
423 insertNonEmpty("Path", I.Path, Obj);
424
425 if (!I.Namespace.empty()) {
426 Obj["Namespace"] = json::Array();
427 for (const auto &NS : I.Namespace)
428 Obj["Namespace"].getAsArray()->push_back(NS.Name);
429 }
430
431 serializeDescription(I.Description, Obj);
432
433 // Namespaces aren't SymbolInfos, so they dont have a DefLoc
434 if (I.IT != InfoType::IT_namespace) {
435 const auto *Symbol = static_cast<const SymbolInfo *>(&I);
436 if (Symbol->DefLoc)
437 Obj["Location"] = serializeLocation(Symbol->DefLoc.value());
438 }
439
440 auto It = ContextsMap.find(&I);
441 if (It != ContextsMap.end() && !It->second.empty())
442 generateContext(I, Obj);
443}
444
445void JSONGenerator::serializeReference(const Reference &Ref,
446 Object &ReferenceObj) {
447 insertNonEmpty("Path", Ref.Path, ReferenceObj);
448 ReferenceObj["Name"] = Ref.Name;
449 ReferenceObj["QualName"] = Ref.QualName;
450 ReferenceObj["USR"] = toHex(toStringRef(Ref.USR));
451 if (!Ref.DocumentationFileName.empty()) {
452 ReferenceObj["DocumentationFileName"] = Ref.DocumentationFileName;
453
454 // If the reference is a nested class it will be put into a folder named
455 // after the parent class. We can get that name from the path's stem.
456 if (Ref.Path != "GlobalNamespace" && !Ref.Path.empty())
457 ReferenceObj["PathStem"] = sys::path::stem(Ref.Path);
458 }
459}
460
461void JSONGenerator::serializeMDReference(const Reference &Ref,
462 Object &ReferenceObj,
463 StringRef BasePath) {
464 serializeReference(Ref, ReferenceObj);
465 SmallString<64> Path = Ref.getRelativeFilePath(BasePath);
466 sys::path::native(Path, sys::path::Style::posix);
467 sys::path::append(Path, sys::path::Style::posix,
468 Ref.getFileBaseName() + ".md");
469 ReferenceObj["BasePath"] = Path;
470}
471
472// Although namespaces and records both have ScopeChildren, they serialize them
473// differently. Only enums, records, and typedefs are handled here.
474void JSONGenerator::serializeCommonChildren(
475 const ScopeChildren &Children, json::Object &Obj,
476 std::optional<ReferenceFunc> MDReferenceLambda) {
477 if (!Children.Enums.empty()) {
478 serializeArray(Children.Enums, Obj, "Enums", serializeInfoLambda());
479 Obj["HasEnums"] = true;
480 }
481
482 if (!Children.Typedefs.empty()) {
483 serializeArray(Children.Typedefs, Obj, "Typedefs", serializeInfoLambda());
484 Obj["HasTypedefs"] = true;
485 }
486
487 if (!Children.Records.empty()) {
488 ReferenceFunc SerializeReferenceFunc = MDReferenceLambda
489 ? MDReferenceLambda.value()
490 : serializeReferenceLambda();
491 serializeArray(Children.Records, Obj, "Records", SerializeReferenceFunc);
492 Obj["HasRecords"] = true;
493 }
494}
495
496template <typename Container, typename SerializationFunc>
497static void serializeArray(const Container &Records, Object &Obj, StringRef Key,
498 SerializationFunc SerializeInfo, StringRef EndKey,
499 function_ref<void(Object &)> UpdateJson) {
500 json::Value RecordsArray = Array();
501 auto &RecordsArrayRef = *RecordsArray.getAsArray();
502 RecordsArrayRef.reserve(Records.size());
503 size_t Index = 0;
504 size_t Size = Records.size();
505 for (const auto &Item : Records) {
506 json::Value ItemVal = Object();
507 auto &ItemObj = *ItemVal.getAsObject();
508 SerializeInfo(Item, ItemObj);
509 if (Index == Size - 1)
510 ItemObj[EndKey] = true;
511 RecordsArrayRef.push_back(ItemVal);
512 ++Index;
513 }
514 Obj[Key] = RecordsArray;
515 UpdateJson(Obj);
516}
517
518void JSONGenerator::serializeInfo(const ConstraintInfo &I, Object &Obj) {
519 serializeReference(I.ConceptRef, Obj);
520 Obj["Expression"] = I.ConstraintExpr;
521}
522
523void JSONGenerator::serializeInfo(const TemplateInfo &Template, Object &Obj) {
524 json::Value TemplateVal = Object();
525 auto &TemplateObj = *TemplateVal.getAsObject();
526 auto SerializeTemplateParam = [](const TemplateParamInfo &Param,
527 Object &JsonObj) {
528 JsonObj["Param"] = Param.Contents;
529 };
530
531 if (Template.Specialization) {
532 json::Value TemplateSpecializationVal = Object();
533 auto &TemplateSpecializationObj = *TemplateSpecializationVal.getAsObject();
534 TemplateSpecializationObj["SpecializationOf"] =
535 toHex(toStringRef(Template.Specialization->SpecializationOf));
536 if (!Template.Specialization->Params.empty()) {
537 bool VerticalDisplay =
538 Template.Specialization->Params.size() > getMaxParamWrapLimit();
539 serializeArray(Template.Specialization->Params, TemplateSpecializationObj,
540 "Parameters", SerializeTemplateParam, "SpecParamEnd",
541 [VerticalDisplay](Object &JsonObj) {
542 JsonObj["VerticalDisplay"] = VerticalDisplay;
543 });
544 }
545 TemplateObj["Specialization"] = TemplateSpecializationVal;
546 }
547
548 if (!Template.Params.empty()) {
549 bool VerticalDisplay = Template.Params.size() > getMaxParamWrapLimit();
550 serializeArray(Template.Params, TemplateObj, "Parameters",
551 SerializeTemplateParam, "End",
552 [VerticalDisplay](Object &JsonObj) {
553 JsonObj["VerticalDisplay"] = VerticalDisplay;
554 });
555 }
556
557 if (!Template.Constraints.empty())
558 serializeArray(Template.Constraints, TemplateObj, "Constraints",
559 serializeInfoLambda());
560
561 Obj["Template"] = TemplateVal;
562}
563
564void JSONGenerator::serializeInfo(const ConceptInfo &I, Object &Obj) {
565 serializeCommonAttributes(I, Obj);
566 Obj["IsType"] = I.IsType;
567 Obj["ConstraintExpression"] = I.ConstraintExpression;
568 serializeInfo(I.Template, Obj);
569}
570
571void JSONGenerator::serializeInfo(const TypeInfo &I, Object &Obj) {
572 Obj["Name"] = I.Type.Name;
573 Obj["QualName"] = I.Type.QualName;
574 Obj["USR"] = toHex(toStringRef(I.Type.USR));
575 Obj["IsTemplate"] = I.IsTemplate;
576 Obj["IsBuiltIn"] = I.IsBuiltIn;
577}
578
579void JSONGenerator::serializeInfo(const FieldTypeInfo &I, Object &Obj) {
580 Obj["Name"] = I.Name;
581 insertNonEmpty("DefaultValue", I.DefaultValue, Obj);
582 json::Value ReferenceVal = Object();
583 Object &ReferenceObj = *ReferenceVal.getAsObject();
584 serializeReference(I.Type, ReferenceObj);
585 Obj["Type"] = ReferenceVal;
586}
587
588void JSONGenerator::serializeInfo(const FunctionInfo &F, json::Object &Obj) {
589 serializeCommonAttributes(F, Obj);
590 Obj["IsStatic"] = F.IsStatic;
591
592 auto ReturnTypeObj = Object();
593 serializeInfo(F.ReturnType, ReturnTypeObj);
594 Obj["ReturnType"] = std::move(ReturnTypeObj);
595
596 if (!F.Params.empty()) {
597 const bool VerticalDisplay = F.Params.size() > getMaxParamWrapLimit();
598 serializeArray(F.Params, Obj, "Params", serializeInfoLambda(), "ParamEnd",
599 [VerticalDisplay](Object &JsonObj) {
600 JsonObj["VerticalDisplay"] = VerticalDisplay;
601 });
602 }
603
604 if (F.Template)
605 serializeInfo(F.Template.value(), Obj);
606}
607
608void JSONGenerator::serializeInfo(const EnumValueInfo &I, Object &Obj) {
609 Obj["Name"] = I.Name;
610 if (!I.ValueExpr.empty())
611 Obj["ValueExpr"] = I.ValueExpr;
612 else
613 Obj["Value"] = I.Value;
614
615 serializeDescription(I.Description, Obj, "HasEnumMemberComments");
616}
617
618void JSONGenerator::serializeInfo(const EnumInfo &I, json::Object &Obj) {
619 serializeCommonAttributes(I, Obj);
620 Obj["Scoped"] = I.Scoped;
621
622 if (I.BaseType) {
623 json::Value BaseTypeVal = Object();
624 auto &BaseTypeObj = *BaseTypeVal.getAsObject();
625 BaseTypeObj["Name"] = I.BaseType->Type.Name;
626 BaseTypeObj["QualName"] = I.BaseType->Type.QualName;
627 BaseTypeObj["USR"] = toHex(toStringRef(I.BaseType->Type.USR));
628 Obj["BaseType"] = BaseTypeVal;
629 }
630
631 if (!I.Members.empty()) {
632 for (const auto &Member : I.Members) {
633 if (!Member.Description.empty()) {
634 Obj["HasComments"] = true;
635 break;
636 }
637 }
638 serializeArray(I.Members, Obj, "Members", serializeInfoLambda());
639 }
640}
641
642void JSONGenerator::serializeInfo(const TypedefInfo &I, json::Object &Obj) {
643 serializeCommonAttributes(I, Obj);
644 Obj["TypeDeclaration"] = I.TypeDeclaration;
645 Obj["IsUsing"] = I.IsUsing;
646 json::Value TypeVal = Object();
647 auto &TypeObj = *TypeVal.getAsObject();
648 serializeInfo(I.Underlying, TypeObj);
649 Obj["Underlying"] = TypeVal;
650 if (I.Template)
651 serializeInfo(I.Template.value(), Obj);
652}
653
654void JSONGenerator::serializeInfo(const BaseRecordInfo &I, Object &Obj) {
655 serializeInfo(static_cast<const RecordInfo &>(I), Obj);
656 Obj["IsVirtual"] = I.IsVirtual;
657 Obj["Access"] = getAccessSpelling(I.Access);
658 Obj["IsParent"] = I.IsParent;
659}
660
661void JSONGenerator::serializeInfo(const FriendInfo &I, Object &Obj) {
662 auto FriendRef = Object();
663 serializeReference(I.Ref, FriendRef);
664 Obj["Reference"] = std::move(FriendRef);
665 Obj["IsClass"] = I.IsClass;
666 if (I.Template)
667 serializeInfo(I.Template.value(), Obj);
668 if (!I.Params.empty())
669 serializeArray(I.Params, Obj, "Params", serializeInfoLambda());
670 if (I.ReturnType) {
671 auto ReturnTypeObj = Object();
672 serializeInfo(I.ReturnType.value(), ReturnTypeObj);
673 Obj["ReturnType"] = std::move(ReturnTypeObj);
674 }
675 serializeCommonAttributes(I, Obj);
676}
677
678static void insertArray(Object &Obj, json::Value &Array, StringRef Key) {
679 Obj[Key] = Array;
680 Obj["Has" + Key.str()] = true;
681}
682
683void JSONGenerator::serializeInfo(const RecordInfo &I, json::Object &Obj) {
684 serializeCommonAttributes(I, Obj);
685 Obj["TagType"] = getTagType(I.TagType);
686 Obj["IsTypedef"] = I.IsTypeDef;
687 Obj["MangledName"] = I.MangledName;
688
689 if (!I.Children.Functions.empty()) {
690 json::Value PubFunctionsArray = Array();
691 json::Array &PubFunctionsArrayRef = *PubFunctionsArray.getAsArray();
692 json::Value ProtFunctionsArray = Array();
693 json::Array &ProtFunctionsArrayRef = *ProtFunctionsArray.getAsArray();
694
695 for (const auto &Function : I.Children.Functions) {
696 json::Value FunctionVal = Object();
697 auto &FunctionObj = *FunctionVal.getAsObject();
698 serializeInfo(Function, FunctionObj);
699 AccessSpecifier Access = Function->Access;
700 if (Access == AccessSpecifier::AS_public)
701 PubFunctionsArrayRef.push_back(FunctionVal);
702 else if (Access == AccessSpecifier::AS_protected)
703 ProtFunctionsArrayRef.push_back(FunctionVal);
704 }
705
706 if (!PubFunctionsArrayRef.empty())
707 insertArray(Obj, PubFunctionsArray, "PublicMethods");
708 if (!ProtFunctionsArrayRef.empty())
709 insertArray(Obj, ProtFunctionsArray, "ProtectedMethods");
710 }
711
712 if (!I.Members.empty()) {
713 Obj["HasMembers"] = true;
714 json::Value PublicMembersArray = Array();
715 json::Array &PubMembersArrayRef = *PublicMembersArray.getAsArray();
716 json::Value ProtectedMembersArray = Array();
717 json::Array &ProtMembersArrayRef = *ProtectedMembersArray.getAsArray();
718 json::Value PrivateMembersArray = Array();
719 json::Array &PrivateMembersArrayRef = *PrivateMembersArray.getAsArray();
720
721 for (const MemberTypeInfo &Member : I.Members) {
722 json::Value MemberVal = Object();
723 auto &MemberObj = *MemberVal.getAsObject();
724 MemberObj["Name"] = Member.Name;
725 MemberObj["Type"] = Member.Type.Name;
726 MemberObj["IsStatic"] = Member.IsStatic;
727
728 if (Member.Access == AccessSpecifier::AS_public)
729 PubMembersArrayRef.push_back(MemberVal);
730 else if (Member.Access == AccessSpecifier::AS_protected)
731 ProtMembersArrayRef.push_back(MemberVal);
732 else if (Member.Access == AccessSpecifier::AS_private)
733 PrivateMembersArrayRef.push_back(MemberVal);
734 }
735
736 if (!PubMembersArrayRef.empty())
737 insertArray(Obj, PublicMembersArray, "PublicMembers");
738 if (!ProtMembersArrayRef.empty())
739 insertArray(Obj, ProtectedMembersArray, "ProtectedMembers");
740 if (!PrivateMembersArrayRef.empty())
741 insertArray(Obj, PrivateMembersArray, "PrivateMembers");
742 }
743
744 if (!I.Bases.empty())
745 serializeArray(I.Bases, Obj, "Bases", serializeInfoLambda());
746
747 if (!I.Parents.empty()) {
748 serializeArray(I.Parents, Obj, "Parents", serializeReferenceLambda());
749 Obj["HasParents"] = true;
750 }
751
752 if (!I.VirtualParents.empty()) {
753 serializeArray(I.VirtualParents, Obj, "VirtualParents",
754 serializeReferenceLambda());
755 Obj["HasVirtualParents"] = true;
756 }
757
758 if (I.Template)
759 serializeInfo(I.Template.value(), Obj);
760
761 if (!I.Friends.empty()) {
762 serializeArray(I.Friends, Obj, "Friends", serializeInfoLambda());
763 Obj["HasFriends"] = true;
764 }
765
766 serializeCommonChildren(I.Children, Obj);
767}
768
769void JSONGenerator::serializeInfo(const VarInfo &I, json::Object &Obj) {
770 serializeCommonAttributes(I, Obj);
771 Obj["IsStatic"] = I.IsStatic;
772 auto TypeObj = Object();
773 serializeInfo(I.Type, TypeObj);
774 Obj["Type"] = std::move(TypeObj);
775}
776
777void JSONGenerator::serializeInfo(const NamespaceInfo &I, json::Object &Obj) {
778 serializeCommonAttributes(I, Obj);
779 if (I.USR == GlobalNamespaceID)
780 Obj["Name"] = "Global Namespace";
781
782 if (!I.Children.Functions.empty()) {
783 serializeArray(I.Children.Functions, Obj, "Functions",
784 serializeInfoLambda());
785 Obj["HasFunctions"] = true;
786 }
787
788 if (!I.Children.Concepts.empty()) {
789 serializeArray(I.Children.Concepts, Obj, "Concepts", serializeInfoLambda());
790 Obj["HasConcepts"] = true;
791 }
792
793 if (!I.Children.Variables.empty()) {
794 serializeArray(I.Children.Variables, Obj, "Variables",
795 serializeInfoLambda());
796 Obj["HasVariables"] = true;
797 }
798
799 ReferenceFunc SerializeReferenceFunc;
800 if (Markdown) {
801 SmallString<64> BasePath = I.getRelativeFilePath("");
802 // serializeCommonChildren doesn't accept Infos, so this lambda needs to be
803 // created here. To avoid making serializeCommonChildren a template, this
804 // lambda is an std::function
805 SerializeReferenceFunc = [this, BasePath](const Reference &Ref,
806 Object &Object) {
807 serializeMDReference(Ref, Object, BasePath);
808 };
809 serializeCommonChildren(I.Children, Obj, SerializeReferenceFunc);
810 } else {
811 SerializeReferenceFunc = serializeReferenceLambda();
812 serializeCommonChildren(I.Children, Obj);
813 }
814
815 if (!I.Children.Namespaces.empty()) {
816 serializeArray(I.Children.Namespaces, Obj, "Namespaces",
817 SerializeReferenceFunc);
818 Obj["HasNamespaces"] = true;
819 }
820}
821
822SmallString<16> JSONGenerator::determineFileName(Info *I,
823 SmallString<128> &Path) {
824 SmallString<16> FileName;
825 if (I->IT == InfoType::IT_record) {
826 auto *RecordSymbolInfo = static_cast<SymbolInfo *>(I);
827 FileName = RecordSymbolInfo->MangledName;
828 } else if (I->IT == InfoType::IT_namespace) {
829 FileName = "index";
830 } else
831 FileName = I->Name;
832 sys::path::append(Path, FileName + ".json");
833 return FileName;
834}
835
836/// \param CDCtxIndex Passed by copy since clang-doc's context is passed to the
837/// generator as `const`
838static std::vector<Index> preprocessCDCtxIndex(Index CDCtxIndex) {
839 CDCtxIndex.sort();
840 std::vector<Index> Processed;
841 Processed.reserve(CDCtxIndex.Children.size());
842 for (const auto *Idx : CDCtxIndex.getSortedChildren()) {
843 Index NewIdx = *Idx;
844 SmallString<128> NewPath(NewIdx.getRelativeFilePath(""));
845 sys::path::native(NewPath, sys::path::Style::posix);
846 sys::path::append(NewPath, sys::path::Style::posix,
847 NewIdx.getFileBaseName() + ".md");
848 NewIdx.Path = internString(NewPath);
849 Processed.push_back(NewIdx);
850 }
851
852 return Processed;
853}
854
855/// Serialize ClangDocContext's Index for Markdown output
856Error JSONGenerator::serializeAllFiles(const ClangDocContext &CDCtx,
857 StringRef RootDir) {
858 json::Value ObjVal = Object();
859 Object &Obj = *ObjVal.getAsObject();
860 std::vector<Index> IndexCopy = preprocessCDCtxIndex(CDCtx.Idx);
861 serializeArray(IndexCopy, Obj, "Index", serializeReferenceLambda());
862 SmallString<128> Path;
863 sys::path::append(Path, RootDir, "json", "all_files.json");
864 std::error_code FileErr;
865 raw_fd_ostream RootOS(Path, FileErr, sys::fs::OF_Text);
866 if (FileErr)
867 return createFileError("cannot open file " + Path, FileErr);
868 RootOS << llvm::formatv("{0:2}", ObjVal);
869 return Error::success();
870}
871
872// Creates a JSON file above the global namespace directory.
873// An index can be used to create the top-level HTML index page or the Markdown
874// index file.
875Error JSONGenerator::serializeIndex(StringRef RootDir) {
876 if (CDCtx->Idx.Children.empty())
877 return Error::success();
878
879 json::Value ObjVal = Object();
880 Object &Obj = *ObjVal.getAsObject();
881 insertNonEmpty("ProjectName", CDCtx->ProjectName, Obj);
882
883 auto IndexCopy = CDCtx->Idx;
884 IndexCopy.sort();
885 json::Value IndexArray = json::Array();
886 auto &IndexArrayRef = *IndexArray.getAsArray();
887
888 if (IndexCopy.Children.empty()) {
889 // If the index is empty, default to displaying the global namespace.
890 IndexCopy.Children.try_emplace(toStringRef(GlobalNamespaceID),
892 InfoType::IT_namespace, "GlobalNamespace");
893 } else {
894 IndexArrayRef.reserve(CDCtx->Idx.Children.size());
895 }
896
897 auto Children = IndexCopy.getSortedChildren();
898
899 for (const auto *Idx : Children) {
900 if (Idx->Children.empty())
901 continue;
902 std::string TypeStr = infoTypeToString(Idx->RefType);
903 json::Value IdxVal = Object();
904 auto &IdxObj = *IdxVal.getAsObject();
905 if (Markdown)
906 TypeStr.at(0) = toUppercase(TypeStr.at(0));
907 IdxObj["Type"] = TypeStr;
908 serializeReference(*Idx, IdxObj);
909 IndexArrayRef.push_back(IdxVal);
910 }
911 Obj["Index"] = IndexArray;
912
913 SmallString<128> IndexFilePath(RootDir);
914 sys::path::append(IndexFilePath, "/json/index.json");
915 std::error_code FileErr;
916 raw_fd_ostream RootOS(IndexFilePath, FileErr, sys::fs::OF_Text);
917 if (FileErr)
918 return createFileError("cannot open file " + IndexFilePath, FileErr);
919 RootOS << llvm::formatv("{0:2}", ObjVal);
920 return Error::success();
921}
922
923void JSONGenerator::serializeContexts(Info *I,
924 StringMap<OwnedPtr<Info>> &Infos) {
925 if (I->USR == GlobalNamespaceID)
926 return;
927 auto ParentUSR = I->ParentUSR;
928 auto &LocalContexts = ContextsMap[I];
929
930 while (true) {
931 // Infos may not have the ParentUSR, if its been filtered (public or path),
932 // so we can't use at() for the lookup, since it would abort.
933 auto Iter = Infos.find(llvm::toHex(ParentUSR));
934 if (Iter == Infos.end())
935 break;
936 auto &ParentInfo = Iter->second;
937
938 if (ParentInfo && ParentInfo->USR == GlobalNamespaceID) {
939 Context GlobalRef(ParentInfo->USR, "Global Namespace",
940 InfoType::IT_namespace, "GlobalNamespace", "",
941 SmallString<16>("index"));
942 LocalContexts.push_back(GlobalRef);
943 break;
944 }
945
946 Context ParentRef(*ParentInfo);
947 LocalContexts.push_back(ParentRef);
948 ParentUSR = ParentInfo->ParentUSR;
949 }
950}
951
953 StringRef RootDir, llvm::StringMap<doc::OwnedPtr<doc::Info>> Infos,
954 const ClangDocContext &CDCtx, std::string DirName) {
955 this->CDCtx = &CDCtx;
956 StringSet<> CreatedDirs;
957 StringMap<std::vector<doc::Info *>> FileToInfos;
958 for (const auto &Group : Infos) {
959 Info *Info = getPtr(Group.getValue());
960
961 SmallString<128> Path;
962 auto RootDirStr = RootDir.str() + "/json";
963 StringRef JSONDir = StringRef(RootDirStr);
964 sys::path::native(JSONDir, Path);
965 sys::path::append(Path, Info->getRelativeFilePath(""));
966 if (!CreatedDirs.contains(Path)) {
967 if (std::error_code Err = sys::fs::create_directories(Path);
968 Err != std::error_code())
969 return createFileError(Twine(Path), Err);
970 CreatedDirs.insert(Path);
971 }
972
973 SmallString<16> FileName = determineFileName(Info, Path);
974 if (FileToInfos.contains(Path))
975 continue;
976 FileToInfos[Path].push_back(Info);
978 }
979
980 if (CDCtx.Format == OutputFormatTy::md_mustache) {
981 Markdown = true;
982 if (auto Err = serializeAllFiles(CDCtx, RootDir))
983 return Err;
984 }
985
986 for (const auto &Group : FileToInfos) {
987 std::error_code FileErr;
988 raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_Text);
989 if (FileErr)
990 return createFileError("cannot open file " + Group.getKey(), FileErr);
991
992 for (const auto &Info : Group.getValue()) {
994 serializeContexts(Info, Infos);
995 if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
996 return Err;
997 }
998 }
999
1000 return serializeIndex(RootDir);
1001}
1002
1003Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS,
1004 const ClangDocContext &CDCtx) {
1005 json::Object Obj = Object();
1006
1007 switch (I->IT) {
1009 serializeInfo(*static_cast<NamespaceInfo *>(I), Obj);
1010 break;
1012 serializeInfo(*static_cast<RecordInfo *>(I), Obj);
1013 break;
1015 case InfoType::IT_enum:
1020 break;
1022 return createStringError(inconvertibleErrorCode(), "unexpected info type");
1023 }
1024 OS << llvm::formatv("{0:2}", llvm::json::Value(std::move(Obj)));
1025 return Error::success();
1026}
1027
1029 return Error::success();
1030}
1031
1032static GeneratorRegistry::Add<JSONGenerator> JSON(JSONGenerator::Format,
1033 "Generator for JSON output.");
1035} // namespace doc
1036} // namespace clang
clang::find_all_symbols::SymbolInfo SymbolInfo
static const char * Format
Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, const ClangDocContext &CDCtx) override
Error createResources(ClangDocContext &CDCtx) override
Error generateDocumentation(StringRef RootDir, llvm::StringMap< OwnedPtr< doc::Info > > Infos, const ClangDocContext &CDCtx, std::string DirName) override
llvm::json::Object Obj
std::string Path
A typedef to represent a file path.
Definition Path.h:26
static constexpr unsigned getMaxParamWrapLimit()
static std::string infoTypeToString(InfoType IT)
static json::Value extractTextComments(Object *ParagraphComment)
Takes the nested "Children" array from a comment Object.
static Object serializeComment(const CommentInfo &I, Object &Description)
static void insertComment(Object &Description, json::Value &Comment, StringRef Key)
Insert comments into a key in the Description object.
volatile int JSONGeneratorAnchorSource
T * getPtr(const OwnedPtr< T > &O)
llvm::StringRef getTagType(TagTypeKind AS)
static void serializeDescription(const OwningVec< CommentInfo > &Description, json::Object &Obj, StringRef Key="")
llvm::simple_ilist< InfoNode< T > > OwningVec
static GeneratorRegistry::Add< JSONGenerator > JSON(JSONGenerator::Format, "Generator for JSON output.")
std::function< void(const Reference &, Object &)> ReferenceFunc
StringRef internString(const Twine &T)
static void serializeArray(const Container &Records, Object &Obj, const StringRef Key, SerializationFunc SerializeInfo, const StringRef EndKey="End", function_ref< void(Object &)> UpdateJson=[](Object &Obj) {})
constexpr SymbolID GlobalNamespaceID
static json::Value extractVerbatimComments(json::Array VerbatimLines)
static void insertNonEmpty(StringRef Key, StringRef Value, Object &Obj)
llvm::StringRef commentKindToString(CommentKind Kind)
static void insertArray(Object &Obj, json::Value &Array, StringRef Key)
static std::vector< Index > preprocessCDCtxIndex(Index CDCtxIndex)
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Some operations such as code completion produce a set of candidates.
Definition Generators.h:150
std::optional< std::string > RepositoryUrl
std::optional< std::string > RepositoryLinePrefix
llvm::ArrayRef< StringRef > Args
llvm::ArrayRef< StringRef > AttrValues
llvm::ArrayRef< CommentInfo > Children
llvm::ArrayRef< StringRef > AttrKeys
llvm::StringMap< Index > Children
std::vector< const Index * > getSortedChildren() const
A base struct for Infos.
StringRef getRelativeFilePath(const StringRef &CurrentPath) const
Returns the file path for this Info relative to CurrentPath.
StringRef DocumentationFileName
StringRef getFileBaseName() const
Returns the basename that should be used for this Reference.
StringRef getRelativeFilePath(const StringRef &CurrentPath) const
Returns the path for this Reference relative to CurrentPath.