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
27namespace clang {
28namespace doc {
29
30CommentKind 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
53llvm::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
85namespace {
86
87const SymbolID EmptySID = SymbolID();
88
89template <typename T>
90llvm::Expected<std::unique_ptr<Info>>
91reduce(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.
104template <typename T>
105int 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
113template <typename T>
114void 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.
129llvm::Expected<std::unique_ptr<Info>>
130mergeInfos(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 case InfoType::IT_concept:
147 return reduce<ConceptInfo>(Values);
148 case InfoType::IT_variable:
149 return reduce<VarInfo>(Values);
150 case InfoType::IT_friend:
151 return reduce<FriendInfo>(Values);
152 case InfoType::IT_default:
153 return llvm::createStringError(EC: llvm::inconvertibleErrorCode(),
154 S: "unexpected info type");
155 }
156 llvm_unreachable("unhandled enumerator");
157}
158
159bool CommentInfo::operator==(const CommentInfo &Other) const {
160 auto FirstCI = std::tie(args: Kind, args: Text, args: Name, args: Direction, args: ParamName, args: CloseName,
161 args: SelfClosing, args: Explicit, args: AttrKeys, args: AttrValues, args: Args);
162 auto SecondCI =
163 std::tie(args: Other.Kind, args: Other.Text, args: Other.Name, args: Other.Direction,
164 args: Other.ParamName, args: Other.CloseName, args: Other.SelfClosing,
165 args: Other.Explicit, args: Other.AttrKeys, args: Other.AttrValues, args: Other.Args);
166
167 if (FirstCI != SecondCI || Children.size() != Other.Children.size())
168 return false;
169
170 return std::equal(first1: Children.begin(), last1: Children.end(), first2: Other.Children.begin(),
171 binary_pred: llvm::deref<std::equal_to<>>{});
172}
173
174bool CommentInfo::operator<(const CommentInfo &Other) const {
175 auto FirstCI = std::tie(args: Kind, args: Text, args: Name, args: Direction, args: ParamName, args: CloseName,
176 args: SelfClosing, args: Explicit, args: AttrKeys, args: AttrValues, args: Args);
177 auto SecondCI =
178 std::tie(args: Other.Kind, args: Other.Text, args: Other.Name, args: Other.Direction,
179 args: Other.ParamName, args: Other.CloseName, args: Other.SelfClosing,
180 args: Other.Explicit, args: Other.AttrKeys, args: Other.AttrValues, args: Other.Args);
181
182 if (FirstCI < SecondCI)
183 return true;
184
185 if (FirstCI == SecondCI) {
186 return std::lexicographical_compare(
187 first1: Children.begin(), last1: Children.end(), first2: Other.Children.begin(),
188 last2: Other.Children.end(), comp: llvm::deref<std::less<>>());
189 }
190
191 return false;
192}
193
194static llvm::SmallString<64>
195calculateRelativeFilePath(const InfoType &Type, const StringRef &Path,
196 const StringRef &Name, const StringRef &CurrentPath) {
197 llvm::SmallString<64> FilePath;
198
199 if (CurrentPath != Path) {
200 // iterate back to the top
201 for (llvm::sys::path::const_iterator I =
202 llvm::sys::path::begin(path: CurrentPath);
203 I != llvm::sys::path::end(path: CurrentPath); ++I)
204 llvm::sys::path::append(path&: FilePath, a: "..");
205 llvm::sys::path::append(path&: FilePath, a: Path);
206 }
207
208 // Namespace references have a Path to the parent namespace, but
209 // the file is actually in the subdirectory for the namespace.
210 if (Type == doc::InfoType::IT_namespace)
211 llvm::sys::path::append(path&: FilePath, a: Name);
212
213 return llvm::sys::path::relative_path(path: FilePath);
214}
215
216llvm::SmallString<64>
217Reference::getRelativeFilePath(const StringRef &CurrentPath) const {
218 return calculateRelativeFilePath(Type: RefType, Path, Name, CurrentPath);
219}
220
221llvm::SmallString<16> Reference::getFileBaseName() const {
222 if (RefType == InfoType::IT_namespace)
223 return llvm::SmallString<16>("index");
224
225 return Name;
226}
227
228llvm::SmallString<64>
229Info::getRelativeFilePath(const StringRef &CurrentPath) const {
230 return calculateRelativeFilePath(Type: IT, Path, Name: extractName(), CurrentPath);
231}
232
233llvm::SmallString<16> Info::getFileBaseName() const {
234 if (IT == InfoType::IT_namespace)
235 return llvm::SmallString<16>("index");
236
237 return extractName();
238}
239
240bool Reference::mergeable(const Reference &Other) {
241 return RefType == Other.RefType && USR == Other.USR;
242}
243
244void Reference::merge(Reference &&Other) {
245 assert(mergeable(Other));
246 if (Name.empty())
247 Name = Other.Name;
248 if (Path.empty())
249 Path = Other.Path;
250}
251
252bool FriendInfo::mergeable(const FriendInfo &Other) {
253 return Ref.USR == Other.Ref.USR && Ref.Name == Other.Ref.Name;
254}
255
256void FriendInfo::merge(FriendInfo &&Other) {
257 assert(mergeable(Other));
258 Ref.merge(Other: std::move(Other.Ref));
259}
260
261void Info::mergeBase(Info &&Other) {
262 assert(mergeable(Other));
263 if (USR == EmptySID)
264 USR = Other.USR;
265 if (Name == "")
266 Name = Other.Name;
267 if (Path == "")
268 Path = Other.Path;
269 if (Namespace.empty())
270 Namespace = std::move(Other.Namespace);
271 // Unconditionally extend the description, since each decl may have a comment.
272 std::move(first: Other.Description.begin(), last: Other.Description.end(),
273 result: std::back_inserter(x&: Description));
274 llvm::sort(C&: Description);
275 auto Last = llvm::unique(R&: Description);
276 Description.erase(first: Last, last: Description.end());
277}
278
279bool Info::mergeable(const Info &Other) {
280 return IT == Other.IT && USR == Other.USR;
281}
282
283void SymbolInfo::merge(SymbolInfo &&Other) {
284 assert(mergeable(Other));
285 if (!DefLoc)
286 DefLoc = std::move(Other.DefLoc);
287 // Unconditionally extend the list of locations, since we want all of them.
288 std::move(first: Other.Loc.begin(), last: Other.Loc.end(), result: std::back_inserter(x&: Loc));
289 llvm::sort(C&: Loc);
290 auto *Last = llvm::unique(R&: Loc);
291 Loc.erase(CS: Last, CE: Loc.end());
292 mergeBase(Other: std::move(Other));
293 if (MangledName.empty())
294 MangledName = std::move(Other.MangledName);
295}
296
297NamespaceInfo::NamespaceInfo(SymbolID USR, StringRef Name, StringRef Path)
298 : Info(InfoType::IT_namespace, USR, Name, Path) {}
299
300void NamespaceInfo::merge(NamespaceInfo &&Other) {
301 assert(mergeable(Other));
302 // Reduce children if necessary.
303 reduceChildren(Children&: Children.Namespaces, ChildrenToMerge: std::move(Other.Children.Namespaces));
304 reduceChildren(Children&: Children.Records, ChildrenToMerge: std::move(Other.Children.Records));
305 reduceChildren(Children&: Children.Functions, ChildrenToMerge: std::move(Other.Children.Functions));
306 reduceChildren(Children&: Children.Enums, ChildrenToMerge: std::move(Other.Children.Enums));
307 reduceChildren(Children&: Children.Typedefs, ChildrenToMerge: std::move(Other.Children.Typedefs));
308 reduceChildren(Children&: Children.Concepts, ChildrenToMerge: std::move(Other.Children.Concepts));
309 reduceChildren(Children&: Children.Variables, ChildrenToMerge: std::move(Other.Children.Variables));
310 mergeBase(Other: std::move(Other));
311}
312
313RecordInfo::RecordInfo(SymbolID USR, StringRef Name, StringRef Path)
314 : SymbolInfo(InfoType::IT_record, USR, Name, Path) {}
315
316void RecordInfo::merge(RecordInfo &&Other) {
317 assert(mergeable(Other));
318 if (!llvm::to_underlying(E: TagType))
319 TagType = Other.TagType;
320 IsTypeDef = IsTypeDef || Other.IsTypeDef;
321 if (Members.empty())
322 Members = std::move(Other.Members);
323 if (Bases.empty())
324 Bases = std::move(Other.Bases);
325 if (Parents.empty())
326 Parents = std::move(Other.Parents);
327 if (VirtualParents.empty())
328 VirtualParents = std::move(Other.VirtualParents);
329 if (Friends.empty())
330 Friends = std::move(Other.Friends);
331 // Reduce children if necessary.
332 reduceChildren(Children&: Children.Records, ChildrenToMerge: std::move(Other.Children.Records));
333 reduceChildren(Children&: Children.Functions, ChildrenToMerge: std::move(Other.Children.Functions));
334 reduceChildren(Children&: Children.Enums, ChildrenToMerge: std::move(Other.Children.Enums));
335 reduceChildren(Children&: Children.Typedefs, ChildrenToMerge: std::move(Other.Children.Typedefs));
336 SymbolInfo::merge(Other: std::move(Other));
337 if (!Template)
338 Template = Other.Template;
339}
340
341void EnumInfo::merge(EnumInfo &&Other) {
342 assert(mergeable(Other));
343 if (!Scoped)
344 Scoped = Other.Scoped;
345 if (Members.empty())
346 Members = std::move(Other.Members);
347 SymbolInfo::merge(Other: std::move(Other));
348}
349
350void FunctionInfo::merge(FunctionInfo &&Other) {
351 assert(mergeable(Other));
352 if (!IsMethod)
353 IsMethod = Other.IsMethod;
354 if (!Access)
355 Access = Other.Access;
356 if (ReturnType.Type.USR == EmptySID && ReturnType.Type.Name == "")
357 ReturnType = std::move(Other.ReturnType);
358 if (Parent.USR == EmptySID && Parent.Name == "")
359 Parent = std::move(Other.Parent);
360 if (Params.empty())
361 Params = std::move(Other.Params);
362 SymbolInfo::merge(Other: std::move(Other));
363 if (!Template)
364 Template = Other.Template;
365}
366
367void TypedefInfo::merge(TypedefInfo &&Other) {
368 assert(mergeable(Other));
369 if (!IsUsing)
370 IsUsing = Other.IsUsing;
371 if (Underlying.Type.Name == "")
372 Underlying = Other.Underlying;
373 SymbolInfo::merge(Other: std::move(Other));
374}
375
376void ConceptInfo::merge(ConceptInfo &&Other) {
377 assert(mergeable(Other));
378 if (!IsType)
379 IsType = Other.IsType;
380 if (ConstraintExpression.empty())
381 ConstraintExpression = std::move(Other.ConstraintExpression);
382 if (Template.Constraints.empty())
383 Template.Constraints = std::move(Other.Template.Constraints);
384 if (Template.Params.empty())
385 Template.Params = std::move(Other.Template.Params);
386 SymbolInfo::merge(Other: std::move(Other));
387}
388
389void VarInfo::merge(VarInfo &&Other) {
390 assert(mergeable(Other));
391 if (!IsStatic)
392 IsStatic = Other.IsStatic;
393 if (Type.Type.USR == EmptySID && Type.Type.Name == "")
394 Type = std::move(Other.Type);
395 SymbolInfo::merge(Other: std::move(Other));
396}
397
398BaseRecordInfo::BaseRecordInfo() : RecordInfo() {}
399
400BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path,
401 bool IsVirtual, AccessSpecifier Access,
402 bool IsParent)
403 : RecordInfo(USR, Name, Path), IsVirtual(IsVirtual), Access(Access),
404 IsParent(IsParent) {}
405
406llvm::SmallString<16> Info::extractName() const {
407 if (!Name.empty())
408 return Name;
409
410 switch (IT) {
411 case InfoType::IT_namespace:
412 // Cover the case where the project contains a base namespace called
413 // 'GlobalNamespace' (i.e. a namespace at the same level as the global
414 // namespace, which would conflict with the hard-coded global namespace name
415 // below.)
416 if (Name == "GlobalNamespace" && Namespace.empty())
417 return llvm::SmallString<16>("@GlobalNamespace");
418 // The case of anonymous namespaces is taken care of in serialization,
419 // so here we can safely assume an unnamed namespace is the global
420 // one.
421 return llvm::SmallString<16>("GlobalNamespace");
422 case InfoType::IT_record:
423 return llvm::SmallString<16>("@nonymous_record_" +
424 toHex(Input: llvm::toStringRef(Input: USR)));
425 case InfoType::IT_enum:
426 return llvm::SmallString<16>("@nonymous_enum_" +
427 toHex(Input: llvm::toStringRef(Input: USR)));
428 case InfoType::IT_typedef:
429 return llvm::SmallString<16>("@nonymous_typedef_" +
430 toHex(Input: llvm::toStringRef(Input: USR)));
431 case InfoType::IT_function:
432 return llvm::SmallString<16>("@nonymous_function_" +
433 toHex(Input: llvm::toStringRef(Input: USR)));
434 case InfoType::IT_concept:
435 return llvm::SmallString<16>("@nonymous_concept_" +
436 toHex(Input: llvm::toStringRef(Input: USR)));
437 case InfoType::IT_variable:
438 return llvm::SmallString<16>("@nonymous_variable_" +
439 toHex(Input: llvm::toStringRef(Input: USR)));
440 case InfoType::IT_friend:
441 return llvm::SmallString<16>("@nonymous_friend_" +
442 toHex(Input: llvm::toStringRef(Input: USR)));
443 case InfoType::IT_default:
444 return llvm::SmallString<16>("@nonymous_" + toHex(Input: llvm::toStringRef(Input: USR)));
445 }
446 llvm_unreachable("Invalid InfoType.");
447 return llvm::SmallString<16>("");
448}
449
450// Order is based on the Name attribute: case insensitive order
451bool Index::operator<(const Index &Other) const {
452 // Loop through each character of both strings
453 for (unsigned I = 0; I < Name.size() && I < Other.Name.size(); ++I) {
454 // Compare them after converting both to lower case
455 int D = tolower(c: Name[I]) - tolower(c: Other.Name[I]);
456 if (D == 0)
457 continue;
458 return D < 0;
459 }
460 // If both strings have the size it means they would be equal if changed to
461 // lower case. In here, lower case will be smaller than upper case
462 // Example: string < stRing = true
463 // This is the opposite of how operator < handles strings
464 if (Name.size() == Other.Name.size())
465 return Name > Other.Name;
466 // If they are not the same size; the shorter string is smaller
467 return Name.size() < Other.Name.size();
468}
469
470void Index::sort() {
471 llvm::sort(C&: Children);
472 for (auto &C : Children)
473 C.sort();
474}
475
476ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx,
477 StringRef ProjectName, bool PublicOnly,
478 StringRef OutDirectory, StringRef SourceRoot,
479 StringRef RepositoryUrl,
480 StringRef RepositoryLinePrefix, StringRef Base,
481 std::vector<std::string> UserStylesheets,
482 bool FTimeTrace)
483 : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly),
484 FTimeTrace(FTimeTrace), OutDirectory(OutDirectory),
485 UserStylesheets(UserStylesheets), Base(Base) {
486 llvm::SmallString<128> SourceRootDir(SourceRoot);
487 if (SourceRoot.empty())
488 // If no SourceRoot was provided the current path is used as the default
489 llvm::sys::fs::current_path(result&: SourceRootDir);
490 this->SourceRoot = std::string(SourceRootDir);
491 if (!RepositoryUrl.empty()) {
492 this->RepositoryUrl = std::string(RepositoryUrl);
493 if (!RepositoryUrl.empty() && !RepositoryUrl.starts_with(Prefix: "http://") &&
494 !RepositoryUrl.starts_with(Prefix: "https://"))
495 this->RepositoryUrl->insert(pos: 0, s: "https://");
496
497 if (!RepositoryLinePrefix.empty())
498 this->RepositoryLinePrefix = std::string(RepositoryLinePrefix);
499 }
500}
501
502void ScopeChildren::sort() {
503 llvm::sort(Start: Namespaces.begin(), End: Namespaces.end());
504 llvm::sort(Start: Records.begin(), End: Records.end());
505 llvm::sort(Start: Functions.begin(), End: Functions.end());
506 llvm::sort(Start: Enums.begin(), End: Enums.end());
507 llvm::sort(Start: Typedefs.begin(), End: Typedefs.end());
508 llvm::sort(Start: Concepts.begin(), End: Concepts.end());
509 llvm::sort(Start: Variables.begin(), End: Variables.end());
510}
511} // namespace doc
512} // namespace clang
513

source code of clang-tools-extra/clang-doc/Representation.cpp