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