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