157 lines
5.6 KiB
C++
157 lines
5.6 KiB
C++
//===--- HeaderSourceSwitch.cpp - --------------------------------*- C++-*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "HeaderSourceSwitch.h"
|
|
#include "AST.h"
|
|
#include "Logger.h"
|
|
#include "SourceCode.h"
|
|
#include "index/SymbolCollector.h"
|
|
#include "clang/AST/Decl.h"
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
|
|
llvm::Optional<Path> getCorrespondingHeaderOrSource(
|
|
const Path &OriginalFile,
|
|
llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
|
|
llvm::StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
|
|
".c++", ".m", ".mm"};
|
|
llvm::StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
|
|
|
|
llvm::StringRef PathExt = llvm::sys::path::extension(OriginalFile);
|
|
|
|
// Lookup in a list of known extensions.
|
|
auto SourceIter =
|
|
llvm::find_if(SourceExtensions, [&PathExt](PathRef SourceExt) {
|
|
return SourceExt.equals_lower(PathExt);
|
|
});
|
|
bool IsSource = SourceIter != std::end(SourceExtensions);
|
|
|
|
auto HeaderIter =
|
|
llvm::find_if(HeaderExtensions, [&PathExt](PathRef HeaderExt) {
|
|
return HeaderExt.equals_lower(PathExt);
|
|
});
|
|
bool IsHeader = HeaderIter != std::end(HeaderExtensions);
|
|
|
|
// We can only switch between the known extensions.
|
|
if (!IsSource && !IsHeader)
|
|
return None;
|
|
|
|
// Array to lookup extensions for the switch. An opposite of where original
|
|
// extension was found.
|
|
llvm::ArrayRef<llvm::StringRef> NewExts;
|
|
if (IsSource)
|
|
NewExts = HeaderExtensions;
|
|
else
|
|
NewExts = SourceExtensions;
|
|
|
|
// Storage for the new path.
|
|
llvm::SmallString<128> NewPath = llvm::StringRef(OriginalFile);
|
|
|
|
// Loop through switched extension candidates.
|
|
for (llvm::StringRef NewExt : NewExts) {
|
|
llvm::sys::path::replace_extension(NewPath, NewExt);
|
|
if (VFS->exists(NewPath))
|
|
return NewPath.str().str(); // First str() to convert from SmallString to
|
|
// StringRef, second to convert from StringRef
|
|
// to std::string
|
|
|
|
// Also check NewExt in upper-case, just in case.
|
|
llvm::sys::path::replace_extension(NewPath, NewExt.upper());
|
|
if (VFS->exists(NewPath))
|
|
return NewPath.str().str();
|
|
}
|
|
return None;
|
|
}
|
|
|
|
llvm::Optional<Path> getCorrespondingHeaderOrSource(const Path &OriginalFile,
|
|
ParsedAST &AST,
|
|
const SymbolIndex *Index) {
|
|
if (!Index) {
|
|
// FIXME: use the AST to do the inference.
|
|
return None;
|
|
}
|
|
LookupRequest Request;
|
|
// Find all symbols present in the original file.
|
|
for (const auto *D : getIndexableLocalDecls(AST)) {
|
|
if (auto ID = getSymbolID(D))
|
|
Request.IDs.insert(*ID);
|
|
}
|
|
llvm::StringMap<int> Candidates; // Target path => score.
|
|
auto AwardTarget = [&](const char *TargetURI) {
|
|
if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
|
|
if (*TargetPath != OriginalFile) // exclude the original file.
|
|
++Candidates[*TargetPath];
|
|
} else {
|
|
elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
|
|
}
|
|
};
|
|
// If we switch from a header, we are looking for the implementation
|
|
// file, so we use the definition loc; otherwise we look for the header file,
|
|
// we use the decl loc;
|
|
//
|
|
// For each symbol in the original file, we get its target location (decl or
|
|
// def) from the index, then award that target file.
|
|
bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
|
|
Index->lookup(Request, [&](const Symbol &Sym) {
|
|
if (IsHeader)
|
|
AwardTarget(Sym.Definition.FileURI);
|
|
else
|
|
AwardTarget(Sym.CanonicalDeclaration.FileURI);
|
|
});
|
|
// FIXME: our index doesn't have any interesting information (this could be
|
|
// that the background-index is not finished), we should use the decl/def
|
|
// locations from the AST to do the inference (from .cc to .h).
|
|
if (Candidates.empty())
|
|
return None;
|
|
|
|
// Pickup the winner, who contains most of symbols.
|
|
// FIXME: should we use other signals (file proximity) to help score?
|
|
auto Best = Candidates.begin();
|
|
for (auto It = Candidates.begin(); It != Candidates.end(); ++It) {
|
|
if (It->second > Best->second)
|
|
Best = It;
|
|
else if (It->second == Best->second && It->first() < Best->first())
|
|
// Select the first one in the lexical order if we have multiple
|
|
// candidates.
|
|
Best = It;
|
|
}
|
|
return Path(Best->first());
|
|
}
|
|
|
|
std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
|
|
std::vector<const Decl *> Results;
|
|
std::function<void(Decl *)> TraverseDecl = [&](Decl *D) {
|
|
auto *ND = llvm::dyn_cast<NamedDecl>(D);
|
|
if (!ND || ND->isImplicit())
|
|
return;
|
|
if (!SymbolCollector::shouldCollectSymbol(*ND, D->getASTContext(), {},
|
|
/*IsMainFileSymbol=*/false))
|
|
return;
|
|
if (!llvm::isa<FunctionDecl>(ND)) {
|
|
// Visit the children, but we skip function decls as we are not interested
|
|
// in the function body.
|
|
if (auto *Scope = llvm::dyn_cast<DeclContext>(ND)) {
|
|
for (auto *D : Scope->decls())
|
|
TraverseDecl(D);
|
|
}
|
|
}
|
|
if (llvm::isa<NamespaceDecl>(D))
|
|
return; // namespace is indexable, but we're not interested.
|
|
Results.push_back(D);
|
|
};
|
|
// Traverses the ParsedAST directly to collect all decls present in the main
|
|
// file.
|
|
for (auto *TopLevel : AST.getLocalTopLevelDecls())
|
|
TraverseDecl(TopLevel);
|
|
return Results;
|
|
}
|
|
|
|
} // namespace clangd
|
|
} // namespace clang
|