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