[clang-doc] Generate HTML links for children namespaces/records
authorDiego Astiazaran <diegoaat97@gmail.com>
Mon, 12 Aug 2019 18:42:46 +0000 (18:42 +0000)
committerDiego Astiazaran <diegoaat97@gmail.com>
Mon, 12 Aug 2019 18:42:46 +0000 (18:42 +0000)
Path is now stored in the references to the child while serializing,
then this path is used to generate the relative path in the HTML
generator.
Now some references have paths and some don't so in the reducing phase,
references are now properly merged checking for empty attributes.
Tests added for HTML and YAML generators, merging and serializing.
computeRelativePath function had a bug when the filepath is part of the
given directory; it returned a path that starts with a separator. This
has been fixed.

Differential Revision: https://reviews.llvm.org/D65987

llvm-svn: 368602

clang-tools-extra/clang-doc/HTMLGenerator.cpp
clang-tools-extra/clang-doc/Representation.cpp
clang-tools-extra/clang-doc/Representation.h
clang-tools-extra/clang-doc/Serialize.cpp
clang-tools-extra/unittests/clang-doc/HTMLGeneratorTest.cpp
clang-tools-extra/unittests/clang-doc/MergeTest.cpp
clang-tools-extra/unittests/clang-doc/SerializeTest.cpp
clang-tools-extra/unittests/clang-doc/YAMLGeneratorTest.cpp

index 3987f23..b85232c 100644 (file)
@@ -207,26 +207,44 @@ static void AppendVector(std::vector<Derived> &&New,
   std::move(New.begin(), New.end(), std::back_inserter(Original));
 }
 
