From e215c97c1c4bc1c423c1e837794d389885e22b70 Mon Sep 17 00:00:00 2001 From: Paul Kirth Date: Wed, 30 Apr 2025 08:11:39 -0700 Subject: [PATCH] [clang-doc] Extract Info into JSON values Split from #133161. This patch provides the implementation of a number of extractValue overloads used with the different types of Info. The new helper functions extract the relevant information from the different *Infos and inserts them into the correct fields of the JSON values that will be used with the specific Mustache templates, which will land separately. Co-authored-by: Peter Chou --- .../clang-doc/HTMLMustacheGenerator.cpp | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp index 1c44465b22b02..e2a739f97faf1 100644 --- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp +++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp @@ -141,15 +141,266 @@ Error MustacheHTMLGenerator::generateDocs( return Error::success(); } +static json::Value +extractValue(const Location &L, + std::optional RepositoryUrl = std::nullopt) { + Object Obj = Object(); + // Should there be Start/End line numbers? + Obj.insert({"LineNumber", L.StartLineNumber}); + Obj.insert({"Filename", L.Filename}); + + if (!L.IsFileInRootDir || !RepositoryUrl) { + return Obj; + } + SmallString<128> FileURL(*RepositoryUrl); + sys::path::append(FileURL, sys::path::Style::posix, L.Filename); + FileURL += "#" + std::to_string(L.StartLineNumber); + Obj.insert({"FileURL", FileURL}); + + return Obj; +} + +static json::Value extractValue(const Reference &I, + StringRef CurrentDirectory) { + SmallString<64> Path = I.getRelativeFilePath(CurrentDirectory); + sys::path::append(Path, I.getFileBaseName() + ".html"); + sys::path::native(Path, sys::path::Style::posix); + Object Obj = Object(); + Obj.insert({"Link", Path}); + Obj.insert({"Name", I.Name}); + Obj.insert({"QualName", I.QualName}); + Obj.insert({"ID", toHex(toStringRef(I.USR))}); + return Obj; +} + +static json::Value extractValue(const TypedefInfo &I) { + // Not Supported + return nullptr; +} + +static json::Value extractValue(const CommentInfo &I) { + assert((I.Kind == "BlockCommandComment" || I.Kind == "FullComment" || + I.Kind == "ParagraphComment" || I.Kind == "TextComment") && + "Unknown Comment type in CommentInfo."); + + Object Obj = Object(); + json::Value Child = Object(); + + // TextComment has no children, so return it. + if (I.Kind == "TextComment") { + Obj.insert({"TextComment", I.Text}); + return Obj; + } + + // BlockCommandComment needs to generate a Command key. + if (I.Kind == "BlockCommandComment") { + Child.getAsObject()->insert({"Command", I.Name}); + } + + // Use the same handling for everything else. + // Only valid for: + // - BlockCommandComment + // - FullComment + // - ParagraphComment + json::Value ChildArr = Array(); + auto &CARef = *ChildArr.getAsArray(); + CARef.reserve(I.Children.size()); + for (const auto &C : I.Children) + CARef.emplace_back(extractValue(*C)); + Child.getAsObject()->insert({"Children", ChildArr}); + Obj.insert({I.Kind, Child}); + + return Obj; +} + +static void maybeInsertLocation(std::optional Loc, + const ClangDocContext &CDCtx, Object &Obj) { + if (!Loc) + return; + Location L = *Loc; + Obj.insert({"Location", extractValue(L, CDCtx.RepositoryUrl)}); +} + +static void extractDescriptionFromInfo(ArrayRef Descriptions, + json::Object &EnumValObj) { + if (Descriptions.empty()) + return; + json::Value ArrDesc = Array(); + json::Array &ADescRef = *ArrDesc.getAsArray(); + for (const CommentInfo &Child : Descriptions) + ADescRef.emplace_back(extractValue(Child)); + EnumValObj.insert({"EnumValueComments", ArrDesc}); +} + +static json::Value extractValue(const FunctionInfo &I, StringRef ParentInfoDir, + const ClangDocContext &CDCtx) { + Object Obj = Object(); + Obj.insert({"Name", I.Name}); + Obj.insert({"ID", toHex(toStringRef(I.USR))}); + Obj.insert({"Access", getAccessSpelling(I.Access).str()}); + Obj.insert({"ReturnType", extractValue(I.ReturnType.Type, ParentInfoDir)}); + + json::Value ParamArr = Array(); + for (const auto Val : enumerate(I.Params)) { + json::Value V = Object(); + auto &VRef = *V.getAsObject(); + VRef.insert({"Name", Val.value().Name}); + VRef.insert({"Type", Val.value().Type.Name}); + VRef.insert({"End", Val.index() + 1 == I.Params.size()}); + ParamArr.getAsArray()->emplace_back(V); + } + Obj.insert({"Params", ParamArr}); + + maybeInsertLocation(I.DefLoc, CDCtx, Obj); + return Obj; +} + +static json::Value extractValue(const EnumInfo &I, + const ClangDocContext &CDCtx) { + Object Obj = Object(); + std::string EnumType = I.Scoped ? "enum class " : "enum "; + EnumType += I.Name; + bool HasComment = std::any_of( + I.Members.begin(), I.Members.end(), + [](const EnumValueInfo &M) { return !M.Description.empty(); }); + Obj.insert({"EnumName", EnumType}); + Obj.insert({"HasComment", HasComment}); + Obj.insert({"ID", toHex(toStringRef(I.USR))}); + json::Value Arr = Array(); + json::Array &ARef = *Arr.getAsArray(); + for (const EnumValueInfo &M : I.Members) { + json::Value EnumValue = Object(); + auto &EnumValObj = *EnumValue.getAsObject(); + EnumValObj.insert({"Name", M.Name}); + if (!M.ValueExpr.empty()) + EnumValObj.insert({"ValueExpr", M.ValueExpr}); + else + EnumValObj.insert({"Value", M.Value}); + + extractDescriptionFromInfo(M.Description, EnumValObj); + ARef.emplace_back(EnumValue); + } + Obj.insert({"EnumValues", Arr}); + + extractDescriptionFromInfo(I.Description, Obj); + maybeInsertLocation(I.DefLoc, CDCtx, Obj); + + return Obj; +} + +static void extractScopeChildren(const ScopeChildren &S, Object &Obj, + StringRef ParentInfoDir, + const ClangDocContext &CDCtx) { + json::Value ArrNamespace = Array(); + for (const Reference &Child : S.Namespaces) + ArrNamespace.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); + + if (!ArrNamespace.getAsArray()->empty()) + Obj.insert({"Namespace", Object{{"Links", ArrNamespace}}}); + + json::Value ArrRecord = Array(); + for (const Reference &Child : S.Records) + ArrRecord.getAsArray()->emplace_back(extractValue(Child, ParentInfoDir)); + + if (!ArrRecord.getAsArray()->empty()) + Obj.insert({"Record", Object{{"Links", ArrRecord}}}); + + json::Value ArrFunction = Array(); + json::Value PublicFunction = Array(); + json::Value ProtectedFunction = Array(); + json::Value PrivateFunction = Array(); + + for (const FunctionInfo &Child : S.Functions) { + json::Value F = extractValue(Child, ParentInfoDir, CDCtx); + AccessSpecifier Access = Child.Access; + if (Access == AccessSpecifier::AS_public) + PublicFunction.getAsArray()->emplace_back(F); + else if (Access == AccessSpecifier::AS_protected) + ProtectedFunction.getAsArray()->emplace_back(F); + else + ArrFunction.getAsArray()->emplace_back(F); + } + + if (!ArrFunction.getAsArray()->empty()) + Obj.insert({"Function", Object{{"Obj", ArrFunction}}}); + + if (!PublicFunction.getAsArray()->empty()) + Obj.insert({"PublicFunction", Object{{"Obj", PublicFunction}}}); + + if (!ProtectedFunction.getAsArray()->empty()) + Obj.insert({"ProtectedFunction", Object{{"Obj", ProtectedFunction}}}); + + json::Value ArrEnum = Array(); + auto &ArrEnumRef = *ArrEnum.getAsArray(); + for (const EnumInfo &Child : S.Enums) + ArrEnumRef.emplace_back(extractValue(Child, CDCtx)); + + if (!ArrEnumRef.empty()) + Obj.insert({"Enums", Object{{"Obj", ArrEnum}}}); + + json::Value ArrTypedefs = Array(); + auto &ArrTypedefsRef = *ArrTypedefs.getAsArray(); + for (const TypedefInfo &Child : S.Typedefs) + ArrTypedefsRef.emplace_back(extractValue(Child)); + + if (!ArrTypedefsRef.empty()) + Obj.insert({"Typedefs", Object{{"Obj", ArrTypedefs}}}); +} + static json::Value extractValue(const NamespaceInfo &I, const ClangDocContext &CDCtx) { Object NamespaceValue = Object(); + std::string InfoTitle = I.Name.empty() ? "Global Namespace" + : (Twine("namespace ") + I.Name).str(); + + StringRef BasePath = I.getRelativeFilePath(""); + NamespaceValue.insert({"NamespaceTitle", InfoTitle}); + NamespaceValue.insert({"NamespacePath", BasePath}); + + extractDescriptionFromInfo(I.Description, NamespaceValue); + extractScopeChildren(I.Children, NamespaceValue, BasePath, CDCtx); return NamespaceValue; } static json::Value extractValue(const RecordInfo &I, const ClangDocContext &CDCtx) { Object RecordValue = Object(); + extractDescriptionFromInfo(I.Description, RecordValue); + RecordValue.insert({"Name", I.Name}); + RecordValue.insert({"FullName", I.FullName}); + RecordValue.insert({"RecordType", getTagType(I.TagType)}); + + maybeInsertLocation(I.DefLoc, CDCtx, RecordValue); + + StringRef BasePath = I.getRelativeFilePath(""); + extractScopeChildren(I.Children, RecordValue, BasePath, CDCtx); + json::Value PublicMembers = Array(); + json::Array &PubMemberRef = *PublicMembers.getAsArray(); + json::Value ProtectedMembers = Array(); + json::Array &ProtMemberRef = *ProtectedMembers.getAsArray(); + json::Value PrivateMembers = Array(); + json::Array &PrivMemberRef = *PrivateMembers.getAsArray(); + for (const MemberTypeInfo &Member : I.Members) { + json::Value MemberValue = Object(); + auto &MVRef = *MemberValue.getAsObject(); + MVRef.insert({"Name", Member.Name}); + MVRef.insert({"Type", Member.Type.Name}); + extractDescriptionFromInfo(Member.Description, MVRef); + + if (Member.Access == AccessSpecifier::AS_public) + PubMemberRef.emplace_back(MemberValue); + else if (Member.Access == AccessSpecifier::AS_protected) + ProtMemberRef.emplace_back(MemberValue); + else if (Member.Access == AccessSpecifier::AS_private) + ProtMemberRef.emplace_back(MemberValue); + } + if (!PubMemberRef.empty()) + RecordValue.insert({"PublicMembers", Object{{"Obj", PublicMembers}}}); + if (!ProtMemberRef.empty()) + RecordValue.insert({"ProtectedMembers", Object{{"Obj", ProtectedMembers}}}); + if (!PrivMemberRef.empty()) + RecordValue.insert({"PrivateMembers", Object{{"Obj", PrivateMembers}}}); + return RecordValue; }