| //===-- JSONGenerator.cpp - JSON Generator ----------------------*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| /// |
| /// \file |
| /// This file contains the implementation of the JSONGenerator, which serializes |
| /// the clang-doc internal representation (Info structures) into JSON format. |
| /// It handles the mapping of C++ constructs like namespaces, records, |
| /// functions, and enums to their JSON equivalents, enabling downstream tools |
| /// to consume the structured documentation data. |
| /// |
| //===----------------------------------------------------------------------===// |
| #include "Generators.h" |
| #include "clang/Basic/Specifiers.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/Support/JSON.h" |
| |
| using namespace llvm; |
| using namespace llvm::json; |
| using namespace clang::doc; |
| |
| template <typename Container, typename SerializationFunc> |
| static void serializeArray( |
| const Container &Records, Object &Obj, const StringRef Key, |
| SerializationFunc SerializeInfo, const StringRef EndKey = "End", |
| function_ref<void(Object &)> UpdateJson = [](Object &Obj) {}); |
| |
| // TODO(issue URL): Wrapping logic for HTML should probably use a more |
| // sophisticated heuristic than number of parameters. |
| constexpr static unsigned getMaxParamWrapLimit() { return 2; } |
| |
| namespace { |
| typedef std::function<void(const Reference &, Object &)> ReferenceFunc; |
| |
| class JSONGenerator : public Generator { |
| json::Object serializeLocation(const Location &Loc); |
| void serializeCommonAttributes(const Info &I, json::Object &Obj); |
| void serializeCommonChildren( |
| const ScopeChildren &Children, json::Object &Obj, |
| std::optional<ReferenceFunc> MDReferenceLambda = std::nullopt); |
| void serializeContexts(Info *I, llvm::StringMap<Info *> &Infos); |
| void serializeInfo(const ConstraintInfo &I, Object &Obj); |
| void serializeInfo(const TemplateInfo &Template, Object &Obj); |
| void serializeInfo(const ConceptInfo &I, Object &Obj); |
| void serializeInfo(const TypeInfo &I, Object &Obj); |
| void serializeInfo(const FieldTypeInfo &I, Object &Obj); |
| void serializeInfo(const FunctionInfo &F, json::Object &Obj); |
| void serializeInfo(const EnumValueInfo &I, Object &Obj); |
| void serializeInfo(const EnumInfo &I, json::Object &Obj); |
| void serializeInfo(const TypedefInfo &I, json::Object &Obj); |
| void serializeInfo(const BaseRecordInfo &I, Object &Obj); |
| void serializeInfo(const FriendInfo &I, Object &Obj); |
| void serializeInfo(const RecordInfo &I, json::Object &Obj); |
| void serializeInfo(const VarInfo &I, json::Object &Obj); |
| void serializeInfo(const NamespaceInfo &I, json::Object &Obj); |
| SmallString<16> determineFileName(Info *I, SmallString<128> &Path); |
| Error serializeIndex(StringRef RootDir); |
| void generateContext(const Info &I, Object &Obj); |
| void serializeReference(const Reference &Ref, Object &ReferenceObj); |
| Error serializeAllFiles(const ClangDocContext &CDCtx, StringRef RootDir); |
| void serializeMDReference(const Reference &Ref, Object &ReferenceObj, |
| StringRef BasePath); |
| |
| void serializeClassSpecializations(SymbolID ClassUSR, Object &ReferenceObj); |
| |
| // Convenience lambdas to pass to serializeArray. |
| auto serializeInfoLambda() { |
| return [this](const auto &Info, Object &Object) { |
| serializeInfo(Info, Object); |
| }; |
| } |
| auto serializeReferenceLambda() { |
| return [this](const auto &Ref, Object &Object) { |
| serializeReference(Ref, Object); |
| }; |
| } |
| |
| llvm::DenseMap<const Info *, SmallVector<Context, 4>> ContextsMap; |
| llvm::StringMap<Info *> *Infos = nullptr; |
| const ClangDocContext *CDCtx; |
| bool Markdown; |
| |
| public: |
| static const char *Format; |
| |
| Error generateDocumentation(StringRef RootDir, llvm::StringMap<Info *> Infos, |
| const ClangDocContext &CDCtx, |
| std::string DirName) override; |
| Error createResources(ClangDocContext &CDCtx) override; |
| // FIXME: Once legacy generators are removed, we can refactor the Generator |
| // interface to sto passing CDCtx here since we hold a pointer to it. |
| Error generateDocForInfo(Info *I, llvm::raw_ostream &OS, |
| const ClangDocContext &CDCtx) override; |
| }; |
| |
| } // namespace |
| |
| const char *JSONGenerator::Format = "json"; |
| |
| static void insertNonEmpty(StringRef Key, StringRef Value, Object &Obj) { |
| if (!Value.empty()) |
| Obj[Key] = Value; |
| } |
| |
| static std::string infoTypeToString(InfoType IT) { |
| switch (IT) { |
| case InfoType::IT_default: |
| return "default"; |
| case InfoType::IT_namespace: |
| return "namespace"; |
| case InfoType::IT_record: |
| return "record"; |
| case InfoType::IT_function: |
| return "function"; |
| case InfoType::IT_enum: |
| return "enum"; |
| case InfoType::IT_typedef: |
| return "typedef"; |
| case InfoType::IT_concept: |
| return "concept"; |
| case InfoType::IT_variable: |
| return "variable"; |
| case InfoType::IT_friend: |
| return "friend"; |
| } |
| llvm_unreachable("Unknown InfoType encountered."); |
| } |
| |
| json::Object JSONGenerator::serializeLocation(const Location &Loc) { |
| Object LocationObj = Object(); |
| LocationObj["LineNumber"] = Loc.StartLineNumber; |
| LocationObj["Filename"] = Loc.Filename; |
| |
| if (!Loc.IsFileInRootDir || !CDCtx->RepositoryUrl) |
| return LocationObj; |
| SmallString<128> FileURL(*CDCtx->RepositoryUrl); |
| sys::path::append(FileURL, sys::path::Style::posix, Loc.Filename); |
| |
| std::string LinePrefix; |
| if (!CDCtx->RepositoryLinePrefix) |
| LinePrefix = "#L"; |
| else |
| LinePrefix = *CDCtx->RepositoryLinePrefix; |
| |
| FileURL += LinePrefix + std::to_string(Loc.StartLineNumber); |
| LocationObj["FileURL"] = FileURL; |
| return LocationObj; |
| } |
| |
| /// Insert comments into a key in the Description object. |
| /// |
| /// \param Comment Either an Object or Array, depending on the comment type |
| /// \param Key The type (Brief, Code, etc.) of comment to be inserted |
| static void insertComment(Object &Description, json::Value &Comment, |
| StringRef Key) { |
| // The comment has a Children array for the actual text, with meta attributes |
| // alongside it in the Object. |
| if (auto *Obj = Comment.getAsObject()) { |
| if (auto *Children = Obj->getArray("Children"); |
| Children && Children->empty()) |
| return; |
| } |
| // The comment is just an array of text comments. |
| else if (auto *Array = Comment.getAsArray(); Array && Array->empty()) { |
| return; |
| } |
| |
| auto DescriptionIt = Description.find(Key); |
| |
| if (DescriptionIt == Description.end()) { |
| auto CommentsArray = json::Array(); |
| CommentsArray.push_back(Comment); |
| Description[Key] = std::move(CommentsArray); |
| Description["Has" + Key.str()] = true; |
| } else { |
| DescriptionIt->getSecond().getAsArray()->push_back(Comment); |
| } |
| } |
| |
| /// Takes the nested "Children" array from a comment Object. |
| /// |
| /// \return a json::Array of comments, possible json::Value::Kind::Null |
| static json::Value extractTextComments(Object *ParagraphComment) { |
| if (!ParagraphComment) |
| return json::Value(nullptr); |
| json::Value *Children = ParagraphComment->get("Children"); |
| if (!Children) |
| return json::Value(nullptr); |
| auto ChildrenArray = *Children->getAsArray(); |
| auto ChildrenIt = ChildrenArray.begin(); |
| while (ChildrenIt != ChildrenArray.end()) { |
| auto *ChildObj = ChildrenIt->getAsObject(); |
| assert(ChildObj && "Invalid JSON object in Comment"); |
| auto TextComment = ChildObj->getString("TextComment"); |
| if (!TextComment || TextComment->empty()) { |
| ChildrenIt = ChildrenArray.erase(ChildrenIt); |
| continue; |
| } |
| ++ChildrenIt; |
| } |
| return ChildrenArray; |
| } |
| |
| static json::Value extractVerbatimComments(json::Array VerbatimLines) { |
| json::Value TextArray = json::Array(); |
| auto &TextArrayRef = *TextArray.getAsArray(); |
| for (auto &Line : VerbatimLines) |
| TextArrayRef.push_back(*Line.getAsObject() |
| ->get("VerbatimBlockLineComment") |
| ->getAsObject() |
| ->get("Text")); |
| |
| return TextArray; |
| } |
| |
| static Object serializeComment(const CommentInfo &I, Object &Description) { |
| // taken from PR #142273 |
| Object Obj = Object(); |
| |
| json::Value ChildVal = Object(); |
| Object &Child = *ChildVal.getAsObject(); |
| |
| json::Value ChildArr = Array(); |
| auto &CARef = *ChildArr.getAsArray(); |
| CARef.reserve(I.Children.size()); |
| for (const auto &C : I.Children) |
| CARef.emplace_back(serializeComment(C, Description)); |
| |
| switch (I.Kind) { |
| case CommentKind::CK_TextComment: { |
| if (!I.Text.empty()) |
| Obj.insert({commentKindToString(I.Kind), I.Text}); |
| return Obj; |
| } |
| |
| case CommentKind::CK_BlockCommandComment: { |
| auto TextCommentsArray = extractTextComments(CARef.front().getAsObject()); |
| if (I.Name == "brief") |
| insertComment(Description, TextCommentsArray, "BriefComments"); |
| else if (I.Name == "return") |
| insertComment(Description, TextCommentsArray, "ReturnComments"); |
| else if (I.Name == "throws" || I.Name == "throw") { |
| json::Value ThrowsVal = Object(); |
| auto &ThrowsObj = *ThrowsVal.getAsObject(); |
| ThrowsObj["Exception"] = I.Args.front(); |
| ThrowsObj["Children"] = TextCommentsArray; |
| insertComment(Description, ThrowsVal, "ThrowsComments"); |
| } |
| return Obj; |
| } |
| |
| case CommentKind::CK_InlineCommandComment: { |
| json::Value ArgsArr = Array(); |
| auto &ARef = *ArgsArr.getAsArray(); |
| ARef.reserve(I.Args.size()); |
| for (const auto &Arg : I.Args) |
| ARef.emplace_back(Arg); |
| Child.insert({"Command", I.Name}); |
| Child.insert({"Args", ArgsArr}); |
| Child.insert({"Children", ChildArr}); |
| Obj.insert({commentKindToString(I.Kind), ChildVal}); |
| return Obj; |
| } |
| |
| case CommentKind::CK_ParamCommandComment: |
| case CommentKind::CK_TParamCommandComment: { |
| Child.insert({"ParamName", I.ParamName}); |
| Child.insert({"Direction", I.Direction}); |
| Child.insert({"Explicit", I.Explicit}); |
| auto TextCommentsArray = extractTextComments(CARef.front().getAsObject()); |
| Child.insert({"Children", TextCommentsArray}); |
| if (I.Kind == CommentKind::CK_ParamCommandComment) |
| insertComment(Description, ChildVal, "ParamComments"); |
| if (I.Kind == CommentKind::CK_TParamCommandComment) |
| insertComment(Description, ChildVal, "TParamComments"); |
| return Obj; |
| } |
| |
| case CommentKind::CK_VerbatimBlockComment: { |
| if (I.CloseName == "endcode") { |
| // We don't support \code language specification |
| auto TextCommentsArray = extractVerbatimComments(CARef); |
| insertComment(Description, TextCommentsArray, "CodeComments"); |
| } else if (I.CloseName == "endverbatim") |
| insertComment(Description, ChildVal, "VerbatimComments"); |
| return Obj; |
| } |
| |
| case CommentKind::CK_VerbatimBlockLineComment: |
| case CommentKind::CK_VerbatimLineComment: { |
| Child.insert({"Text", I.Text}); |
| Child.insert({"Children", ChildArr}); |
| Obj.insert({commentKindToString(I.Kind), ChildVal}); |
| return Obj; |
| } |
| |
| case CommentKind::CK_HTMLStartTagComment: { |
| json::Value AttrKeysArray = json::Array(); |
| json::Value AttrValuesArray = json::Array(); |
| auto &KeyArr = *AttrKeysArray.getAsArray(); |
| auto &ValArr = *AttrValuesArray.getAsArray(); |
| KeyArr.reserve(I.AttrKeys.size()); |
| ValArr.reserve(I.AttrValues.size()); |
| for (const auto &K : I.AttrKeys) |
| KeyArr.emplace_back(K); |
| for (const auto &V : I.AttrValues) |
| ValArr.emplace_back(V); |
| Child.insert({"Name", I.Name}); |
| Child.insert({"SelfClosing", I.SelfClosing}); |
| Child.insert({"AttrKeys", AttrKeysArray}); |
| Child.insert({"AttrValues", AttrValuesArray}); |
| Child.insert({"Children", ChildArr}); |
| Obj.insert({commentKindToString(I.Kind), ChildVal}); |
| return Obj; |
| } |
| |
| case CommentKind::CK_HTMLEndTagComment: { |
| Child.insert({"Name", I.Name}); |
| Child.insert({"Children", ChildArr}); |
| Obj.insert({commentKindToString(I.Kind), ChildVal}); |
| return Obj; |
| } |
| |
| case CommentKind::CK_FullComment: |
| case CommentKind::CK_ParagraphComment: { |
| Child.insert({"Children", ChildArr}); |
| Child["ParagraphComment"] = true; |
| return Child; |
| } |
| |
| case CommentKind::CK_Unknown: { |
| Obj.insert({commentKindToString(I.Kind), I.Text}); |
| return Obj; |
| } |
| } |
| llvm_unreachable("Unknown comment kind encountered."); |
| } |
| |
| /// Creates Contexts for namespaces and records to allow for navigation. |
| void JSONGenerator::generateContext(const Info &I, Object &Obj) { |
| Obj["Contexts"] = json::Array(); |
| Obj["HasContexts"] = true; |
| |
| auto It = ContextsMap.find(&I); |
| if (It == ContextsMap.end() || It->second.empty()) |
| return; |
| |
| auto &ContextArrayRef = *Obj["Contexts"].getAsArray(); |
| const auto &Contexts = It->second; |
| ContextArrayRef.reserve(Contexts.size()); |
| |
| std::string CurrentRelativePath; |
| bool PreviousRecord = false; |
| for (const auto &Current : Contexts) { |
| json::Value ContextVal = Object(); |
| Object &Context = *ContextVal.getAsObject(); |
| serializeReference(Current, Context); |
| |
| if (ContextArrayRef.empty() && I.IT == InfoType::IT_record) { |
| if (Current.DocumentationFileName == "index") { |
| // If the record's immediate context is a namespace, then the |
| // "index.html" is in the same directory. |
| PreviousRecord = false; |
| Context["RelativePath"] = "./"; |
| } else { |
| // If the immediate context is a record, then the file is one level |
| // above |
| PreviousRecord = true; |
| CurrentRelativePath += "../"; |
| Context["RelativePath"] = CurrentRelativePath; |
| } |
| ContextArrayRef.push_back(ContextVal); |
| continue; |
| } |
| |
| if (PreviousRecord && (Current.DocumentationFileName == "index")) { |
| // If the previous Context was a record then we already went up a level, |
| // so the current namespace index is in the same directory. |
| PreviousRecord = false; |
| } else if (Current.DocumentationFileName != "index") { |
| // If the current Context is a record but the previous wasn't a record, |
| // then the namespace index is located one level above. |
| PreviousRecord = true; |
| CurrentRelativePath += "../"; |
| } else { |
| // The current Context is a namespace and so was the previous Context. |
| PreviousRecord = false; |
| CurrentRelativePath += "../"; |
| // If this namespace is the global namespace, then its documentation |
| // name needs to be changed to link correctly. |
| if (Current.QualName == "GlobalNamespace" && Current.RelativePath != "./") |
| Context["DocumentationFileName"] = |
| SmallString<16>("GlobalNamespace/index"); |
| } |
| Context["RelativePath"] = CurrentRelativePath; |
| ContextArrayRef.insert(ContextArrayRef.begin(), ContextVal); |
| } |
| |
| ContextArrayRef.back().getAsObject()->insert({"End", true}); |
| } |
| |
| static void serializeDescription(const DocList<CommentInfo> &Description, |
| json::Object &Obj, StringRef Key = "") { |
| if (Description.empty()) |
| return; |
| |
| // Skip straight to the FullComment's children |
| auto &Comments = Description.front()->Children; |
| Object DescriptionObj = Object(); |
| for (const auto &CommentInfo : Comments) { |
| json::Value Comment = serializeComment(CommentInfo, DescriptionObj); |
| // if a ParagraphComment is returned, then it is a top-level comment that |
| // needs to be inserted manually. |
| if (auto *ParagraphComment = Comment.getAsObject(); |
| ParagraphComment->get("ParagraphComment")) { |
| auto TextCommentsArray = extractTextComments(ParagraphComment); |
| if (TextCommentsArray.kind() == json::Value::Null || |
| TextCommentsArray.getAsArray()->empty()) |
| continue; |
| insertComment(DescriptionObj, TextCommentsArray, "ParagraphComments"); |
| } |
| } |
| Obj["Description"] = std::move(DescriptionObj); |
| if (!Key.empty()) |
| Obj[Key] = true; |
| } |
| |
| void JSONGenerator::serializeCommonAttributes(const Info &I, |
| json::Object &Obj) { |
| insertNonEmpty("Name", I.Name, Obj); |
| if (!(I.USR == GlobalNamespaceID)) |
| Obj["USR"] = toHex(toStringRef(I.USR)); |
| Obj["InfoType"] = infoTypeToString(I.IT); |
| // Conditionally insert fields. |
| // Empty properties are omitted because Mustache templates use existence |
| // to conditionally render content. |
| insertNonEmpty("DocumentationFileName", I.DocumentationFileName, Obj); |
| insertNonEmpty("Path", I.Path, Obj); |
| |
| if (!I.Namespace.empty()) { |
| Obj["Namespace"] = json::Array(); |
| for (const auto &NS : I.Namespace) |
| Obj["Namespace"].getAsArray()->push_back(NS.Name); |
| } |
| |
| serializeDescription(I.Description, Obj); |
| |
| // Namespaces aren't SymbolInfos, so they dont have a DefLoc |
| if (I.IT != InfoType::IT_namespace) { |
| const auto *Symbol = cast<SymbolInfo>(&I); |
| if (Symbol->DefLoc) |
| Obj["Location"] = serializeLocation(Symbol->DefLoc.value()); |
| } |
| |
| auto It = ContextsMap.find(&I); |
| if (It != ContextsMap.end() && !It->second.empty()) |
| generateContext(I, Obj); |
| } |
| |
| static auto SerializeTemplateParam = [](const TemplateParamInfo &Param, |
| Object &JsonObj) { |
| JsonObj["Param"] = Param.Contents; |
| }; |
| |
| static void serializeTemplateSpecialization(TemplateInfo Template, |
| Object &TemplateObj) { |
| json::Value TemplateSpecializationVal = Object(); |
| auto &TemplateSpecializationObj = *TemplateSpecializationVal.getAsObject(); |
| TemplateSpecializationObj["SpecializationOf"] = |
| toHex(toStringRef(Template.Specialization->SpecializationOf)); |
| if (!Template.Specialization->Params.empty()) { |
| bool VerticalDisplay = |
| Template.Specialization->Params.size() > getMaxParamWrapLimit(); |
| serializeArray(Template.Specialization->Params, TemplateSpecializationObj, |
| "Parameters", SerializeTemplateParam, "SpecParamEnd", |
| [VerticalDisplay](Object &JsonObj) { |
| JsonObj["VerticalDisplay"] = VerticalDisplay; |
| }); |
| } |
| TemplateObj["Specialization"] = TemplateSpecializationVal; |
| } |
| |
| void JSONGenerator::serializeClassSpecializations(SymbolID ClassUSR, |
| Object &ReferenceObj) { |
| if (!Infos) |
| return; |
| auto *Class = Infos->lookup(toHex(ClassUSR)); |
| if (!Class || Class->IT != InfoType::IT_record) |
| return; |
| RecordInfo *ClassInfo = cast<RecordInfo>(Class); |
| if (!ClassInfo->Template || !ClassInfo->Template->Specialization) |
| return; |
| serializeTemplateSpecialization(ClassInfo->Template.value(), ReferenceObj); |
| } |
| |
| void JSONGenerator::serializeReference(const Reference &Ref, |
| Object &ReferenceObj) { |
| insertNonEmpty("Path", Ref.Path, ReferenceObj); |
| ReferenceObj["Name"] = Ref.Name; |
| ReferenceObj["QualName"] = Ref.QualName; |
| ReferenceObj["USR"] = toHex(toStringRef(Ref.USR)); |
| if (!Ref.DocumentationFileName.empty()) { |
| ReferenceObj["DocumentationFileName"] = Ref.DocumentationFileName; |
| |
| // If the reference is a nested class it will be put into a folder named |
| // after the parent class. We can get that name from the path's stem. |
| if (Ref.Path != "GlobalNamespace" && !Ref.Path.empty()) |
| ReferenceObj["PathStem"] = sys::path::stem(Ref.Path); |
| } |
| } |
| |
| void JSONGenerator::serializeMDReference(const Reference &Ref, |
| Object &ReferenceObj, |
| StringRef BasePath) { |
| serializeReference(Ref, ReferenceObj); |
| SmallString<64> Path = Ref.getRelativeFilePath(BasePath); |
| sys::path::native(Path, sys::path::Style::posix); |
| sys::path::append(Path, sys::path::Style::posix, |
| Ref.getFileBaseName() + ".md"); |
| ReferenceObj["BasePath"] = Path; |
| } |
| |
| // Although namespaces and records both have ScopeChildren, they serialize them |
| // differently. Only enums, records, and typedefs are handled here. |
| void JSONGenerator::serializeCommonChildren( |
| const ScopeChildren &Children, json::Object &Obj, |
| std::optional<ReferenceFunc> MDReferenceLambda) { |
| if (!Children.Enums.empty()) { |
| serializeArray(Children.Enums, Obj, "Enums", serializeInfoLambda()); |
| Obj["HasEnums"] = true; |
| } |
| |
| if (!Children.Typedefs.empty()) { |
| serializeArray(Children.Typedefs, Obj, "Typedefs", serializeInfoLambda()); |
| Obj["HasTypedefs"] = true; |
| } |
| |
| if (!Children.Records.empty()) { |
| ReferenceFunc BaseFunc = MDReferenceLambda ? MDReferenceLambda.value() |
| : serializeReferenceLambda(); |
| |
| ReferenceFunc SerializeReferenceFunc = |
| [this, BaseFunc](const Reference &Ref, Object &Object) { |
| BaseFunc(Ref, Object); |
| serializeClassSpecializations(Ref.USR, Object); |
| }; |
| serializeArray(Children.Records, Obj, "Records", SerializeReferenceFunc); |
| Obj["HasRecords"] = true; |
| } |
| } |
| |
| template <typename Container, typename SerializationFunc> |
| static void serializeArray(const Container &Records, Object &Obj, StringRef Key, |
| SerializationFunc SerializeInfo, StringRef EndKey, |
| function_ref<void(Object &)> UpdateJson) { |
| json::Value RecordsArray = Array(); |
| auto &RecordsArrayRef = *RecordsArray.getAsArray(); |
| RecordsArrayRef.reserve(Records.size()); |
| size_t Index = 0; |
| size_t Size = Records.size(); |
| for (const auto &Item : Records) { |
| json::Value ItemVal = Object(); |
| auto &ItemObj = *ItemVal.getAsObject(); |
| SerializeInfo(Item, ItemObj); |
| if (Index == Size - 1) |
| ItemObj[EndKey] = true; |
| RecordsArrayRef.push_back(ItemVal); |
| ++Index; |
| } |
| Obj[Key] = RecordsArray; |
| UpdateJson(Obj); |
| } |
| |
| void JSONGenerator::serializeInfo(const ConstraintInfo &I, Object &Obj) { |
| serializeReference(I.ConceptRef, Obj); |
| Obj["Expression"] = I.ConstraintExpr; |
| } |
| |
| void JSONGenerator::serializeInfo(const TemplateInfo &Template, Object &Obj) { |
| json::Value TemplateVal = Object(); |
| auto &TemplateObj = *TemplateVal.getAsObject(); |
| |
| if (Template.Specialization) |
| serializeTemplateSpecialization(Template, TemplateObj); |
| |
| if (!Template.Params.empty()) { |
| bool VerticalDisplay = Template.Params.size() > getMaxParamWrapLimit(); |
| ::serializeArray(Template.Params, TemplateObj, "Parameters", |
| SerializeTemplateParam, "End", |
| [VerticalDisplay](Object &JsonObj) { |
| JsonObj["VerticalDisplay"] = VerticalDisplay; |
| }); |
| } |
| |
| if (!Template.Constraints.empty()) |
| serializeArray(Template.Constraints, TemplateObj, "Constraints", |
| serializeInfoLambda()); |
| |
| Obj["Template"] = TemplateVal; |
| } |
| |
| void JSONGenerator::serializeInfo(const ConceptInfo &I, Object &Obj) { |
| serializeCommonAttributes(I, Obj); |
| Obj["IsType"] = I.IsType; |
| Obj["ConstraintExpression"] = I.ConstraintExpression; |
| serializeInfo(I.Template, Obj); |
| } |
| |
| void JSONGenerator::serializeInfo(const TypeInfo &I, Object &Obj) { |
| Obj["Name"] = I.Type.Name; |
| Obj["QualName"] = I.Type.QualName; |
| Obj["USR"] = toHex(toStringRef(I.Type.USR)); |
| Obj["IsTemplate"] = I.IsTemplate; |
| Obj["IsBuiltIn"] = I.IsBuiltIn; |
| } |
| |
| void JSONGenerator::serializeInfo(const FieldTypeInfo &I, Object &Obj) { |
| Obj["Name"] = I.Name; |
| insertNonEmpty("DefaultValue", I.DefaultValue, Obj); |
| json::Value ReferenceVal = Object(); |
| Object &ReferenceObj = *ReferenceVal.getAsObject(); |
| serializeReference(I.Type, ReferenceObj); |
| Obj["Type"] = ReferenceVal; |
| } |
| |
| void JSONGenerator::serializeInfo(const FunctionInfo &F, json::Object &Obj) { |
| serializeCommonAttributes(F, Obj); |
| Obj["IsStatic"] = F.IsStatic; |
| |
| auto ReturnTypeObj = Object(); |
| serializeInfo(F.ReturnType, ReturnTypeObj); |
| Obj["ReturnType"] = std::move(ReturnTypeObj); |
| |
| if (!F.Params.empty()) { |
| const bool VerticalDisplay = F.Params.size() > getMaxParamWrapLimit(); |
| serializeArray(F.Params, Obj, "Params", serializeInfoLambda(), "ParamEnd", |
| [VerticalDisplay](Object &JsonObj) { |
| JsonObj["VerticalDisplay"] = VerticalDisplay; |
| }); |
| } |
| |
| if (F.Template) |
| serializeInfo(F.Template.value(), Obj); |
| } |
| |
| void JSONGenerator::serializeInfo(const EnumValueInfo &I, Object &Obj) { |
| Obj["Name"] = I.Name; |
| if (!I.ValueExpr.empty()) |
| Obj["ValueExpr"] = I.ValueExpr; |
| else |
| Obj["Value"] = I.Value; |
| |
| serializeDescription(I.Description, Obj, "HasEnumMemberComments"); |
| } |
| |
| void JSONGenerator::serializeInfo(const EnumInfo &I, json::Object &Obj) { |
| serializeCommonAttributes(I, Obj); |
| Obj["Scoped"] = I.Scoped; |
| |
| if (I.BaseType) { |
| json::Value BaseTypeVal = Object(); |
| auto &BaseTypeObj = *BaseTypeVal.getAsObject(); |
| BaseTypeObj["Name"] = I.BaseType->Type.Name; |
| BaseTypeObj["QualName"] = I.BaseType->Type.QualName; |
| BaseTypeObj["USR"] = toHex(toStringRef(I.BaseType->Type.USR)); |
| Obj["BaseType"] = BaseTypeVal; |
| } |
| |
| if (!I.Members.empty()) { |
| for (const auto &Member : I.Members) { |
| if (!Member.Description.empty()) { |
| Obj["HasComments"] = true; |
| break; |
| } |
| } |
| serializeArray(I.Members, Obj, "Members", serializeInfoLambda()); |
| } |
| } |
| |
| void JSONGenerator::serializeInfo(const TypedefInfo &I, json::Object &Obj) { |
| serializeCommonAttributes(I, Obj); |
| Obj["TypeDeclaration"] = I.TypeDeclaration; |
| Obj["IsUsing"] = I.IsUsing; |
| json::Value TypeVal = Object(); |
| auto &TypeObj = *TypeVal.getAsObject(); |
| serializeInfo(I.Underlying, TypeObj); |
| Obj["Underlying"] = TypeVal; |
| if (I.Template) |
| serializeInfo(I.Template.value(), Obj); |
| } |
| |
| void JSONGenerator::serializeInfo(const BaseRecordInfo &I, Object &Obj) { |
| serializeInfo(static_cast<const RecordInfo &>(I), Obj); |
| Obj["IsVirtual"] = I.IsVirtual; |
| Obj["Access"] = getAccessSpelling(I.Access); |
| Obj["IsParent"] = I.IsParent; |
| } |
| |
| void JSONGenerator::serializeInfo(const FriendInfo &I, Object &Obj) { |
| auto FriendRef = Object(); |
| serializeReference(I.Ref, FriendRef); |
| Obj["Reference"] = std::move(FriendRef); |
| Obj["IsClass"] = I.IsClass; |
| if (I.Template) |
| serializeInfo(I.Template.value(), Obj); |
| if (!I.Params.empty()) |
| serializeArray(I.Params, Obj, "Params", serializeInfoLambda()); |
| if (I.ReturnType) { |
| auto ReturnTypeObj = Object(); |
| serializeInfo(I.ReturnType.value(), ReturnTypeObj); |
| Obj["ReturnType"] = std::move(ReturnTypeObj); |
| } |
| serializeCommonAttributes(I, Obj); |
| } |
| |
| static void insertArray(Object &Obj, json::Value &Array, StringRef Key) { |
| Obj[Key] = Array; |
| Obj["Has" + Key.str()] = true; |
| } |
| |
| void JSONGenerator::serializeInfo(const RecordInfo &I, json::Object &Obj) { |
| serializeCommonAttributes(I, Obj); |
| Obj["TagType"] = getTagType(I.TagType); |
| Obj["IsTypedef"] = I.IsTypeDef; |
| Obj["MangledName"] = I.MangledName; |
| |
| if (!I.Children.Functions.empty()) { |
| json::Value PubFunctionsArray = Array(); |
| json::Array &PubFunctionsArrayRef = *PubFunctionsArray.getAsArray(); |
| json::Value ProtFunctionsArray = Array(); |
| json::Array &ProtFunctionsArrayRef = *ProtFunctionsArray.getAsArray(); |
| |
| for (const auto &Function : I.Children.Functions) { |
| json::Value FunctionVal = Object(); |
| auto &FunctionObj = *FunctionVal.getAsObject(); |
| serializeInfo(Function, FunctionObj); |
| clang::AccessSpecifier Access = Function->Access; |
| if (Access == clang::AccessSpecifier::AS_public) |
| PubFunctionsArrayRef.push_back(FunctionVal); |
| else if (Access == clang::AccessSpecifier::AS_protected) |
| ProtFunctionsArrayRef.push_back(FunctionVal); |
| } |
| |
| if (!PubFunctionsArrayRef.empty()) |
| insertArray(Obj, PubFunctionsArray, "PublicMethods"); |
| if (!ProtFunctionsArrayRef.empty()) |
| insertArray(Obj, ProtFunctionsArray, "ProtectedMethods"); |
| } |
| |
| if (!I.Members.empty()) { |
| Obj["HasMembers"] = true; |
| json::Value PublicMembersArray = Array(); |
| json::Array &PubMembersArrayRef = *PublicMembersArray.getAsArray(); |
| json::Value ProtectedMembersArray = Array(); |
| json::Array &ProtMembersArrayRef = *ProtectedMembersArray.getAsArray(); |
| json::Value PrivateMembersArray = Array(); |
| json::Array &PrivateMembersArrayRef = *PrivateMembersArray.getAsArray(); |
| |
| for (const MemberTypeInfo &Member : I.Members) { |
| json::Value MemberVal = Object(); |
| auto &MemberObj = *MemberVal.getAsObject(); |
| MemberObj["Name"] = Member.Name; |
| MemberObj["Type"] = Member.Type.Name; |
| MemberObj["IsStatic"] = Member.IsStatic; |
| |
| if (Member.Access == clang::AccessSpecifier::AS_public) |
| PubMembersArrayRef.push_back(MemberVal); |
| else if (Member.Access == clang::AccessSpecifier::AS_protected) |
| ProtMembersArrayRef.push_back(MemberVal); |
| else if (Member.Access == clang::AccessSpecifier::AS_private) |
| PrivateMembersArrayRef.push_back(MemberVal); |
| } |
| |
| if (!PubMembersArrayRef.empty()) |
| insertArray(Obj, PublicMembersArray, "PublicMembers"); |
| if (!ProtMembersArrayRef.empty()) |
| insertArray(Obj, ProtectedMembersArray, "ProtectedMembers"); |
| if (!PrivateMembersArrayRef.empty()) |
| insertArray(Obj, PrivateMembersArray, "PrivateMembers"); |
| } |
| |
| if (!I.Bases.empty()) |
| serializeArray(I.Bases, Obj, "Bases", serializeInfoLambda()); |
| |
| if (!I.Parents.empty()) { |
| serializeArray(I.Parents, Obj, "Parents", serializeReferenceLambda()); |
| Obj["HasParents"] = true; |
| } |
| |
| if (!I.VirtualParents.empty()) { |
| serializeArray(I.VirtualParents, Obj, "VirtualParents", |
| serializeReferenceLambda()); |
| Obj["HasVirtualParents"] = true; |
| } |
| |
| if (I.Template) |
| serializeInfo(I.Template.value(), Obj); |
| |
| if (!I.Friends.empty()) { |
| serializeArray(I.Friends, Obj, "Friends", serializeInfoLambda()); |
| Obj["HasFriends"] = true; |
| } |
| |
| serializeCommonChildren(I.Children, Obj); |
| } |
| |
| void JSONGenerator::serializeInfo(const VarInfo &I, json::Object &Obj) { |
| serializeCommonAttributes(I, Obj); |
| Obj["IsStatic"] = I.IsStatic; |
| auto TypeObj = Object(); |
| serializeInfo(I.Type, TypeObj); |
| Obj["Type"] = std::move(TypeObj); |
| } |
| |
| void JSONGenerator::serializeInfo(const NamespaceInfo &I, json::Object &Obj) { |
| serializeCommonAttributes(I, Obj); |
| if (I.USR == GlobalNamespaceID) |
| Obj["Name"] = "Global Namespace"; |
| |
| if (!I.Children.Functions.empty()) { |
| serializeArray(I.Children.Functions, Obj, "Functions", |
| serializeInfoLambda()); |
| Obj["HasFunctions"] = true; |
| } |
| |
| if (!I.Children.Concepts.empty()) { |
| serializeArray(I.Children.Concepts, Obj, "Concepts", serializeInfoLambda()); |
| Obj["HasConcepts"] = true; |
| } |
| |
| if (!I.Children.Variables.empty()) { |
| serializeArray(I.Children.Variables, Obj, "Variables", |
| serializeInfoLambda()); |
| Obj["HasVariables"] = true; |
| } |
| |
| ReferenceFunc SerializeReferenceFunc; |
| if (Markdown) { |
| SmallString<64> BasePath = I.getRelativeFilePath(""); |
| // serializeCommonChildren doesn't accept Infos, so this lambda needs to be |
| // created here. To avoid making serializeCommonChildren a template, this |
| // lambda is an std::function |
| SerializeReferenceFunc = [this, BasePath](const Reference &Ref, |
| Object &Object) { |
| serializeMDReference(Ref, Object, BasePath); |
| }; |
| serializeCommonChildren(I.Children, Obj, SerializeReferenceFunc); |
| } else { |
| SerializeReferenceFunc = serializeReferenceLambda(); |
| serializeCommonChildren(I.Children, Obj); |
| } |
| |
| if (!I.Children.Namespaces.empty()) { |
| serializeArray(I.Children.Namespaces, Obj, "Namespaces", |
| SerializeReferenceFunc); |
| Obj["HasNamespaces"] = true; |
| } |
| } |
| |
| SmallString<16> JSONGenerator::determineFileName(Info *I, |
| SmallString<128> &Path) { |
| SmallString<16> FileName; |
| if (I->IT == InfoType::IT_record) { |
| auto *RecordSymbolInfo = cast<SymbolInfo>(I); |
| FileName = RecordSymbolInfo->MangledName; |
| } else if (I->IT == InfoType::IT_namespace) { |
| FileName = "index"; |
| } else |
| FileName = I->Name; |
| sys::path::append(Path, FileName + ".json"); |
| return FileName; |
| } |
| |
| /// \param CDCtxIndex Passed by copy since clang-doc's context is passed to the |
| /// generator as `const` |
| static std::vector<Index> preprocessCDCtxIndex(Index CDCtxIndex) { |
| CDCtxIndex.sort(); |
| std::vector<Index> Processed; |
| Processed.reserve(CDCtxIndex.Children.size()); |
| for (const auto *Idx : CDCtxIndex.getSortedChildren()) { |
| Index NewIdx = *Idx; |
| SmallString<128> NewPath(NewIdx.getRelativeFilePath("")); |
| sys::path::native(NewPath, sys::path::Style::posix); |
| sys::path::append(NewPath, sys::path::Style::posix, |
| NewIdx.getFileBaseName() + ".md"); |
| NewIdx.Path = internString(NewPath); |
| Processed.push_back(NewIdx); |
| } |
| |
| return Processed; |
| } |
| |
| /// Serialize ClangDocContext's Index for Markdown output |
| Error JSONGenerator::serializeAllFiles(const ClangDocContext &CDCtx, |
| StringRef RootDir) { |
| json::Value ObjVal = Object(); |
| Object &Obj = *ObjVal.getAsObject(); |
| std::vector<Index> IndexCopy = preprocessCDCtxIndex(CDCtx.Idx); |
| serializeArray(IndexCopy, Obj, "Index", serializeReferenceLambda()); |
| SmallString<128> Path; |
| sys::path::append(Path, RootDir, "json", "all_files.json"); |
| std::error_code FileErr; |
| raw_fd_ostream RootOS(Path, FileErr, sys::fs::OF_Text); |
| if (FileErr) |
| return createFileError("cannot open file " + Path, FileErr); |
| RootOS << llvm::formatv("{0:2}", ObjVal); |
| return Error::success(); |
| } |
| |
| // Creates a JSON file above the global namespace directory. |
| // An index can be used to create the top-level HTML index page or the Markdown |
| // index file. |
| Error JSONGenerator::serializeIndex(StringRef RootDir) { |
| if (CDCtx->Idx.Children.empty()) |
| return Error::success(); |
| |
| json::Value ObjVal = Object(); |
| Object &Obj = *ObjVal.getAsObject(); |
| insertNonEmpty("ProjectName", CDCtx->ProjectName, Obj); |
| |
| auto IndexCopy = CDCtx->Idx; |
| IndexCopy.sort(); |
| json::Value IndexArray = json::Array(); |
| auto &IndexArrayRef = *IndexArray.getAsArray(); |
| |
| if (IndexCopy.Children.empty()) { |
| // If the index is empty, default to displaying the global namespace. |
| IndexCopy.Children.try_emplace(toStringRef(GlobalNamespaceID), |
| GlobalNamespaceID, "", |
| InfoType::IT_namespace, "GlobalNamespace"); |
| } else { |
| IndexArrayRef.reserve(CDCtx->Idx.Children.size()); |
| } |
| |
| auto Children = IndexCopy.getSortedChildren(); |
| |
| for (const auto *Idx : Children) { |
| if (Idx->Children.empty()) |
| continue; |
| std::string TypeStr = infoTypeToString(Idx->RefType); |
| json::Value IdxVal = Object(); |
| auto &IdxObj = *IdxVal.getAsObject(); |
| if (Markdown) |
| TypeStr.at(0) = clang::toUppercase(TypeStr.at(0)); |
| IdxObj["Type"] = TypeStr; |
| serializeReference(*Idx, IdxObj); |
| IndexArrayRef.push_back(IdxVal); |
| } |
| Obj["Index"] = IndexArray; |
| |
| SmallString<128> IndexFilePath(RootDir); |
| sys::path::append(IndexFilePath, "/json/index.json"); |
| std::error_code FileErr; |
| raw_fd_ostream RootOS(IndexFilePath, FileErr, sys::fs::OF_Text); |
| if (FileErr) |
| return createFileError("cannot open file " + IndexFilePath, FileErr); |
| if (CDCtx->Pretty) |
| RootOS << llvm::formatv("{0:2}", ObjVal); |
| else |
| RootOS << llvm::formatv("{0}", ObjVal); |
| return Error::success(); |
| } |
| |
| void JSONGenerator::serializeContexts(Info *I, StringMap<Info *> &Infos) { |
| if (I->USR == GlobalNamespaceID) |
| return; |
| auto ParentUSR = I->ParentUSR; |
| auto &LocalContexts = ContextsMap[I]; |
| |
| while (true) { |
| // Infos may not have the ParentUSR, if its been filtered (public or path), |
| // so we can't use at() for the lookup, since it would abort. |
| auto Iter = Infos.find(llvm::toHex(ParentUSR)); |
| if (Iter == Infos.end()) |
| break; |
| auto &ParentInfo = Iter->second; |
| |
| if (ParentInfo && ParentInfo->USR == GlobalNamespaceID) { |
| Context GlobalRef(ParentInfo->USR, "Global Namespace", |
| InfoType::IT_namespace, "GlobalNamespace", "", |
| SmallString<16>("index")); |
| LocalContexts.push_back(GlobalRef); |
| break; |
| } |
| |
| Context ParentRef(*ParentInfo); |
| LocalContexts.push_back(ParentRef); |
| ParentUSR = ParentInfo->ParentUSR; |
| } |
| } |
| |
| Error JSONGenerator::generateDocumentation(StringRef RootDir, |
| llvm::StringMap<Info *> Infos, |
| const ClangDocContext &CDCtx, |
| std::string DirName) { |
| this->CDCtx = &CDCtx; |
| this->Infos = &Infos; |
| StringSet<> CreatedDirs; |
| StringMap<std::vector<Info *>> FileToInfos; |
| for (const auto &Group : Infos) { |
| Info *Info = Group.getValue(); |
| |
| SmallString<128> Path; |
| auto RootDirStr = RootDir.str() + "/json"; |
| StringRef JSONDir = StringRef(RootDirStr); |
| sys::path::native(JSONDir, Path); |
| sys::path::append(Path, Info->getRelativeFilePath("")); |
| if (!CreatedDirs.contains(Path)) { |
| if (std::error_code Err = sys::fs::create_directories(Path); |
| Err != std::error_code()) |
| return createFileError(Twine(Path), Err); |
| CreatedDirs.insert(Path); |
| } |
| |
| SmallString<16> FileName = determineFileName(Info, Path); |
| if (FileToInfos.contains(Path)) |
| continue; |
| FileToInfos[Path].push_back(Info); |
| Info->DocumentationFileName = internString(FileName); |
| } |
| |
| if (CDCtx.Format == OutputFormatTy::md_mustache) { |
| Markdown = true; |
| if (auto Err = serializeAllFiles(CDCtx, RootDir)) |
| return Err; |
| } |
| |
| for (const auto &Group : FileToInfos) { |
| std::error_code FileErr; |
| raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_Text); |
| if (FileErr) |
| return createFileError("cannot open file " + Group.getKey(), FileErr); |
| |
| for (const auto &Info : Group.getValue()) { |
| if (Info->IT == InfoType::IT_record || Info->IT == InfoType::IT_namespace) |
| serializeContexts(Info, Infos); |
| if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx)) |
| return Err; |
| } |
| } |
| |
| return serializeIndex(RootDir); |
| } |
| |
| Error JSONGenerator::generateDocForInfo(Info *I, raw_ostream &OS, |
| const ClangDocContext &CDCtx) { |
| json::Object Obj = Object(); |
| |
| switch (I->IT) { |
| case InfoType::IT_namespace: |
| serializeInfo(*cast<NamespaceInfo>(I), Obj); |
| break; |
| case InfoType::IT_record: |
| serializeInfo(*cast<RecordInfo>(I), Obj); |
| break; |
| case InfoType::IT_concept: |
| case InfoType::IT_enum: |
| case InfoType::IT_function: |
| case InfoType::IT_typedef: |
| case InfoType::IT_variable: |
| case InfoType::IT_friend: |
| break; |
| case InfoType::IT_default: |
| return createStringError(inconvertibleErrorCode(), "unexpected info type"); |
| } |
| StringRef Fmt = CDCtx.Pretty ? "{0:2}" : "{0}"; |
| OS << llvm::formatv(Fmt.data(), llvm::json::Value(std::move(Obj))); |
| return Error::success(); |
| } |
| |
| Error JSONGenerator::createResources(ClangDocContext &CDCtx) { |
| return Error::success(); |
| } |
| |
| static GeneratorRegistry::Add<JSONGenerator> JSON(JSONGenerator::Format, |
| "Generator for JSON output."); |
| namespace clang::doc { |
| volatile int JSONGeneratorAnchorSource = 0; |
| } // namespace clang::doc |