xref: /llvm-project/clang-tools-extra/clang-doc/Representation.cpp (revision 5ef2456a438578b0783241a2744efc62d47e5ab6)
1 ///===-- Representation.cpp - ClangDoc Representation -----------*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file defines the merging of different types of infos. The data in the
10 // calling Info is preserved during a merge unless that field is empty or
11 // default. In that case, the data from the parameter Info is used to replace
12 // the empty or default data.
13 //
14 // For most fields, the first decl seen provides the data. Exceptions to this
15 // include the location and description fields, which are collections of data on
16 // all decls related to a given definition. All other fields are ignored in new
17 // decls unless the first seen decl didn't, for whatever reason, incorporate
18 // data on that field (e.g. a forward declared class wouldn't have information
19 // on members on the forward declaration, but would have the class name).
20 //
21 //===----------------------------------------------------------------------===//
22 #include "Representation.h"
23 #include "llvm/Support/Error.h"
24 #include "llvm/Support/Path.h"
25 
26 namespace clang {
27 namespace doc {
28 
29 namespace {
30 
31 const SymbolID EmptySID = SymbolID();
32 
33 template <typename T>
34 llvm::Expected<std::unique_ptr<Info>>
35 reduce(std::vector<std::unique_ptr<Info>> &Values) {
36   if (Values.empty() || !Values[0])
37     return llvm::createStringError(llvm::inconvertibleErrorCode(),
38                                    "no value to reduce");
39   std::unique_ptr<Info> Merged = std::make_unique<T>(Values[0]->USR);
40   T *Tmp = static_cast<T *>(Merged.get());
41   for (auto &I : Values)
42     Tmp->merge(std::move(*static_cast<T *>(I.get())));
43   return std::move(Merged);
44 }
45 
46 // Return the index of the matching child in the vector, or -1 if merge is not
47 // necessary.
48 template <typename T>
49 int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) {
50   for (unsigned long I = 0; I < Children.size(); I++) {
51     if (ChildToMerge.USR == Children[I].USR)
52       return I;
53   }
54   return -1;
55 }
56 
57 template <typename T>
58 void reduceChildren(std::vector<T> &Children,
59                     std::vector<T> &&ChildrenToMerge) {
60   for (auto &ChildToMerge : ChildrenToMerge) {
61     int MergeIdx = getChildIndexIfExists(Children, ChildToMerge);
62     if (MergeIdx == -1) {
63       Children.push_back(std::move(ChildToMerge));
64       continue;
65     }
66     Children[MergeIdx].merge(std::move(ChildToMerge));
67   }
68 }
69 
70 } // namespace
71 
72 // Dispatch function.
73 llvm::Expected<std::unique_ptr<Info>>
74 mergeInfos(std::vector<std::unique_ptr<Info>> &Values) {
75   if (Values.empty() || !Values[0])
76     return llvm::createStringError(llvm::inconvertibleErrorCode(),
77                                    "no info values to merge");
78 
79   switch (Values[0]->IT) {
80   case InfoType::IT_namespace:
81     return reduce<NamespaceInfo>(Values);
82   case InfoType::IT_record:
83     return reduce<RecordInfo>(Values);
84   case InfoType::IT_enum:
85     return reduce<EnumInfo>(Values);
86   case InfoType::IT_function:
87     return reduce<FunctionInfo>(Values);
88   case InfoType::IT_typedef:
89     return reduce<TypedefInfo>(Values);
90   default:
91     return llvm::createStringError(llvm::inconvertibleErrorCode(),
92                                    "unexpected info type");
93   }
94 }
95 
96 bool CommentInfo::operator==(const CommentInfo &Other) const {
97   auto FirstCI = std::tie(Kind, Text, Name, Direction, ParamName, CloseName,
98                           SelfClosing, Explicit, AttrKeys, AttrValues, Args);
99   auto SecondCI =
100       std::tie(Other.Kind, Other.Text, Other.Name, Other.Direction,
101                Other.ParamName, Other.CloseName, Other.SelfClosing,
102                Other.Explicit, Other.AttrKeys, Other.AttrValues, Other.Args);
103 
104   if (FirstCI != SecondCI || Children.size() != Other.Children.size())
105     return false;
106 
107   return std::equal(Children.begin(), Children.end(), Other.Children.begin(),
108                     llvm::deref<std::equal_to<>>{});
109 }
110 
111 bool CommentInfo::operator<(const CommentInfo &Other) const {
112   auto FirstCI = std::tie(Kind, Text, Name, Direction, ParamName, CloseName,
113                           SelfClosing, Explicit, AttrKeys, AttrValues, Args);
114   auto SecondCI =
115       std::tie(Other.Kind, Other.Text, Other.Name, Other.Direction,
116                Other.ParamName, Other.CloseName, Other.SelfClosing,
117                Other.Explicit, Other.AttrKeys, Other.AttrValues, Other.Args);
118 
119   if (FirstCI < SecondCI)
120     return true;
121 
122   if (FirstCI == SecondCI) {
123     return std::lexicographical_compare(
124         Children.begin(), Children.end(), Other.Children.begin(),
125         Other.Children.end(), llvm::deref<std::less<>>());
126   }
127 
128   return false;
129 }
130 
131 static llvm::SmallString<64>
132 calculateRelativeFilePath(const InfoType &Type, const StringRef &Path,
133                           const StringRef &Name, const StringRef &CurrentPath) {
134   llvm::SmallString<64> FilePath;
135 
136   if (CurrentPath != Path) {
137     // iterate back to the top
138     for (llvm::sys::path::const_iterator I =
139              llvm::sys::path::begin(CurrentPath);
140          I != llvm::sys::path::end(CurrentPath); ++I)
141       llvm::sys::path::append(FilePath, "..");
142     llvm::sys::path::append(FilePath, Path);
143   }
144 
145   // Namespace references have a Path to the parent namespace, but
146   // the file is actually in the subdirectory for the namespace.
147   if (Type == doc::InfoType::IT_namespace)
148     llvm::sys::path::append(FilePath, Name);
149 
150   return llvm::sys::path::relative_path(FilePath);
151 }
152 
153 llvm::SmallString<64>
154 Reference::getRelativeFilePath(const StringRef &CurrentPath) const {
155   return calculateRelativeFilePath(RefType, Path, Name, CurrentPath);
156 }
157 
158 llvm::SmallString<16> Reference::getFileBaseName() const {
159   if (RefType == InfoType::IT_namespace)
160     return llvm::SmallString<16>("index");
161 
162   return Name;
163 }
164 
165 llvm::SmallString<64>
166 Info::getRelativeFilePath(const StringRef &CurrentPath) const {
167   return calculateRelativeFilePath(IT, Path, extractName(), CurrentPath);
168 }
169 
170 llvm::SmallString<16> Info::getFileBaseName() const {
171   if (IT == InfoType::IT_namespace)
172     return llvm::SmallString<16>("index");
173 
174   return extractName();
175 }
176 
177 bool Reference::mergeable(const Reference &Other) {
178   return RefType == Other.RefType && USR == Other.USR;
179 }
180 
181 void Reference::merge(Reference &&Other) {
182   assert(mergeable(Other));
183   if (Name.empty())
184     Name = Other.Name;
185   if (Path.empty())
186     Path = Other.Path;
187 }
188 
189 void Info::mergeBase(Info &&Other) {
190   assert(mergeable(Other));
191   if (USR == EmptySID)
192     USR = Other.USR;
193   if (Name == "")
194     Name = Other.Name;
195   if (Path == "")
196     Path = Other.Path;
197   if (Namespace.empty())
198     Namespace = std::move(Other.Namespace);
199   // Unconditionally extend the description, since each decl may have a comment.
200   std::move(Other.Description.begin(), Other.Description.end(),
201             std::back_inserter(Description));
202   llvm::sort(Description);
203   auto Last = std::unique(Description.begin(), Description.end());
204   Description.erase(Last, Description.end());
205 }
206 
207 bool Info::mergeable(const Info &Other) {
208   return IT == Other.IT && USR == Other.USR;
209 }
210 
211 void SymbolInfo::merge(SymbolInfo &&Other) {
212   assert(mergeable(Other));
213   if (!DefLoc)
214     DefLoc = std::move(Other.DefLoc);
215   // Unconditionally extend the list of locations, since we want all of them.
216   std::move(Other.Loc.begin(), Other.Loc.end(), std::back_inserter(Loc));
217   llvm::sort(Loc);
218   auto Last = std::unique(Loc.begin(), Loc.end());
219   Loc.erase(Last, Loc.end());
220   mergeBase(std::move(Other));
221 }
222 
223 NamespaceInfo::NamespaceInfo(SymbolID USR, StringRef Name, StringRef Path)
224     : Info(InfoType::IT_namespace, USR, Name, Path) {}
225 
226 void NamespaceInfo::merge(NamespaceInfo &&Other) {
227   assert(mergeable(Other));
228   // Reduce children if necessary.
229   reduceChildren(Children.Namespaces, std::move(Other.Children.Namespaces));
230   reduceChildren(Children.Records, std::move(Other.Children.Records));
231   reduceChildren(Children.Functions, std::move(Other.Children.Functions));
232   reduceChildren(Children.Enums, std::move(Other.Children.Enums));
233   reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs));
234   mergeBase(std::move(Other));
235 }
236 
237 RecordInfo::RecordInfo(SymbolID USR, StringRef Name, StringRef Path)
238     : SymbolInfo(InfoType::IT_record, USR, Name, Path) {}
239 
240 void RecordInfo::merge(RecordInfo &&Other) {
241   assert(mergeable(Other));
242   if (!llvm::to_underlying(TagType))
243     TagType = Other.TagType;
244   IsTypeDef = IsTypeDef || Other.IsTypeDef;
245   if (Members.empty())
246     Members = std::move(Other.Members);
247   if (Bases.empty())
248     Bases = std::move(Other.Bases);
249   if (Parents.empty())
250     Parents = std::move(Other.Parents);
251   if (VirtualParents.empty())
252     VirtualParents = std::move(Other.VirtualParents);
253   // Reduce children if necessary.
254   reduceChildren(Children.Records, std::move(Other.Children.Records));
255   reduceChildren(Children.Functions, std::move(Other.Children.Functions));
256   reduceChildren(Children.Enums, std::move(Other.Children.Enums));
257   reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs));
258   SymbolInfo::merge(std::move(Other));
259   if (!Template)
260     Template = Other.Template;
261 }
262 
263 void EnumInfo::merge(EnumInfo &&Other) {
264   assert(mergeable(Other));
265   if (!Scoped)
266     Scoped = Other.Scoped;
267   if (Members.empty())
268     Members = std::move(Other.Members);
269   SymbolInfo::merge(std::move(Other));
270 }
271 
272 void FunctionInfo::merge(FunctionInfo &&Other) {
273   assert(mergeable(Other));
274   if (!IsMethod)
275     IsMethod = Other.IsMethod;
276   if (!Access)
277     Access = Other.Access;
278   if (ReturnType.Type.USR == EmptySID && ReturnType.Type.Name == "")
279     ReturnType = std::move(Other.ReturnType);
280   if (Parent.USR == EmptySID && Parent.Name == "")
281     Parent = std::move(Other.Parent);
282   if (Params.empty())
283     Params = std::move(Other.Params);
284   SymbolInfo::merge(std::move(Other));
285   if (!Template)
286     Template = Other.Template;
287 }
288 
289 void TypedefInfo::merge(TypedefInfo &&Other) {
290   assert(mergeable(Other));
291   if (!IsUsing)
292     IsUsing = Other.IsUsing;
293   if (Underlying.Type.Name == "")
294     Underlying = Other.Underlying;
295   SymbolInfo::merge(std::move(Other));
296 }
297 
298 BaseRecordInfo::BaseRecordInfo() : RecordInfo() {}
299 
300 BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path,
301                                bool IsVirtual, AccessSpecifier Access,
302                                bool IsParent)
303     : RecordInfo(USR, Name, Path), IsVirtual(IsVirtual), Access(Access),
304       IsParent(IsParent) {}
305 
306 llvm::SmallString<16> Info::extractName() const {
307   if (!Name.empty())
308     return Name;
309 
310   switch (IT) {
311   case InfoType::IT_namespace:
312     // Cover the case where the project contains a base namespace called
313     // 'GlobalNamespace' (i.e. a namespace at the same level as the global
314     // namespace, which would conflict with the hard-coded global namespace name
315     // below.)
316     if (Name == "GlobalNamespace" && Namespace.empty())
317       return llvm::SmallString<16>("@GlobalNamespace");
318     // The case of anonymous namespaces is taken care of in serialization,
319     // so here we can safely assume an unnamed namespace is the global
320     // one.
321     return llvm::SmallString<16>("GlobalNamespace");
322   case InfoType::IT_record:
323     return llvm::SmallString<16>("@nonymous_record_" +
324                                  toHex(llvm::toStringRef(USR)));
325   case InfoType::IT_enum:
326     return llvm::SmallString<16>("@nonymous_enum_" +
327                                  toHex(llvm::toStringRef(USR)));
328   case InfoType::IT_typedef:
329     return llvm::SmallString<16>("@nonymous_typedef_" +
330                                  toHex(llvm::toStringRef(USR)));
331   case InfoType::IT_function:
332     return llvm::SmallString<16>("@nonymous_function_" +
333                                  toHex(llvm::toStringRef(USR)));
334   case InfoType::IT_default:
335     return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR)));
336   }
337   llvm_unreachable("Invalid InfoType.");
338   return llvm::SmallString<16>("");
339 }
340 
341 // Order is based on the Name attribute: case insensitive order
342 bool Index::operator<(const Index &Other) const {
343   // Loop through each character of both strings
344   for (unsigned I = 0; I < Name.size() && I < Other.Name.size(); ++I) {
345     // Compare them after converting both to lower case
346     int D = tolower(Name[I]) - tolower(Other.Name[I]);
347     if (D == 0)
348       continue;
349     return D < 0;
350   }
351   // If both strings have the size it means they would be equal if changed to
352   // lower case. In here, lower case will be smaller than upper case
353   // Example: string < stRing = true
354   // This is the opposite of how operator < handles strings
355   if (Name.size() == Other.Name.size())
356     return Name > Other.Name;
357   // If they are not the same size; the shorter string is smaller
358   return Name.size() < Other.Name.size();
359 }
360 
361 void Index::sort() {
362   llvm::sort(Children);
363   for (auto &C : Children)
364     C.sort();
365 }
366 
367 ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx,
368                                  StringRef ProjectName, bool PublicOnly,
369                                  StringRef OutDirectory, StringRef SourceRoot,
370                                  StringRef RepositoryUrl,
371                                  std::vector<std::string> UserStylesheets)
372     : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly),
373       OutDirectory(OutDirectory), UserStylesheets(UserStylesheets) {
374   llvm::SmallString<128> SourceRootDir(SourceRoot);
375   if (SourceRoot.empty())
376     // If no SourceRoot was provided the current path is used as the default
377     llvm::sys::fs::current_path(SourceRootDir);
378   this->SourceRoot = std::string(SourceRootDir);
379   if (!RepositoryUrl.empty()) {
380     this->RepositoryUrl = std::string(RepositoryUrl);
381     if (!RepositoryUrl.empty() && !RepositoryUrl.starts_with("http://") &&
382         !RepositoryUrl.starts_with("https://"))
383       this->RepositoryUrl->insert(0, "https://");
384   }
385 }
386 
387 void ScopeChildren::sort() {
388   llvm::sort(Namespaces.begin(), Namespaces.end());
389   llvm::sort(Records.begin(), Records.end());
390   llvm::sort(Functions.begin(), Functions.end());
391   llvm::sort(Enums.begin(), Enums.end());
392   llvm::sort(Typedefs.begin(), Typedefs.end());
393 }
394 } // namespace doc
395 } // namespace clang
396