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/ADT/StringMap.h" |
24 | #include "llvm/Support/Error.h" |
25 | #include "llvm/Support/Path.h" |
26 | |
27 | namespace clang { |
28 | namespace doc { |
29 | |
30 | CommentKind stringToCommentKind(llvm::StringRef KindStr) { |
31 | static const llvm::StringMap<CommentKind> KindMap = { |
32 | {"FullComment", CommentKind::CK_FullComment}, |
33 | {"ParagraphComment", CommentKind::CK_ParagraphComment}, |
34 | {"TextComment", CommentKind::CK_TextComment}, |
35 | {"InlineCommandComment", CommentKind::CK_InlineCommandComment}, |
36 | {"HTMLStartTagComment", CommentKind::CK_HTMLStartTagComment}, |
37 | {"HTMLEndTagComment", CommentKind::CK_HTMLEndTagComment}, |
38 | {"BlockCommandComment", CommentKind::CK_BlockCommandComment}, |
39 | {"ParamCommandComment", CommentKind::CK_ParamCommandComment}, |
40 | {"TParamCommandComment", CommentKind::CK_TParamCommandComment}, |
41 | {"VerbatimBlockComment", CommentKind::CK_VerbatimBlockComment}, |
42 | {"VerbatimBlockLineComment", CommentKind::CK_VerbatimBlockLineComment}, |
43 | {"VerbatimLineComment", CommentKind::CK_VerbatimLineComment}, |
44 | }; |
45 | |
46 | auto It = KindMap.find(Key: KindStr); |
47 | if (It != KindMap.end()) { |
48 | return It->second; |
49 | } |
50 | return CommentKind::CK_Unknown; |
51 | } |
52 | |
53 | llvm::StringRef commentKindToString(CommentKind Kind) { |
54 | switch (Kind) { |
55 | case CommentKind::CK_FullComment: |
56 | return "FullComment"; |
57 | case CommentKind::CK_ParagraphComment: |
58 | return "ParagraphComment"; |
59 | case CommentKind::CK_TextComment: |
60 | return "TextComment"; |
61 | case CommentKind::CK_InlineCommandComment: |
62 | return "InlineCommandComment"; |
63 | case CommentKind::CK_HTMLStartTagComment: |
64 | return "HTMLStartTagComment"; |
65 | case CommentKind::CK_HTMLEndTagComment: |
66 | return "HTMLEndTagComment"; |
67 | case CommentKind::CK_BlockCommandComment: |
68 | return "BlockCommandComment"; |
69 | case CommentKind::CK_ParamCommandComment: |
70 | return "ParamCommandComment"; |
71 | case CommentKind::CK_TParamCommandComment: |
72 | return "TParamCommandComment"; |
73 | case CommentKind::CK_VerbatimBlockComment: |
74 | return "VerbatimBlockComment"; |
75 | case CommentKind::CK_VerbatimBlockLineComment: |
76 | return "VerbatimBlockLineComment"; |
77 | case CommentKind::CK_VerbatimLineComment: |
78 | return "VerbatimLineComment"; |
79 | case CommentKind::CK_Unknown: |
80 | return "Unknown"; |
81 | } |
82 | llvm_unreachable("Unhandled CommentKind"); |
83 | } |
84 | |
85 | namespace { |
86 | |
87 | const SymbolID EmptySID = SymbolID(); |
88 | |
89 | template <typename T> |
90 | llvm::Expected<std::unique_ptr<Info>> |
91 | reduce(std::vector<std::unique_ptr<Info>> &Values) { |
92 | if (Values.empty() || !Values[0]) |
93 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
94 | S: "no value to reduce"); |
95 | std::unique_ptr<Info> Merged = std::make_unique<T>(Values[0]->USR); |
96 | T *Tmp = static_cast<T *>(Merged.get()); |
97 | for (auto &I : Values) |
98 | Tmp->merge(std::move(*static_cast<T *>(I.get()))); |
99 | return std::move(Merged); |
100 | } |
101 | |
102 | // Return the index of the matching child in the vector, or -1 if merge is not |
103 | // necessary. |
104 | template <typename T> |
105 | int getChildIndexIfExists(std::vector<T> &Children, T &ChildToMerge) { |
106 | for (unsigned long I = 0; I < Children.size(); I++) { |
107 | if (ChildToMerge.USR == Children[I].USR) |
108 | return I; |
109 | } |
110 | return -1; |
111 | } |
112 | |
113 | template <typename T> |
114 | void reduceChildren(std::vector<T> &Children, |
115 | std::vector<T> &&ChildrenToMerge) { |
116 | for (auto &ChildToMerge : ChildrenToMerge) { |
117 | int MergeIdx = getChildIndexIfExists(Children, ChildToMerge); |
118 | if (MergeIdx == -1) { |
119 | Children.push_back(std::move(ChildToMerge)); |
120 | continue; |
121 | } |
122 | Children[MergeIdx].merge(std::move(ChildToMerge)); |
123 | } |
124 | } |
125 | |
126 | } // namespace |
127 | |
128 | // Dispatch function. |
129 | llvm::Expected<std::unique_ptr<Info>> |
130 | mergeInfos(std::vector<std::unique_ptr<Info>> &Values) { |
131 | if (Values.empty() || !Values[0]) |
132 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
133 | S: "no info values to merge"); |
134 | |
135 | switch (Values[0]->IT) { |
136 | case InfoType::IT_namespace: |
137 | return reduce<NamespaceInfo>(Values); |
138 | case InfoType::IT_record: |
139 | return reduce<RecordInfo>(Values); |
140 | case InfoType::IT_enum: |
141 | return reduce<EnumInfo>(Values); |
142 | case InfoType::IT_function: |
143 | return reduce<FunctionInfo>(Values); |
144 | case InfoType::IT_typedef: |
145 | return reduce<TypedefInfo>(Values); |
146 | default: |
147 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
148 | S: "unexpected info type"); |
149 | } |
150 | } |
151 | |
152 | bool CommentInfo::operator==(const CommentInfo &Other) const { |
153 | auto FirstCI = std::tie(args: Kind, args: Text, args: Name, args: Direction, args: ParamName, args: CloseName, |
154 | args: SelfClosing, args: Explicit, args: AttrKeys, args: AttrValues, args: Args); |
155 | auto SecondCI = |
156 | std::tie(args: Other.Kind, args: Other.Text, args: Other.Name, args: Other.Direction, |
157 | args: Other.ParamName, args: Other.CloseName, args: Other.SelfClosing, |
158 | args: Other.Explicit, args: Other.AttrKeys, args: Other.AttrValues, args: Other.Args); |
159 | |
160 | if (FirstCI != SecondCI || Children.size() != Other.Children.size()) |
161 | return false; |
162 | |
163 | return std::equal(first1: Children.begin(), last1: Children.end(), first2: Other.Children.begin(), |
164 | binary_pred: llvm::deref<std::equal_to<>>{}); |
165 | } |
166 | |
167 | bool CommentInfo::operator<(const CommentInfo &Other) const { |
168 | auto FirstCI = std::tie(args: Kind, args: Text, args: Name, args: Direction, args: ParamName, args: CloseName, |
169 | args: SelfClosing, args: Explicit, args: AttrKeys, args: AttrValues, args: Args); |
170 | auto SecondCI = |
171 | std::tie(args: Other.Kind, args: Other.Text, args: Other.Name, args: Other.Direction, |
172 | args: Other.ParamName, args: Other.CloseName, args: Other.SelfClosing, |
173 | args: Other.Explicit, args: Other.AttrKeys, args: Other.AttrValues, args: Other.Args); |
174 | |
175 | if (FirstCI < SecondCI) |
176 | return true; |
177 | |
178 | if (FirstCI == SecondCI) { |
179 | return std::lexicographical_compare( |
180 | first1: Children.begin(), last1: Children.end(), first2: Other.Children.begin(), |
181 | last2: Other.Children.end(), comp: llvm::deref<std::less<>>()); |
182 | } |
183 | |
184 | return false; |
185 | } |
186 | |
187 | static llvm::SmallString<64> |
188 | calculateRelativeFilePath(const InfoType &Type, const StringRef &Path, |
189 | const StringRef &Name, const StringRef &CurrentPath) { |
190 | llvm::SmallString<64> FilePath; |
191 | |
192 | if (CurrentPath != Path) { |
193 | // iterate back to the top |
194 | for (llvm::sys::path::const_iterator I = |
195 | llvm::sys::path::begin(path: CurrentPath); |
196 | I != llvm::sys::path::end(path: CurrentPath); ++I) |
197 | llvm::sys::path::append(path&: FilePath, a: ".."); |
198 | llvm::sys::path::append(path&: FilePath, a: Path); |
199 | } |
200 | |
201 | // Namespace references have a Path to the parent namespace, but |
202 | // the file is actually in the subdirectory for the namespace. |
203 | if (Type == doc::InfoType::IT_namespace) |
204 | llvm::sys::path::append(path&: FilePath, a: Name); |
205 | |
206 | return llvm::sys::path::relative_path(path: FilePath); |
207 | } |
208 | |
209 | llvm::SmallString<64> |
210 | Reference::getRelativeFilePath(const StringRef &CurrentPath) const { |
211 | return calculateRelativeFilePath(Type: RefType, Path, Name, CurrentPath); |
212 | } |
213 | |
214 | llvm::SmallString<16> Reference::getFileBaseName() const { |
215 | if (RefType == InfoType::IT_namespace) |
216 | return llvm::SmallString<16>("index"); |
217 | |
218 | return Name; |
219 | } |
220 | |
221 | llvm::SmallString<64> |
222 | Info::getRelativeFilePath(const StringRef &CurrentPath) const { |
223 | return calculateRelativeFilePath(Type: IT, Path, Name: extractName(), CurrentPath); |
224 | } |
225 | |
226 | llvm::SmallString<16> Info::getFileBaseName() const { |
227 | if (IT == InfoType::IT_namespace) |
228 | return llvm::SmallString<16>("index"); |
229 | |
230 | return extractName(); |
231 | } |
232 | |
233 | bool Reference::mergeable(const Reference &Other) { |
234 | return RefType == Other.RefType && USR == Other.USR; |
235 | } |
236 | |
237 | void Reference::merge(Reference &&Other) { |
238 | assert(mergeable(Other)); |
239 | if (Name.empty()) |
240 | Name = Other.Name; |
241 | if (Path.empty()) |
242 | Path = Other.Path; |
243 | } |
244 | |
245 | void Info::mergeBase(Info &&Other) { |
246 | assert(mergeable(Other)); |
247 | if (USR == EmptySID) |
248 | USR = Other.USR; |
249 | if (Name == "") |
250 | Name = Other.Name; |
251 | if (Path == "") |
252 | Path = Other.Path; |
253 | if (Namespace.empty()) |
254 | Namespace = std::move(Other.Namespace); |
255 | // Unconditionally extend the description, since each decl may have a comment. |
256 | std::move(first: Other.Description.begin(), last: Other.Description.end(), |
257 | result: std::back_inserter(x&: Description)); |
258 | llvm::sort(C&: Description); |
259 | auto Last = llvm::unique(R&: Description); |
260 | Description.erase(first: Last, last: Description.end()); |
261 | } |
262 | |
263 | bool Info::mergeable(const Info &Other) { |
264 | return IT == Other.IT && USR == Other.USR; |
265 | } |
266 | |
267 | void SymbolInfo::merge(SymbolInfo &&Other) { |
268 | assert(mergeable(Other)); |
269 | if (!DefLoc) |
270 | DefLoc = std::move(Other.DefLoc); |
271 | // Unconditionally extend the list of locations, since we want all of them. |
272 | std::move(first: Other.Loc.begin(), last: Other.Loc.end(), result: std::back_inserter(x&: Loc)); |
273 | llvm::sort(C&: Loc); |
274 | auto *Last = llvm::unique(R&: Loc); |
275 | Loc.erase(CS: Last, CE: Loc.end()); |
276 | mergeBase(Other: std::move(Other)); |
277 | } |
278 | |
279 | NamespaceInfo::NamespaceInfo(SymbolID USR, StringRef Name, StringRef Path) |
280 | : Info(InfoType::IT_namespace, USR, Name, Path) {} |
281 | |
282 | void NamespaceInfo::merge(NamespaceInfo &&Other) { |
283 | assert(mergeable(Other)); |
284 | // Reduce children if necessary. |
285 | reduceChildren(Children&: Children.Namespaces, ChildrenToMerge: std::move(Other.Children.Namespaces)); |
286 | reduceChildren(Children&: Children.Records, ChildrenToMerge: std::move(Other.Children.Records)); |
287 | reduceChildren(Children&: Children.Functions, ChildrenToMerge: std::move(Other.Children.Functions)); |
288 | reduceChildren(Children&: Children.Enums, ChildrenToMerge: std::move(Other.Children.Enums)); |
289 | reduceChildren(Children&: Children.Typedefs, ChildrenToMerge: std::move(Other.Children.Typedefs)); |
290 | mergeBase(Other: std::move(Other)); |
291 | } |
292 | |
293 | RecordInfo::RecordInfo(SymbolID USR, StringRef Name, StringRef Path) |
294 | : SymbolInfo(InfoType::IT_record, USR, Name, Path) {} |
295 | |
296 | void RecordInfo::merge(RecordInfo &&Other) { |
297 | assert(mergeable(Other)); |
298 | if (!llvm::to_underlying(E: TagType)) |
299 | TagType = Other.TagType; |
300 | IsTypeDef = IsTypeDef || Other.IsTypeDef; |
301 | if (Members.empty()) |
302 | Members = std::move(Other.Members); |
303 | if (Bases.empty()) |
304 | Bases = std::move(Other.Bases); |
305 | if (Parents.empty()) |
306 | Parents = std::move(Other.Parents); |
307 | if (VirtualParents.empty()) |
308 | VirtualParents = std::move(Other.VirtualParents); |
309 | // Reduce children if necessary. |
310 | reduceChildren(Children&: Children.Records, ChildrenToMerge: std::move(Other.Children.Records)); |
311 | reduceChildren(Children&: Children.Functions, ChildrenToMerge: std::move(Other.Children.Functions)); |
312 | reduceChildren(Children&: Children.Enums, ChildrenToMerge: std::move(Other.Children.Enums)); |
313 | reduceChildren(Children&: Children.Typedefs, ChildrenToMerge: std::move(Other.Children.Typedefs)); |
314 | SymbolInfo::merge(Other: std::move(Other)); |
315 | if (!Template) |
316 | Template = Other.Template; |
317 | } |
318 | |
319 | void EnumInfo::merge(EnumInfo &&Other) { |
320 | assert(mergeable(Other)); |
321 | if (!Scoped) |
322 | Scoped = Other.Scoped; |
323 | if (Members.empty()) |
324 | Members = std::move(Other.Members); |
325 | SymbolInfo::merge(Other: std::move(Other)); |
326 | } |
327 | |
328 | void FunctionInfo::merge(FunctionInfo &&Other) { |
329 | assert(mergeable(Other)); |
330 | if (!IsMethod) |
331 | IsMethod = Other.IsMethod; |
332 | if (!Access) |
333 | Access = Other.Access; |
334 | if (ReturnType.Type.USR == EmptySID && ReturnType.Type.Name == "") |
335 | ReturnType = std::move(Other.ReturnType); |
336 | if (Parent.USR == EmptySID && Parent.Name == "") |
337 | Parent = std::move(Other.Parent); |
338 | if (Params.empty()) |
339 | Params = std::move(Other.Params); |
340 | SymbolInfo::merge(Other: std::move(Other)); |
341 | if (!Template) |
342 | Template = Other.Template; |
343 | } |
344 | |
345 | void TypedefInfo::merge(TypedefInfo &&Other) { |
346 | assert(mergeable(Other)); |
347 | if (!IsUsing) |
348 | IsUsing = Other.IsUsing; |
349 | if (Underlying.Type.Name == "") |
350 | Underlying = Other.Underlying; |
351 | SymbolInfo::merge(Other: std::move(Other)); |
352 | } |
353 | |
354 | BaseRecordInfo::BaseRecordInfo() : RecordInfo() {} |
355 | |
356 | BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path, |
357 | bool IsVirtual, AccessSpecifier Access, |
358 | bool IsParent) |
359 | : RecordInfo(USR, Name, Path), IsVirtual(IsVirtual), Access(Access), |
360 | IsParent(IsParent) {} |
361 | |
362 | llvm::SmallString<16> Info::extractName() const { |
363 | if (!Name.empty()) |
364 | return Name; |
365 | |
366 | switch (IT) { |
367 | case InfoType::IT_namespace: |
368 | // Cover the case where the project contains a base namespace called |
369 | // 'GlobalNamespace' (i.e. a namespace at the same level as the global |
370 | // namespace, which would conflict with the hard-coded global namespace name |
371 | // below.) |
372 | if (Name == "GlobalNamespace"&& Namespace.empty()) |
373 | return llvm::SmallString<16>("@GlobalNamespace"); |
374 | // The case of anonymous namespaces is taken care of in serialization, |
375 | // so here we can safely assume an unnamed namespace is the global |
376 | // one. |
377 | return llvm::SmallString<16>("GlobalNamespace"); |
378 | case InfoType::IT_record: |
379 | return llvm::SmallString<16>("@nonymous_record_"+ |
380 | toHex(Input: llvm::toStringRef(Input: USR))); |
381 | case InfoType::IT_enum: |
382 | return llvm::SmallString<16>("@nonymous_enum_"+ |
383 | toHex(Input: llvm::toStringRef(Input: USR))); |
384 | case InfoType::IT_typedef: |
385 | return llvm::SmallString<16>("@nonymous_typedef_"+ |
386 | toHex(Input: llvm::toStringRef(Input: USR))); |
387 | case InfoType::IT_function: |
388 | return llvm::SmallString<16>("@nonymous_function_"+ |
389 | toHex(Input: llvm::toStringRef(Input: USR))); |
390 | case InfoType::IT_default: |
391 | return llvm::SmallString<16>("@nonymous_"+ toHex(Input: llvm::toStringRef(Input: USR))); |
392 | } |
393 | llvm_unreachable("Invalid InfoType."); |
394 | return llvm::SmallString<16>(""); |
395 | } |
396 | |
397 | // Order is based on the Name attribute: case insensitive order |
398 | bool Index::operator<(const Index &Other) const { |
399 | // Loop through each character of both strings |
400 | for (unsigned I = 0; I < Name.size() && I < Other.Name.size(); ++I) { |
401 | // Compare them after converting both to lower case |
402 | int D = tolower(c: Name[I]) - tolower(c: Other.Name[I]); |
403 | if (D == 0) |
404 | continue; |
405 | return D < 0; |
406 | } |
407 | // If both strings have the size it means they would be equal if changed to |
408 | // lower case. In here, lower case will be smaller than upper case |
409 | // Example: string < stRing = true |
410 | // This is the opposite of how operator < handles strings |
411 | if (Name.size() == Other.Name.size()) |
412 | return Name > Other.Name; |
413 | // If they are not the same size; the shorter string is smaller |
414 | return Name.size() < Other.Name.size(); |
415 | } |
416 | |
417 | void Index::sort() { |
418 | llvm::sort(C&: Children); |
419 | for (auto &C : Children) |
420 | C.sort(); |
421 | } |
422 | |
423 | ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx, |
424 | StringRef ProjectName, bool PublicOnly, |
425 | StringRef OutDirectory, StringRef SourceRoot, |
426 | StringRef RepositoryUrl, |
427 | StringRef RepositoryLinePrefix, StringRef Base, |
428 | std::vector<std::string> UserStylesheets, |
429 | bool FTimeTrace) |
430 | : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly), |
431 | FTimeTrace(FTimeTrace), OutDirectory(OutDirectory), |
432 | UserStylesheets(UserStylesheets), Base(Base) { |
433 | llvm::SmallString<128> SourceRootDir(SourceRoot); |
434 | if (SourceRoot.empty()) |
435 | // If no SourceRoot was provided the current path is used as the default |
436 | llvm::sys::fs::current_path(result&: SourceRootDir); |
437 | this->SourceRoot = std::string(SourceRootDir); |
438 | if (!RepositoryUrl.empty()) { |
439 | this->RepositoryUrl = std::string(RepositoryUrl); |
440 | if (!RepositoryUrl.empty() && !RepositoryUrl.starts_with(Prefix: "http://") && |
441 | !RepositoryUrl.starts_with(Prefix: "https://")) |
442 | this->RepositoryUrl->insert(pos: 0, s: "https://"); |
443 | |
444 | if (!RepositoryLinePrefix.empty()) |
445 | this->RepositoryLinePrefix = std::string(RepositoryLinePrefix); |
446 | } |
447 | } |
448 | |
449 | void ScopeChildren::sort() { |
450 | llvm::sort(Start: Namespaces.begin(), End: Namespaces.end()); |
451 | llvm::sort(Start: Records.begin(), End: Records.end()); |
452 | llvm::sort(Start: Functions.begin(), End: Functions.end()); |
453 | llvm::sort(Start: Enums.begin(), End: Enums.end()); |
454 | llvm::sort(Start: Typedefs.begin(), End: Typedefs.end()); |
455 | } |
456 | } // namespace doc |
457 | } // namespace clang |
458 |
Definitions
- stringToCommentKind
- commentKindToString
- EmptySID
- reduce
- getChildIndexIfExists
- reduceChildren
- mergeInfos
- operator==
- operator<
- calculateRelativeFilePath
- getRelativeFilePath
- getFileBaseName
- getRelativeFilePath
- getFileBaseName
- mergeable
- merge
- mergeBase
- mergeable
- merge
- NamespaceInfo
- merge
- RecordInfo
- merge
- merge
- merge
- merge
- BaseRecordInfo
- BaseRecordInfo
- extractName
- operator<
- sort
- ClangDocContext
Learn to use CMake with our Intro Training
Find out more