- Notifications
You must be signed in to change notification settings - Fork14.5k
[clang-doc] integrate JSON as the source for Mustache templates#149589
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:users/evelez7/clang-doc-refactor-json-for-mustache
Are you sure you want to change the base?
Conversation
evelez7 commentedJul 18, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stackon Graphite.
This stack of pull requests is managed byGraphite. Learn more aboutstacking. |
@llvm/pr-subscribers-clang-tools-extra Author: Erick Velez (evelez7) ChangesThis patch integrates JSON as the source to generate HTML Mustache templates. The Mustache generator calls the JSON generator and reads JSON files on the disk to produce HTML serially. Patch is 48.74 KiB, truncated to 20.00 KiB below, full version:https://github.com/llvm/llvm-project/pull/149589.diff 9 Files Affected:
diff --git a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cppindex 7aeaa1b7cf67d..98e2935a8aada 100644--- a/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp+++ b/clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp@@ -27,6 +27,9 @@ using namespace llvm::mustache; namespace clang { namespace doc {+static Error generateDocForJSON(json::Value &JSON, StringRef Filename,+ StringRef Path, raw_fd_ostream &OS,+ const ClangDocContext &CDCtx); static Error createFileOpenError(StringRef FileName, std::error_code EC) { return createFileError("cannot open file " + FileName, EC);@@ -132,404 +135,65 @@ Error MustacheHTMLGenerator::generateDocs( return Err; }- // Track which directories we already tried to create.- StringSet<> CreatedDirs;- // Collect all output by file name and create the necessary directories.- StringMap<std::vector<doc::Info *>> FileToInfos;- for (const auto &Group : Infos) {- llvm::TimeTraceScope TS("setup directories");- doc::Info *Info = Group.getValue().get();-- SmallString<128> Path;- sys::path::native(RootDir, Path);- sys::path::append(Path, Info->getRelativeFilePath(""));- if (!CreatedDirs.contains(Path)) {- if (std::error_code EC = sys::fs::create_directories(Path))- return createStringError(EC, "failed to create directory '%s'.",- Path.c_str());- CreatedDirs.insert(Path);- }-- sys::path::append(Path, Info->getFileBaseName() + ".html");- FileToInfos[Path].push_back(Info);+ {+ llvm::TimeTraceScope TS("Generate JSON for Mustache");+ if (auto JSONGenerator = findGeneratorByName("json")) {+ if (Error Err = JSONGenerator.get()->generateDocs(+ RootDir, std::move(Infos), CDCtx))+ return Err;+ } else+ return JSONGenerator.takeError(); }+ StringMap<json::Value> JSONFileMap; {- llvm::TimeTraceScope TS("Generate Docs");- for (const auto &Group : FileToInfos) {- llvm::TimeTraceScope TS("Info to Doc");+ llvm::TimeTraceScope TS("Iterate JSON files");+ std::error_code EC;+ sys::fs::directory_iterator JSONIter(RootDir, EC);+ std::vector<json::Value> JSONFiles;+ JSONFiles.reserve(Infos.size());+ if (EC)+ return createStringError("Failed to create directory iterator.");++ while (JSONIter != sys::fs::directory_iterator()) {+ if (EC)+ return createStringError(EC, "Failed to iterate directory");++ auto Path = StringRef(JSONIter->path());+ if (!Path.ends_with(".json")) {+ JSONIter.increment(EC);+ continue;+ }++ auto File = MemoryBuffer::getFile(Path);+ if ((EC = File.getError()))+ continue;++ auto Parsed = json::parse((*File)->getBuffer());+ if (!Parsed)+ return Parsed.takeError();+ std::error_code FileErr;- raw_fd_ostream InfoOS(Group.getKey(), FileErr, sys::fs::OF_None);+ SmallString<16> HTMLPath(Path.begin(), Path.end());+ sys::path::replace_extension(HTMLPath, "html");+ raw_fd_ostream InfoOS(HTMLPath, FileErr, sys::fs::OF_None); if (FileErr)- return createFileOpenError(Group.getKey(), FileErr);+ return createFileOpenError(Path, FileErr);- for (const auto &Info : Group.getValue())- if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))- return Err;+ if (Error Err = generateDocForJSON(*Parsed, sys::path::stem(HTMLPath),+ HTMLPath, InfoOS, CDCtx))+ return Err;+ JSONIter.increment(EC); } }- return Error::success();-}--static json::Value-extractValue(const Location &L,- std::optional<StringRef> RepositoryUrl = std::nullopt) {- Object Obj = Object();- // TODO: Consider using both Start/End line numbers to improve location report- 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) {- 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(extractValue(*C));-- switch (I.Kind) {- case CommentKind::CK_TextComment: {- Obj.insert({commentKindToString(I.Kind), I.Text});- return Obj;- }-- case CommentKind::CK_BlockCommandComment: {- Child.insert({"Command", I.Name});- Child.insert({"Children", ChildArr});- Obj.insert({commentKindToString(I.Kind), ChildVal});- 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});- Child.insert({"Children", ChildArr});- Obj.insert({commentKindToString(I.Kind), ChildVal});- return Obj;- }-- case CommentKind::CK_VerbatimBlockComment: {- Child.insert({"Text", I.Text});- if (!I.CloseName.empty())- Child.insert({"CloseName", I.CloseName});- Child.insert({"Children", ChildArr});- Obj.insert({commentKindToString(I.Kind), ChildVal});- 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});- Obj.insert({commentKindToString(I.Kind), ChildVal});- return Obj;- }-- case CommentKind::CK_Unknown: {- Obj.insert({commentKindToString(I.Kind), I.Text});- return Obj;- }- }- llvm_unreachable("Unknown comment kind encountered.");-}--static void maybeInsertLocation(std::optional<Location> Loc,- const ClangDocContext &CDCtx, Object &Obj) {- if (!Loc)- return;- Location L = *Loc;- Obj.insert({"Location", extractValue(L, CDCtx.RepositoryUrl)});-}--static void extractDescriptionFromInfo(ArrayRef<CommentInfo> Descriptions,- json::Object &EnumValObj) {- if (Descriptions.empty())- return;- json::Value DescArr = Array();- json::Array &DescARef = *DescArr.getAsArray();- DescARef.reserve(Descriptions.size());- for (const CommentInfo &Child : Descriptions)- DescARef.emplace_back(extractValue(Child));- EnumValObj.insert({"EnumValueComments", DescArr});-}--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();- json::Array &ParamARef = *ParamArr.getAsArray();- ParamARef.reserve(I.Params.size());- 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()});- ParamARef.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 = llvm::any_of(- I.Members, [](const EnumValueInfo &M) { return !M.Description.empty(); });- Obj.insert({"EnumName", EnumType});- Obj.insert({"HasComment", HasComment});- Obj.insert({"ID", toHex(toStringRef(I.USR))});- json::Value EnumArr = Array();- json::Array &EnumARef = *EnumArr.getAsArray();- EnumARef.reserve(I.Members.size());- 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);- EnumARef.emplace_back(EnumValue);- }- Obj.insert({"EnumValues", EnumArr});-- 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 NamespaceArr = Array();- json::Array &NamespaceARef = *NamespaceArr.getAsArray();- NamespaceARef.reserve(S.Namespaces.size());- for (const Reference &Child : S.Namespaces)- NamespaceARef.emplace_back(extractValue(Child, ParentInfoDir));-- if (!NamespaceARef.empty())- Obj.insert({"Namespace", Object{{"Links", NamespaceArr}}});-- json::Value RecordArr = Array();- json::Array &RecordARef = *RecordArr.getAsArray();- RecordARef.reserve(S.Records.size());- for (const Reference &Child : S.Records)- RecordARef.emplace_back(extractValue(Child, ParentInfoDir));-- if (!RecordARef.empty())- Obj.insert({"Record", Object{{"Links", RecordArr}}});-- json::Value FunctionArr = Array();- json::Array &FunctionARef = *FunctionArr.getAsArray();- FunctionARef.reserve(S.Functions.size());-- json::Value PublicFunctionArr = Array();- json::Array &PublicFunctionARef = *PublicFunctionArr.getAsArray();- PublicFunctionARef.reserve(S.Functions.size());-- json::Value ProtectedFunctionArr = Array();- json::Array &ProtectedFunctionARef = *ProtectedFunctionArr.getAsArray();- ProtectedFunctionARef.reserve(S.Functions.size());-- for (const FunctionInfo &Child : S.Functions) {- json::Value F = extractValue(Child, ParentInfoDir, CDCtx);- AccessSpecifier Access = Child.Access;- if (Access == AccessSpecifier::AS_public)- PublicFunctionARef.emplace_back(F);- else if (Access == AccessSpecifier::AS_protected)- ProtectedFunctionARef.emplace_back(F);- else- FunctionARef.emplace_back(F);- }-- if (!FunctionARef.empty())- Obj.insert({"Function", Object{{"Obj", FunctionArr}}});-- if (!PublicFunctionARef.empty())- Obj.insert({"PublicFunction", Object{{"Obj", PublicFunctionArr}}});-- if (!ProtectedFunctionARef.empty())- Obj.insert({"ProtectedFunction", Object{{"Obj", ProtectedFunctionArr}}});-- json::Value EnumArr = Array();- auto &EnumARef = *EnumArr.getAsArray();- EnumARef.reserve(S.Enums.size());- for (const EnumInfo &Child : S.Enums)- EnumARef.emplace_back(extractValue(Child, CDCtx));-- if (!EnumARef.empty())- Obj.insert({"Enums", Object{{"Obj", EnumArr}}});-- json::Value TypedefArr = Array();- auto &TypedefARef = *TypedefArr.getAsArray();- TypedefARef.reserve(S.Typedefs.size());- for (const TypedefInfo &Child : S.Typedefs)- TypedefARef.emplace_back(extractValue(Child));-- if (!TypedefARef.empty())- Obj.insert({"Typedefs", Object{{"Obj", TypedefArr}}});-}--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();-- SmallString<64> 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);-- SmallString<64> BasePath = I.getRelativeFilePath("");- extractScopeChildren(I.Children, RecordValue, BasePath, CDCtx);- json::Value PublicMembers = Array();- json::Array &PubMemberRef = *PublicMembers.getAsArray();- PubMemberRef.reserve(I.Members.size());- json::Value ProtectedMembers = Array();- json::Array &ProtMemberRef = *ProtectedMembers.getAsArray();- ProtMemberRef.reserve(I.Members.size());- json::Value PrivateMembers = Array();- json::Array &PrivMemberRef = *PrivateMembers.getAsArray();- PrivMemberRef.reserve(I.Members.size());- 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;+ return Error::success(); }-static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V,- Info *I) {+static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V) { V.getAsObject()->insert({"ProjectName", CDCtx.ProjectName}); json::Value StylesheetArr = Array();- auto InfoPath = I->getRelativeFilePath("");- SmallString<128> RelativePath = computeRelativePath("", InfoPath);+ SmallString<128> RelativePath("./"); sys::path::native(RelativePath, sys::path::Style::posix); auto *SSA = StylesheetArr.getAsArray();@@ -555,38 +219,43 @@ static Error setupTemplateValue(const ClangDocContext &CDCtx, json::Value &V, return Error::success(); }-Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,- const ClangDocContext &CDCtx) {- switch (I->IT) {- case InfoType::IT_namespace: {- json::Value V =- extractValue(*static_cast<clang::doc::NamespaceInfo *>(I), CDCtx);- if (auto Err = setupTemplateValue(CDCtx, V, I))+static Error generateDocForJSON(json::Value &JSON, StringRef Filename,+ StringRef Path, raw_fd_ostream &OS,+ const ClangDocContext &CDCtx) {+ auto StrValue = (*JSON.getAsObject())["InfoType"];+ if (StrValue.kind() != json::Value::Kind::String)+ return createStringError(+ "JSON file '%s' does not contain 'InfoType' field.",+ Filename.str().c_str());+ auto ObjTypeStr = StrValue.getAsString();+ if (!ObjTypeStr.has_value())+ return createStringError(+ "JSON file '%s' does not contain 'InfoType' field as a string.",+ Filename.str().c_str());++ if (ObjTypeStr.value() == "namespace") {+ if (auto Err = setupTemplateValue(CDCtx, JSON)) return Err; assert(NamespaceTemplate && "NamespaceTemplate is nullptr.");- NamespaceTemplate->render(V, OS);- break;- }- case InfoType::IT_record: {- json::Value V =- extractValue(*static_cast<clang::doc::RecordInfo *>(I), CDCtx);- if (auto Err = setupTemplateValue(CDCtx, V, I))+ NamespaceTemplate->render(JSON, OS);+ } else if (ObjTypeStr.value() == "record") {+ if (auto Err = setupTemplateValue(CDCtx, JSON)) return Err;- // Serialize the JSON value to the output stream in a readable format.- RecordTemplate->render(V, OS);- break;+ assert(RecordTemplate && "RecordTemplate is nullptr.");+ RecordTemplate->render(JSON, OS); }+ return Error::success();+}++Error MustacheHTMLGenerator::generateDocForInfo(Info *I, raw_ostream &OS,+ const ClangDocContext &CDCtx) {+ switch (I->IT) { case InfoType::IT_enum:- OS << "IT_enum\n";- break; case InfoType::IT_function:- OS << "IT_Function\n";- break; case InfoType::IT_typedef:- OS << "IT_typedef\n";- break;+ case InfoType::IT_namespace:+ case InfoType::IT_record: case InfoType::IT_concept:- break; case InfoType::IT_variable: case InfoType::IT_friend: break;diff --git a/clang-tools-extra/clang-doc/assets/class-template.mustache b/clang-tools-extra/clang-doc/assets/class-template.mustacheindex f9e78f5cd6bc9..a4077323f29e2 100644--- a/clang-tools-extra/clang-doc/assets/class-template.mustache+++ b/clang-tools-extra/clang-doc/assets/class-template.mustache@@ -44,20 +44,20 @@ <main> <div> <div>- <h2>{{RecordType}} {{Name}}</h2>+ <h2>{{TagType}} {{Name}}</h2> <ul>- {{#PublicMembers}}+ {{#HasPublicMembers}} <li>- <a href="#PublicMethods">Public Members</a>+ <ad-flex"> Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later. You can’t perform that action at this time. [8]ページ先頭 ©2009-2025 Movatter.jp
|
Uh oh!
There was an error while loading.Please reload this page.
This patch integrates JSON as the source to generate HTML Mustache templates. The Mustache generator calls the JSON generator and reads JSON files on the disk to produce HTML serially.