Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[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

Open
evelez7 wants to merge1 commit intousers/evelez7/clang-doc-refactor-json-for-mustache
base:users/evelez7/clang-doc-refactor-json-for-mustache
Choose a base branch
Loading
fromusers/evelez7/clang-doc-integrate-json-to-mustache

Conversation

evelez7
Copy link
Member

@evelez7evelez7 commentedJul 18, 2025
edited
Loading

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.

@evelez7Graphite App
Copy link
MemberAuthor

evelez7 commentedJul 18, 2025
edited
Loading

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.
Learn more

This stack of pull requests is managed byGraphite. Learn more aboutstacking.

@llvmbot
Copy link
Member

@llvm/pr-subscribers-clang-tools-extra

Author: Erick Velez (evelez7)

Changes

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.


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:

  • (modified) clang-tools-extra/clang-doc/HTMLMustacheGenerator.cpp (+78-409)
  • (modified) clang-tools-extra/clang-doc/assets/class-template.mustache (+32-30)
  • (modified) clang-tools-extra/clang-doc/assets/enum-template.mustache (+9-3)
  • (modified) clang-tools-extra/clang-doc/assets/function-template.mustache (+1-1)
  • (modified) clang-tools-extra/clang-doc/assets/namespace-template.mustache (+25-20)
  • (modified) clang-tools-extra/test/clang-doc/basic-project.mustache.test (+13-57)
  • (modified) clang-tools-extra/test/clang-doc/mustache-index.cpp (+9-5)
  • (modified) clang-tools-extra/test/clang-doc/mustache-separate-namespace.cpp (+6-2)
  • (modified) clang-tools-extra/unittests/clang-doc/HTMLMustacheGeneratorTest.cpp (-129)
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
Reviewers

@ilovepiilovepiAwaiting requested review from ilovepi

@petrhosekpetrhosekAwaiting requested review from petrhosek

Assignees
No one assigned
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

2 participants
@evelez7@llvmbot

[8]ページ先頭

©2009-2025 Movatter.jp