515 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8//9// This file defines the merging of different types of infos. The data in the10// calling Info is preserved during a merge unless that field is empty or11// default. In that case, the data from the parameter Info is used to replace12// the empty or default data.13//14// For most fields, the first decl seen provides the data. Exceptions to this15// include the location and description fields, which are collections of data on16// all decls related to a given definition. All other fields are ignored in new17// decls unless the first seen decl didn't, for whatever reason, incorporate18// data on that field (e.g. a forward declared class wouldn't have information19// 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(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(llvm::inconvertibleErrorCode(),94 "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 not103// 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} // namespace127 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(llvm::inconvertibleErrorCode(),133 "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(llvm::inconvertibleErrorCode(),154 "unexpected info type");155 }156 llvm_unreachable("unhandled enumerator");157}158 159bool CommentInfo::operator==(const CommentInfo &Other) const {160 auto FirstCI = std::tie(Kind, Text, Name, Direction, ParamName, CloseName,161 SelfClosing, Explicit, AttrKeys, AttrValues, Args);162 auto SecondCI =163 std::tie(Other.Kind, Other.Text, Other.Name, Other.Direction,164 Other.ParamName, Other.CloseName, Other.SelfClosing,165 Other.Explicit, Other.AttrKeys, Other.AttrValues, Other.Args);166 167 if (FirstCI != SecondCI || Children.size() != Other.Children.size())168 return false;169 170 return std::equal(Children.begin(), Children.end(), Other.Children.begin(),171 llvm::deref<std::equal_to<>>{});172}173 174bool CommentInfo::operator<(const CommentInfo &Other) const {175 auto FirstCI = std::tie(Kind, Text, Name, Direction, ParamName, CloseName,176 SelfClosing, Explicit, AttrKeys, AttrValues, Args);177 auto SecondCI =178 std::tie(Other.Kind, Other.Text, Other.Name, Other.Direction,179 Other.ParamName, Other.CloseName, Other.SelfClosing,180 Other.Explicit, Other.AttrKeys, Other.AttrValues, Other.Args);181 182 if (FirstCI < SecondCI)183 return true;184 185 if (FirstCI == SecondCI) {186 return std::lexicographical_compare(187 Children.begin(), Children.end(), Other.Children.begin(),188 Other.Children.end(), 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 top201 for (llvm::sys::path::const_iterator I =202 llvm::sys::path::begin(CurrentPath);203 I != llvm::sys::path::end(CurrentPath); ++I)204 llvm::sys::path::append(FilePath, "..");205 llvm::sys::path::append(FilePath, Path);206 }207 208 // Namespace references have a Path to the parent namespace, but209 // the file is actually in the subdirectory for the namespace.210 if (Type == doc::InfoType::IT_namespace)211 llvm::sys::path::append(FilePath, Name);212 213 return llvm::sys::path::relative_path(FilePath);214}215 216llvm::SmallString<64>217Reference::getRelativeFilePath(const StringRef &CurrentPath) const {218 return calculateRelativeFilePath(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(IT, Path, 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 if (DocumentationFileName.empty())251 DocumentationFileName = Other.DocumentationFileName;252}253 254bool FriendInfo::mergeable(const FriendInfo &Other) {255 return Ref.USR == Other.Ref.USR && Ref.Name == Other.Ref.Name;256}257 258void FriendInfo::merge(FriendInfo &&Other) {259 assert(mergeable(Other));260 Ref.merge(std::move(Other.Ref));261}262 263void Info::mergeBase(Info &&Other) {264 assert(mergeable(Other));265 if (USR == EmptySID)266 USR = Other.USR;267 if (Name == "")268 Name = Other.Name;269 if (Path == "")270 Path = Other.Path;271 if (Namespace.empty())272 Namespace = std::move(Other.Namespace);273 // Unconditionally extend the description, since each decl may have a comment.274 std::move(Other.Description.begin(), Other.Description.end(),275 std::back_inserter(Description));276 llvm::sort(Description);277 auto Last = llvm::unique(Description);278 Description.erase(Last, Description.end());279}280 281bool Info::mergeable(const Info &Other) {282 return IT == Other.IT && USR == Other.USR;283}284 285void SymbolInfo::merge(SymbolInfo &&Other) {286 assert(mergeable(Other));287 if (!DefLoc)288 DefLoc = std::move(Other.DefLoc);289 // Unconditionally extend the list of locations, since we want all of them.290 std::move(Other.Loc.begin(), Other.Loc.end(), std::back_inserter(Loc));291 llvm::sort(Loc);292 auto *Last = llvm::unique(Loc);293 Loc.erase(Last, Loc.end());294 mergeBase(std::move(Other));295 if (MangledName.empty())296 MangledName = std::move(Other.MangledName);297}298 299NamespaceInfo::NamespaceInfo(SymbolID USR, StringRef Name, StringRef Path)300 : Info(InfoType::IT_namespace, USR, Name, Path) {}301 302void NamespaceInfo::merge(NamespaceInfo &&Other) {303 assert(mergeable(Other));304 // Reduce children if necessary.305 reduceChildren(Children.Namespaces, std::move(Other.Children.Namespaces));306 reduceChildren(Children.Records, std::move(Other.Children.Records));307 reduceChildren(Children.Functions, std::move(Other.Children.Functions));308 reduceChildren(Children.Enums, std::move(Other.Children.Enums));309 reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs));310 reduceChildren(Children.Concepts, std::move(Other.Children.Concepts));311 reduceChildren(Children.Variables, std::move(Other.Children.Variables));312 mergeBase(std::move(Other));313}314 315RecordInfo::RecordInfo(SymbolID USR, StringRef Name, StringRef Path)316 : SymbolInfo(InfoType::IT_record, USR, Name, Path) {}317 318void RecordInfo::merge(RecordInfo &&Other) {319 assert(mergeable(Other));320 if (!llvm::to_underlying(TagType))321 TagType = Other.TagType;322 IsTypeDef = IsTypeDef || Other.IsTypeDef;323 if (Members.empty())324 Members = std::move(Other.Members);325 if (Bases.empty())326 Bases = std::move(Other.Bases);327 if (Parents.empty())328 Parents = std::move(Other.Parents);329 if (VirtualParents.empty())330 VirtualParents = std::move(Other.VirtualParents);331 if (Friends.empty())332 Friends = std::move(Other.Friends);333 // Reduce children if necessary.334 reduceChildren(Children.Records, std::move(Other.Children.Records));335 reduceChildren(Children.Functions, std::move(Other.Children.Functions));336 reduceChildren(Children.Enums, std::move(Other.Children.Enums));337 reduceChildren(Children.Typedefs, std::move(Other.Children.Typedefs));338 SymbolInfo::merge(std::move(Other));339 if (!Template)340 Template = Other.Template;341}342 343void EnumInfo::merge(EnumInfo &&Other) {344 assert(mergeable(Other));345 if (!Scoped)346 Scoped = Other.Scoped;347 if (Members.empty())348 Members = std::move(Other.Members);349 SymbolInfo::merge(std::move(Other));350}351 352void FunctionInfo::merge(FunctionInfo &&Other) {353 assert(mergeable(Other));354 if (!IsMethod)355 IsMethod = Other.IsMethod;356 if (!Access)357 Access = Other.Access;358 if (ReturnType.Type.USR == EmptySID && ReturnType.Type.Name == "")359 ReturnType = std::move(Other.ReturnType);360 if (Parent.USR == EmptySID && Parent.Name == "")361 Parent = std::move(Other.Parent);362 if (Params.empty())363 Params = std::move(Other.Params);364 SymbolInfo::merge(std::move(Other));365 if (!Template)366 Template = Other.Template;367}368 369void TypedefInfo::merge(TypedefInfo &&Other) {370 assert(mergeable(Other));371 if (!IsUsing)372 IsUsing = Other.IsUsing;373 if (Underlying.Type.Name == "")374 Underlying = Other.Underlying;375 SymbolInfo::merge(std::move(Other));376}377 378void ConceptInfo::merge(ConceptInfo &&Other) {379 assert(mergeable(Other));380 if (!IsType)381 IsType = Other.IsType;382 if (ConstraintExpression.empty())383 ConstraintExpression = std::move(Other.ConstraintExpression);384 if (Template.Constraints.empty())385 Template.Constraints = std::move(Other.Template.Constraints);386 if (Template.Params.empty())387 Template.Params = std::move(Other.Template.Params);388 SymbolInfo::merge(std::move(Other));389}390 391void VarInfo::merge(VarInfo &&Other) {392 assert(mergeable(Other));393 if (!IsStatic)394 IsStatic = Other.IsStatic;395 if (Type.Type.USR == EmptySID && Type.Type.Name == "")396 Type = std::move(Other.Type);397 SymbolInfo::merge(std::move(Other));398}399 400BaseRecordInfo::BaseRecordInfo() : RecordInfo() {}401 402BaseRecordInfo::BaseRecordInfo(SymbolID USR, StringRef Name, StringRef Path,403 bool IsVirtual, AccessSpecifier Access,404 bool IsParent)405 : RecordInfo(USR, Name, Path), IsVirtual(IsVirtual), Access(Access),406 IsParent(IsParent) {}407 408llvm::SmallString<16> Info::extractName() const {409 if (!Name.empty())410 return Name;411 412 switch (IT) {413 case InfoType::IT_namespace:414 // Cover the case where the project contains a base namespace called415 // 'GlobalNamespace' (i.e. a namespace at the same level as the global416 // namespace, which would conflict with the hard-coded global namespace name417 // below.)418 if (Name == "GlobalNamespace" && Namespace.empty())419 return llvm::SmallString<16>("@GlobalNamespace");420 // The case of anonymous namespaces is taken care of in serialization,421 // so here we can safely assume an unnamed namespace is the global422 // one.423 return llvm::SmallString<16>("GlobalNamespace");424 case InfoType::IT_record:425 return llvm::SmallString<16>("@nonymous_record_" +426 toHex(llvm::toStringRef(USR)));427 case InfoType::IT_enum:428 return llvm::SmallString<16>("@nonymous_enum_" +429 toHex(llvm::toStringRef(USR)));430 case InfoType::IT_typedef:431 return llvm::SmallString<16>("@nonymous_typedef_" +432 toHex(llvm::toStringRef(USR)));433 case InfoType::IT_function:434 return llvm::SmallString<16>("@nonymous_function_" +435 toHex(llvm::toStringRef(USR)));436 case InfoType::IT_concept:437 return llvm::SmallString<16>("@nonymous_concept_" +438 toHex(llvm::toStringRef(USR)));439 case InfoType::IT_variable:440 return llvm::SmallString<16>("@nonymous_variable_" +441 toHex(llvm::toStringRef(USR)));442 case InfoType::IT_friend:443 return llvm::SmallString<16>("@nonymous_friend_" +444 toHex(llvm::toStringRef(USR)));445 case InfoType::IT_default:446 return llvm::SmallString<16>("@nonymous_" + toHex(llvm::toStringRef(USR)));447 }448 llvm_unreachable("Invalid InfoType.");449 return llvm::SmallString<16>("");450}451 452// Order is based on the Name attribute: case insensitive order453bool Index::operator<(const Index &Other) const {454 // Loop through each character of both strings455 for (unsigned I = 0; I < Name.size() && I < Other.Name.size(); ++I) {456 // Compare them after converting both to lower case457 int D = tolower(Name[I]) - tolower(Other.Name[I]);458 if (D == 0)459 continue;460 return D < 0;461 }462 // If both strings have the size it means they would be equal if changed to463 // lower case. In here, lower case will be smaller than upper case464 // Example: string < stRing = true465 // This is the opposite of how operator < handles strings466 if (Name.size() == Other.Name.size())467 return Name > Other.Name;468 // If they are not the same size; the shorter string is smaller469 return Name.size() < Other.Name.size();470}471 472void Index::sort() {473 llvm::sort(Children);474 for (auto &C : Children)475 C.sort();476}477 478ClangDocContext::ClangDocContext(tooling::ExecutionContext *ECtx,479 StringRef ProjectName, bool PublicOnly,480 StringRef OutDirectory, StringRef SourceRoot,481 StringRef RepositoryUrl,482 StringRef RepositoryLinePrefix, StringRef Base,483 std::vector<std::string> UserStylesheets,484 bool FTimeTrace)485 : ECtx(ECtx), ProjectName(ProjectName), PublicOnly(PublicOnly),486 FTimeTrace(FTimeTrace), OutDirectory(OutDirectory),487 UserStylesheets(UserStylesheets), Base(Base) {488 llvm::SmallString<128> SourceRootDir(SourceRoot);489 if (SourceRoot.empty())490 // If no SourceRoot was provided the current path is used as the default491 llvm::sys::fs::current_path(SourceRootDir);492 this->SourceRoot = std::string(SourceRootDir);493 if (!RepositoryUrl.empty()) {494 this->RepositoryUrl = std::string(RepositoryUrl);495 if (!RepositoryUrl.empty() && !RepositoryUrl.starts_with("http://") &&496 !RepositoryUrl.starts_with("https://"))497 this->RepositoryUrl->insert(0, "https://");498 499 if (!RepositoryLinePrefix.empty())500 this->RepositoryLinePrefix = std::string(RepositoryLinePrefix);501 }502}503 504void ScopeChildren::sort() {505 llvm::sort(Namespaces);506 llvm::sort(Records);507 llvm::sort(Functions);508 llvm::sort(Enums);509 llvm::sort(Typedefs);510 llvm::sort(Concepts);511 llvm::sort(Variables);512}513} // namespace doc514} // namespace clang515