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