-// Compute the relative path that names the file path relative to the given
-// directory.
-static SmallString<128> computeRelativePath(StringRef FilePath,
-                                            StringRef Directory) {
-  StringRef Path = FilePath;
-  while (!Path.empty()) {
-    if (Directory == Path)
-      return FilePath.substr(Path.size());
-    Path = llvm::sys::path::parent_path(Path);
-  }
-
-  StringRef Dir = Directory;
-  SmallString<128> Result;
-  while (!Dir.empty()) {
-    if (Dir == FilePath)
-      break;
-    Dir = llvm::sys::path::parent_path(Dir);
+// Compute the relative path from an Origin directory to a Destination directory
+static SmallString<128> computeRelativePath(StringRef Destination,
+                                            StringRef Origin) {
+  // If Origin is empty, the relative path to the Destination is its complete
+  // path.
+  if (Origin.empty())
+    return Destination;
+
+  // The relative path is an empty path if both directories are the same.
+  if (Destination == Origin)
+    return {};
+
+  // These iterators iterate through each of their parent directories
+  llvm::sys::path::const_iterator FileI = llvm::sys::path::begin(Destination);
+  llvm::sys::path::const_iterator FileE = llvm::sys::path::end(Destination);
+  llvm::sys::path::const_iterator DirI = llvm::sys::path::begin(Origin);
+  llvm::sys::path::const_iterator DirE = llvm::sys::path::end(Origin);
+  // Advance both iterators until the paths differ. Example:
+  //    Destination = A/B/C/D
+  //    Origin      = A/B/E/F
+  // FileI will point to C and DirI to E. The directories behind them is the
+  // directory they share (A/B).
+  while (FileI != FileE && DirI != DirE && *FileI == *DirI) {
+    ++FileI;
+    ++DirI;
+  }
+  SmallString<128> Result; // This will hold the resulting path.
+  // Result has to go up one directory for each of the remaining directories in
+  // Origin
+  while (DirI != DirE) {
     llvm::sys::path::append(Result, "..");
+    ++DirI;
+  }
+  // Result has to append each of the remaining directories in Destination
+  while (FileI != FileE) {
+    llvm::sys::path::append(Result, *FileI);
+    ++FileI;
   }
-  llvm::sys::path::append(Result, FilePath.substr(Dir.size()));
   return Result;
 }
 
@@ -271,8 +289,8 @@ static std::unique_ptr<TagNode> genLink(const Twine &Text, const Twine &Link) {
 }
 
 static std::unique_ptr<HTMLNode>
-genTypeReference(const Reference &Type, StringRef CurrentDirectory,
-                 llvm::Optional<StringRef> JumpToSection = None) {
+genReference(const Reference &Type, StringRef CurrentDirectory,
+             llvm::Optional<StringRef> JumpToSection = None) {
   if (Type.Path.empty() && !Type.IsInGlobalNamespace) {
     if (!JumpToSection)
       return llvm::make_unique<TextNode>(Type.Name);
@@ -296,7 +314,7 @@ genReferenceList(const llvm::SmallVectorImpl<Reference> &Refs,
   for (const auto &R : Refs) {
     if (&R != Refs.begin())
       Out.emplace_back(llvm::make_unique<TextNode>(", "));
-    Out.emplace_back(genTypeReference(R, CurrentDirectory));
+    Out.emplace_back(genReference(R, CurrentDirectory));
   }
   return Out;
 }
@@ -372,7 +390,7 @@ genRecordMembersBlock(const llvm::SmallVector<MemberTypeInfo, 4> &Members,
       Access = Access + " ";
     auto LIBody = llvm::make_unique<TagNode>(HTMLTag::TAG_LI);
     LIBody->Children.emplace_back(llvm::make_unique<TextNode>(Access));
-    LIBody->Children.emplace_back(genTypeReference(M.Type, ParentInfoDir));
+    LIBody->Children.emplace_back(genReference(M.Type, ParentInfoDir));
     LIBody->Children.emplace_back(llvm::make_unique<TextNode>(" " + M.Name));
     ULBody->Children.emplace_back(std::move(LIBody));
   }
@@ -381,7 +399,7 @@ genRecordMembersBlock(const llvm::SmallVector<MemberTypeInfo, 4> &Members,
 
 static std::vector<std::unique_ptr<TagNode>>
 genReferencesBlock(const std::vector<Reference> &References,
-                   llvm::StringRef Title) {
+                   llvm::StringRef Title, StringRef ParentPath) {
   if (References.empty())
     return {};
 
@@ -390,9 +408,11 @@ genReferencesBlock(const std::vector<Reference> &References,
   Out.back()->Attributes.try_emplace("id", Title);
   Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_UL));
   auto &ULBody = Out.back();
-  for (const auto &R : References)
-    ULBody->Children.emplace_back(
-        llvm::make_unique<TagNode>(HTMLTag::TAG_LI, R.Name));
+  for (const auto &R : References) {
+    auto LiNode = llvm::make_unique<TagNode>(HTMLTag::TAG_LI);
+    LiNode->Children.emplace_back(genReference(R, ParentPath));
+    ULBody->Children.emplace_back(std::move(LiNode));
+  }
   return Out;
 }
 
@@ -461,9 +481,9 @@ static std::vector<std::unique_ptr<TagNode>> genHTML(const Index &Index,
     Out.emplace_back(llvm::make_unique<TagNode>(HTMLTag::TAG_SPAN));
     auto &SpanBody = Out.back();
     if (!Index.JumpToSection)
-      SpanBody->Children.emplace_back(genTypeReference(Index, InfoPath));
+      SpanBody->Children.emplace_back(genReference(Index, InfoPath));
     else
-      SpanBody->Children.emplace_back(genTypeReference(
+      SpanBody->Children.emplace_back(genReference(
           Index, InfoPath, StringRef{Index.JumpToSection.getValue()}));
   }
   if (Index.Children.empty())
@@ -567,7 +587,7 @@ genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
         llvm::make_unique<TextNode>(Access + " "));
   if (I.ReturnType.Type.Name != "") {
     FunctionHeader->Children.emplace_back(
-        genTypeReference(I.ReturnType.Type, ParentInfoDir));
+        genReference(I.ReturnType.Type, ParentInfoDir));
     FunctionHeader->Children.emplace_back(llvm::make_unique<TextNode>(" "));
   }
   FunctionHeader->Children.emplace_back(
@@ -576,8 +596,7 @@ genHTML(const FunctionInfo &I, const ClangDocContext &CDCtx,
   for (const auto &P : I.Params) {
     if (&P != I.Params.begin())
       FunctionHeader->Children.emplace_back(llvm::make_unique<TextNode>(", "));
-    FunctionHeader->Children.emplace_back(
-        genTypeReference(P.Type, ParentInfoDir));
+    FunctionHeader->Children.emplace_back(genReference(P.Type, ParentInfoDir));
     FunctionHeader->Children.emplace_back(
         llvm::make_unique<TextNode>(" " + P.Name));
   }
@@ -614,10 +633,10 @@ genHTML(const NamespaceInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
     Out.emplace_back(genHTML(I.Description));
 
   std::vector<std::unique_ptr<TagNode>> ChildNamespaces =
-      genReferencesBlock(I.ChildNamespaces, "Namespaces");
+      genReferencesBlock(I.ChildNamespaces, "Namespaces", I.Path);
   AppendVector(std::move(ChildNamespaces), Out);
   std::vector<std::unique_ptr<TagNode>> ChildRecords =
-      genReferencesBlock(I.ChildRecords, "Records");
+      genReferencesBlock(I.ChildRecords, "Records", I.Path);
   AppendVector(std::move(ChildRecords), Out);
 
   std::vector<std::unique_ptr<TagNode>> ChildFunctions =
@@ -682,7 +701,7 @@ genHTML(const RecordInfo &I, Index &InfoIndex, const ClangDocContext &CDCtx,
       genRecordMembersBlock(I.Members, I.Path);
   AppendVector(std::move(Members), Out);
   std::vector<std::unique_ptr<TagNode>> ChildRecords =
-      genReferencesBlock(I.ChildRecords, "Records");
+      genReferencesBlock(I.ChildRecords, "Records", I.Path);
   AppendVector(std::move(ChildRecords), Out);
 
   std::vector<std::unique_ptr<TagNode>> ChildFunctions =
index 3060e03..aee9dc5 100644 (file)
@@ -54,13 +54,15 @@ int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) {
   return -1;
 }
 
-// For References, we don't need to actually merge them, we just don't want
-// duplicates.
 void reduceChildren(std::vector<Reference> &Children,
                     std::vector<Reference> &&ChildrenToMerge) {
   for (auto &ChildToMerge : ChildrenToMerge) {
-    if (getChildIndexIfExists(Children, ChildToMerge) == -1)
+    int mergeIdx = getChildIndexIfExists(Children, ChildToMerge);
+    if (mergeIdx == -1) {
       Children.push_back(std::move(ChildToMerge));
+      continue;
+    }
+    Children[mergeIdx].merge(std::move(ChildToMerge));
   }
 }
 
@@ -112,6 +114,20 @@ mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
   }
 }
 
+bool Reference::mergeable(const Reference &Other) {
+  return RefType == Other.RefType && USR == Other.USR;
+}
+
+void Reference::merge(Reference &&Other) {
+  assert(mergeable(Other));
+  if (Name.empty())
+    Name = Other.Name;
+  if (Path.empty())
+    Path = Other.Path;
+  if (!IsInGlobalNamespace)
+    IsInGlobalNamespace = Other.IsInGlobalNamespace;
+}
+
 void Info::mergeBase(Info &&Other) {
   assert(mergeable(Other));
   if (USR == EmptySID)
index ebb9446..bfe05ec 100644 (file)
@@ -131,6 +131,9 @@ struct Reference {
            std::tie(Other.USR, Other.Name, Other.RefType);
   }
 
+  bool mergeable(const Reference &Other);
+  void merge(Reference &&I);
+
   SymbolID USR = SymbolID(); // Unique identifer for referenced decl
   SmallString<16> Name;      // Name of type (possibly unresolved).
   InfoType RefType = InfoType::IT_default; // Indicates the type of this
index 26f22a9..78f1999 100644 (file)
@@ -394,8 +394,8 @@ emitInfo(const NamespaceDecl *D, const FullComment *FC, int LineNumber,
 
   auto ParentI = llvm::make_unique<NamespaceInfo>();
   ParentI->USR = I->Namespace.empty() ? SymbolID() : I->Namespace[0].USR;
-  ParentI->ChildNamespaces.emplace_back(I->USR, I->Name,
-                                        InfoType::IT_namespace);
+  ParentI->ChildNamespaces.emplace_back(I->USR, I->Name, InfoType::IT_namespace,
+                                        getInfoRelativePath(I->Namespace));
   if (I->Namespace.empty())
     ParentI->Path = getInfoRelativePath(ParentI->Namespace);
   return {std::unique_ptr<Info>{std::move(I)},
@@ -427,7 +427,8 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
   if (I->Namespace.empty()) {
     auto ParentI = llvm::make_unique<NamespaceInfo>();
     ParentI->USR = SymbolID();
-    ParentI->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record);
+    ParentI->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record,
+                                       getInfoRelativePath(I->Namespace));
     ParentI->Path = getInfoRelativePath(ParentI->Namespace);
     return {std::unique_ptr<Info>{std::move(I)},
             std::unique_ptr<Info>{std::move(ParentI)}};
@@ -437,14 +438,16 @@ emitInfo(const RecordDecl *D, const FullComment *FC, int LineNumber,
   case InfoType::IT_namespace: {
     auto ParentI = llvm::make_unique<NamespaceInfo>();
     ParentI->USR = I->Namespace[0].USR;
-    ParentI->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record);
+    ParentI->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record,
+                                       getInfoRelativePath(I->Namespace));
     return {std::unique_ptr<Info>{std::move(I)},
             std::unique_ptr<Info>{std::move(ParentI)}};
   }
   case InfoType::IT_record: {
     auto ParentI = llvm::make_unique<RecordInfo>();
     ParentI->USR = I->Namespace[0].USR;
-    ParentI->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record);
+    ParentI->ChildRecords.emplace_back(I->USR, I->Name, InfoType::IT_record,
+                                       getInfoRelativePath(I->Namespace));
     return {std::unique_ptr<Info>{std::move(I)},
             std::unique_ptr<Info>{std::move(ParentI)}};
   }
index 3a71bf2..901b75a 100644 (file)
@@ -39,8 +39,9 @@ TEST(HTMLGeneratorTest, emitNamespaceHTML) {
   I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
 
   I.ChildNamespaces.emplace_back(EmptySID, "ChildNamespace",
-                                 InfoType::IT_namespace);
-  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record);
+                                 InfoType::IT_namespace, "Namespace");
+  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
+                              "Namespace");
   I.ChildFunctions.emplace_back();
   I.ChildFunctions.back().Name = "OneFunction";
   I.ChildEnums.emplace_back();
@@ -100,11 +101,15 @@ TEST(HTMLGeneratorTest, emitNamespaceHTML) {
   <h1>namespace Namespace</h1>
   <h2 id="Namespaces">Namespaces</h2>
   <ul>
-    <li>ChildNamespace</li>
+    <li>
+      <a href="Namespace/ChildNamespace.html">ChildNamespace</a>
+    </li>
   </ul>
   <h2 id="Records">Records</h2>
   <ul>
-    <li>ChildStruct</li>
+    <li>
+      <a href="Namespace/ChildStruct.html">ChildStruct</a>
+    </li>
   </ul>
   <h2 id="Functions">Functions</h2>
   <div>
@@ -137,7 +142,8 @@ TEST(HTMLGeneratorTest, emitRecordHTML) {
   I.Parents.emplace_back(EmptySID, "F", InfoType::IT_record, PathTo);
   I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record);
 
-  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record);
+  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
+                              "X/Y/Z/r");
   I.ChildFunctions.emplace_back();
   I.ChildFunctions.back().Name = "OneFunction";
   I.ChildEnums.emplace_back();
@@ -215,7 +221,9 @@ TEST(HTMLGeneratorTest, emitRecordHTML) {
   </ul>
   <h2 id="Records">Records</h2>
   <ul>
-    <li>ChildStruct</li>
+    <li>
+      <a href="r/ChildStruct.html">ChildStruct</a>
+    </li>
   </ul>
   <h2 id="Functions">Functions</h2>
   <div>
index 7b3e230..89c68dd 100644 (file)
@@ -87,7 +87,7 @@ TEST(MergeTest, mergeRecordInfos) {
   One.Parents.emplace_back(EmptySID, "F", InfoType::IT_record);
   One.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record);
 
-  One.ChildRecords.emplace_back(NonEmptySID, "ChildStruct",
+  One.ChildRecords.emplace_back(NonEmptySID, "SharedChildStruct",
                                 InfoType::IT_record);
   One.ChildFunctions.emplace_back();
   One.ChildFunctions.back().Name = "OneFunction";
@@ -104,8 +104,8 @@ TEST(MergeTest, mergeRecordInfos) {
 
   Two.TagType = TagTypeKind::TTK_Class;
 
-  Two.ChildRecords.emplace_back(EmptySID, "OtherChildStruct",
-                                InfoType::IT_record);
+  Two.ChildRecords.emplace_back(NonEmptySID, "SharedChildStruct",
+                                InfoType::IT_record, "path");
   Two.ChildFunctions.emplace_back();
   Two.ChildFunctions.back().Name = "TwoFunction";
   Two.ChildEnums.emplace_back();
@@ -127,10 +127,8 @@ TEST(MergeTest, mergeRecordInfos) {
   Expected->Parents.emplace_back(EmptySID, "F", InfoType::IT_record);
   Expected->VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record);
 
-  Expected->ChildRecords.emplace_back(NonEmptySID, "ChildStruct",
-                                      InfoType::IT_record);
-  Expected->ChildRecords.emplace_back(EmptySID, "OtherChildStruct",
-                                      InfoType::IT_record);
+  Expected->ChildRecords.emplace_back(NonEmptySID, "SharedChildStruct",
+                                      InfoType::IT_record, "path");
   Expected->ChildFunctions.emplace_back();
   Expected->ChildFunctions.back().Name = "OneFunction";
   Expected->ChildFunctions.back().USR = NonEmptySID;
index dff12d2..f9feaf9 100644 (file)
@@ -407,12 +407,14 @@ TEST(SerializeTest, emitChildRecords) {
 
   RecordInfo *ParentB = InfoAsRecord(Infos[3].get());
   RecordInfo ExpectedParentB(EmptySID);
-  ExpectedParentB.ChildRecords.emplace_back(EmptySID, "B", InfoType::IT_record);
+  ExpectedParentB.ChildRecords.emplace_back(EmptySID, "B", InfoType::IT_record,
+                                            "A");
   CheckRecordInfo(&ExpectedParentB, ParentB);
 
   NamespaceInfo *ParentC = InfoAsNamespace(Infos[7].get());
   NamespaceInfo ExpectedParentC(EmptySID);
-  ExpectedParentC.ChildRecords.emplace_back(EmptySID, "C", InfoType::IT_record);
+  ExpectedParentC.ChildRecords.emplace_back(EmptySID, "C", InfoType::IT_record,
+                                            "@nonymous_namespace");
   CheckNamespaceInfo(&ExpectedParentC, ParentC);
 }
 
@@ -431,7 +433,7 @@ TEST(SerializeTest, emitChildNamespaces) {
   NamespaceInfo *ParentB = InfoAsNamespace(Infos[3].get());
   NamespaceInfo ExpectedParentB(EmptySID);
   ExpectedParentB.ChildNamespaces.emplace_back(EmptySID, "B",
-                                               InfoType::IT_namespace);
+                                               InfoType::IT_namespace, "A");
   CheckNamespaceInfo(&ExpectedParentB, ParentB);
 }
 
index 262ba00..efc08fd 100644 (file)
@@ -29,8 +29,9 @@ TEST(YAMLGeneratorTest, emitNamespaceYAML) {
   I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
 
   I.ChildNamespaces.emplace_back(EmptySID, "ChildNamespace",
-                                 InfoType::IT_namespace);
-  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record);
+                                 InfoType::IT_namespace, "path/to/A/Namespace");
+  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
+                              "path/to/A/Namespace");
   I.ChildFunctions.emplace_back();
   I.ChildFunctions.back().Name = "OneFunction";
   I.ChildEnums.emplace_back();
@@ -53,9 +54,11 @@ Namespace:
 ChildNamespaces:
   - Type:            Namespace
     Name:            'ChildNamespace'
+    Path:            'path/to/A/Namespace'
 ChildRecords:
   - Type:            Record
     Name:            'ChildStruct'
+    Path:            'path/to/A/Namespace'
 ChildFunctions:
   - USR:             '0000000000000000000000000000000000000000'
     Name:            'OneFunction'
@@ -71,7 +74,7 @@ ChildEnums:
 TEST(YAMLGeneratorTest, emitRecordYAML) {
   RecordInfo I;
   I.Name = "r";
-  I.Path = "path/to/r";
+  I.Path = "path/to/A";
   I.Namespace.emplace_back(EmptySID, "A", InfoType::IT_namespace);
 
   I.DefLoc = Location(10, llvm::SmallString<16>{"test.cpp"});
@@ -85,7 +88,8 @@ TEST(YAMLGeneratorTest, emitRecordYAML) {
   I.VirtualParents.emplace_back(EmptySID, "G", InfoType::IT_record,
                                 "path/to/G");
 
-  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record);
+  I.ChildRecords.emplace_back(EmptySID, "ChildStruct", InfoType::IT_record,
+                              "path/to/A/r");
   I.ChildFunctions.emplace_back();
   I.ChildFunctions.back().Name = "OneFunction";
   I.ChildEnums.emplace_back();
@@ -101,7 +105,7 @@ TEST(YAMLGeneratorTest, emitRecordYAML) {
       R"raw(---
 USR:             '0000000000000000000000000000000000000000'
 Name:            'r'
-Path:            'path/to/r'
+Path:            'path/to/A'
 Namespace:
   - Type:            Namespace
     Name:            'A'
@@ -129,6 +133,7 @@ VirtualParents:
 ChildRecords:
   - Type:            Record
     Name:            'ChildStruct'
+    Path:            'path/to/A/r'
 ChildFunctions:
   - USR:             '0000000000000000000000000000000000000000'
     Name:            'OneFunction'