1 //===--- HeaderSourceSwitch.cpp - --------------------------------*- 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 #include "HeaderSourceSwitch.h" 10 #include "AST.h" 11 #include "SourceCode.h" 12 #include "index/SymbolCollector.h" 13 #include "support/Logger.h" 14 #include "support/Path.h" 15 #include "clang/AST/Decl.h" 16 17 namespace clang { 18 namespace clangd { 19 20 llvm::Optional<Path> getCorrespondingHeaderOrSource( 21 PathRef OriginalFile, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) { 22 llvm::StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx", 23 ".c++", ".m", ".mm"}; 24 llvm::StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"}; 25 26 llvm::StringRef PathExt = llvm::sys::path::extension(OriginalFile); 27 28 // Lookup in a list of known extensions. 29 auto *SourceIter = 30 llvm::find_if(SourceExtensions, [&PathExt](PathRef SourceExt) { 31 return SourceExt.equals_insensitive(PathExt); 32 }); 33 bool IsSource = SourceIter != std::end(SourceExtensions); 34 35 auto *HeaderIter = 36 llvm::find_if(HeaderExtensions, [&PathExt](PathRef HeaderExt) { 37 return HeaderExt.equals_insensitive(PathExt); 38 }); 39 bool IsHeader = HeaderIter != std::end(HeaderExtensions); 40 41 // We can only switch between the known extensions. 42 if (!IsSource && !IsHeader) 43 return None; 44 45 // Array to lookup extensions for the switch. An opposite of where original 46 // extension was found. 47 llvm::ArrayRef<llvm::StringRef> NewExts; 48 if (IsSource) 49 NewExts = HeaderExtensions; 50 else 51 NewExts = SourceExtensions; 52 53 // Storage for the new path. 54 llvm::SmallString<128> NewPath = OriginalFile; 55 56 // Loop through switched extension candidates. 57 for (llvm::StringRef NewExt : NewExts) { 58 llvm::sys::path::replace_extension(NewPath, NewExt); 59 if (VFS->exists(NewPath)) 60 return Path(NewPath); 61 62 // Also check NewExt in upper-case, just in case. 63 llvm::sys::path::replace_extension(NewPath, NewExt.upper()); 64 if (VFS->exists(NewPath)) 65 return Path(NewPath); 66 } 67 return None; 68 } 69 70 llvm::Optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile, 71 ParsedAST &AST, 72 const SymbolIndex *Index) { 73 if (!Index) { 74 // FIXME: use the AST to do the inference. 75 return None; 76 } 77 LookupRequest Request; 78 // Find all symbols present in the original file. 79 for (const auto *D : getIndexableLocalDecls(AST)) { 80 if (auto ID = getSymbolID(D)) 81 Request.IDs.insert(ID); 82 } 83 llvm::StringMap<int> Candidates; // Target path => score. 84 auto AwardTarget = [&](const char *TargetURI) { 85 if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) { 86 if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file. 87 ++Candidates[*TargetPath]; 88 } else { 89 elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError()); 90 } 91 }; 92 // If we switch from a header, we are looking for the implementation 93 // file, so we use the definition loc; otherwise we look for the header file, 94 // we use the decl loc; 95 // 96 // For each symbol in the original file, we get its target location (decl or 97 // def) from the index, then award that target file. 98 bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts()); 99 Index->lookup(Request, [&](const Symbol &Sym) { 100 if (IsHeader) 101 AwardTarget(Sym.Definition.FileURI); 102 else 103 AwardTarget(Sym.CanonicalDeclaration.FileURI); 104 }); 105 // FIXME: our index doesn't have any interesting information (this could be 106 // that the background-index is not finished), we should use the decl/def 107 // locations from the AST to do the inference (from .cc to .h). 108 if (Candidates.empty()) 109 return None; 110 111 // Pickup the winner, who contains most of symbols. 112 // FIXME: should we use other signals (file proximity) to help score? 113 auto Best = Candidates.begin(); 114 for (auto It = Candidates.begin(); It != Candidates.end(); ++It) { 115 if (It->second > Best->second) 116 Best = It; 117 else if (It->second == Best->second && It->first() < Best->first()) 118 // Select the first one in the lexical order if we have multiple 119 // candidates. 120 Best = It; 121 } 122 return Path(Best->first()); 123 } 124 125 std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) { 126 std::vector<const Decl *> Results; 127 std::function<void(Decl *)> TraverseDecl = [&](Decl *D) { 128 auto *ND = llvm::dyn_cast<NamedDecl>(D); 129 if (!ND || ND->isImplicit()) 130 return; 131 if (!SymbolCollector::shouldCollectSymbol(*ND, D->getASTContext(), {}, 132 /*IsMainFileSymbol=*/false)) 133 return; 134 if (!llvm::isa<FunctionDecl>(ND)) { 135 // Visit the children, but we skip function decls as we are not interested 136 // in the function body. 137 if (auto *Scope = llvm::dyn_cast<DeclContext>(ND)) { 138 for (auto *D : Scope->decls()) 139 TraverseDecl(D); 140 } 141 } 142 if (llvm::isa<NamespaceDecl>(D)) 143 return; // namespace is indexable, but we're not interested. 144 Results.push_back(D); 145 }; 146 // Traverses the ParsedAST directly to collect all decls present in the main 147 // file. 148 for (auto *TopLevel : AST.getLocalTopLevelDecls()) 149 TraverseDecl(TopLevel); 150 return Results; 151 } 152 153 } // namespace clangd 154 } // namespace clang 155