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(EC: llvm::inconvertibleErrorCode(), |
38 | Msg: "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(EC: llvm::inconvertibleErrorCode(), |
77 | Msg: "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(EC: llvm::inconvertibleErrorCode(), |
92 | Msg: "unexpected info type" ); |
93 | } |
94 | } |
95 | |
96 | bool CommentInfo::(const CommentInfo &Other) const { |
97 | auto FirstCI = std::tie(args: Kind, args: Text, args: Name, args: Direction, args: ParamName, args: CloseName, |
98 | args: SelfClosing, args: Explicit, args: AttrKeys, args: AttrValues, args: Args); |
99 | auto SecondCI = |
100 | std::tie(args: Other.Kind, args: Other.Text, args: Other.Name, args: Other.Direction, |
101 | args: Other.ParamName, args: Other.CloseName, args: Other.SelfClosing, |
102 | args: Other.Explicit, args: Other.AttrKeys, args: Other.AttrValues, args: Other.Args); |
103 | |
104 | if (FirstCI != SecondCI || Children.size() != Other.Children.size()) |
105 | return false; |
106 | |
107 | return std::equal(first1: Children.begin(), last1: Children.end(), first2: Other.Children.begin(), |
108 | binary_pred: llvm::deref<std::equal_to<>>{}); |
109 | } |
110 | |
111 | bool CommentInfo::(const CommentInfo &Other) const { |
112 | auto FirstCI = std::tie(args: Kind, args: Text, args: Name, args: Direction, args: ParamName, args: CloseName, |
113 | args: SelfClosing, args: Explicit, args: AttrKeys, args: AttrValues, args: Args); |
114 | auto SecondCI = |
115 | std::tie(args: Other.Kind, args: Other.Text, args: Other.Name, args: Other.Direction, |
116 | args: Other.ParamName, args: Other.CloseName, args: Other.SelfClosing, |
117 | args: Other.Explicit, args: Other.AttrKeys, args: Other.AttrValues, args: Other.Args); |
118 | |
119 | if (FirstCI < SecondCI) |
120 | return true; |
121 | |
122 | if (FirstCI == SecondCI) { |
123 | return std::lexicographical_compare( |
124 | first1: Children.begin(), last1: Children.end(), first2: Other.Children.begin(), |
125 | last2: Other.Children.end(), comp: 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(path: CurrentPath); |
140 | I != llvm::sys::path::end(path: CurrentPath); ++I) |
141 | llvm::sys::path::append(path&: FilePath, a: ".." ); |
142 | llvm::sys::path::append(path&: FilePath, a: 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(path&: FilePath, a: Name); |
149 | |
150 | return llvm::sys::path::relative_path(path: FilePath); |
151 | } |
152 | |
153 | llvm::SmallString<64> |
154 | Reference::getRelativeFilePath(const StringRef &CurrentPath) const { |
155 | return calculateRelativeFilePath(Type: 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(Type: IT, Path, Name: 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(first: Other.Description.begin(), last: Other.Description.end(), |
201 | result: std::back_inserter(x&: Description)); |
202 | llvm::sort(C&: Description); |
203 | auto Last = std::unique(first: Description.begin(), last: Description.end()); |
204 | Description.erase(first: Last, 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(first: Other.Loc.begin(), last: Other.Loc.end(), result: std::back_inserter(x&: Loc)); |
217 | llvm::sort(C&: Loc); |
218 | auto Last = std::unique(first: Loc.begin(), last: Loc.end()); |
219 | Loc.erase(CS: Last, CE: Loc.end()); |
220 | mergeBase(Other: 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&: Children.Namespaces, ChildrenToMerge: std::move(Other.Children.Namespaces)); |
230 | reduceChildren(Children&: Children.Records, ChildrenToMerge: std::move(Other.Children.Records)); |
231 | reduceChildren(Children&: Children.Functions, ChildrenToMerge: std::move(Other.Children.Functions)); |
232 | reduceChildren(Children&: Children.Enums, ChildrenToMerge: std::move(Other.Children.Enums)); |
233 | reduceChildren(Children&: Children.Typedefs, ChildrenToMerge: std::move(Other.Children.Typedefs)); |
234 | mergeBase(Other: 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(E: 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&: Children.Records, ChildrenToMerge: std::move(Other.Children.Records)); |
255 | reduceChildren(Children&: Children.Functions, ChildrenToMerge: std::move(Other.Children.Functions)); |
256 | reduceChildren(Children&: Children.Enums, ChildrenToMerge: std::move(Other.Children.Enums)); |
257 | reduceChildren(Children&: Children.Typedefs, ChildrenToMerge: std::move(Other.Children.Typedefs)); |
258 | SymbolInfo::merge(Other: 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(Other: 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(Other: 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(Other: 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::() 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(Input: llvm::toStringRef(Input: USR))); |
325 | case InfoType::IT_enum: |
326 | return llvm::SmallString<16>("@nonymous_enum_" + |
327 | toHex(Input: llvm::toStringRef(Input: USR))); |
328 | case InfoType::IT_typedef: |
329 | return llvm::SmallString<16>("@nonymous_typedef_" + |
330 | toHex(Input: llvm::toStringRef(Input: USR))); |
331 | case InfoType::IT_function: |
332 | return llvm::SmallString<16>("@nonymous_function_" + |
333 | toHex(Input: llvm::toStringRef(Input: USR))); |
334 | case InfoType::IT_default: |
335 | return llvm::SmallString<16>("@nonymous_" + toHex(Input: llvm::toStringRef(Input: 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(c: Name[I]) - tolower(c: 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(C&: 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 | std::vector<std::string> JsScripts) |
373 | : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly), |
374 | OutDirectory(OutDirectory), UserStylesheets(UserStylesheets), |
375 | JsScripts(JsScripts) { |
376 | llvm::SmallString<128> SourceRootDir(SourceRoot); |
377 | if (SourceRoot.empty()) |
378 | // If no SourceRoot was provided the current path is used as the default |
379 | llvm::sys::fs::current_path(result&: SourceRootDir); |
380 | this->SourceRoot = std::string(SourceRootDir); |
381 | if (!RepositoryUrl.empty()) { |
382 | this->RepositoryUrl = std::string(RepositoryUrl); |
383 | if (!RepositoryUrl.empty() && !RepositoryUrl.starts_with(Prefix: "http://" ) && |
384 | !RepositoryUrl.starts_with(Prefix: "https://" )) |
385 | this->RepositoryUrl->insert(pos: 0, s: "https://" ); |
386 | } |
387 | } |
388 | |
389 | } // namespace doc |
390 | } // namespace clang |
391 | |