//===--- 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 "SourceCode.h" #include "index/SymbolCollector.h" #include "support/Logger.h" #include "clang/AST/Decl.h" namespace clang { namespace clangd { llvm::Optional getCorrespondingHeaderOrSource( const Path &OriginalFile, llvm::IntrusiveRefCntPtr 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 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 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 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(std::string(Best->first())); } std::vector getIndexableLocalDecls(ParsedAST &AST) { std::vector Results; std::function TraverseDecl = [&](Decl *D) { auto *ND = llvm::dyn_cast(D); if (!ND || ND->isImplicit()) return; if (!SymbolCollector::shouldCollectSymbol(*ND, D->getASTContext(), {}, /*IsMainFileSymbol=*/false)) return; if (!llvm::isa(ND)) { // Visit the children, but we skip function decls as we are not interested // in the function body. if (auto *Scope = llvm::dyn_cast(ND)) { for (auto *D : Scope->decls()) TraverseDecl(D); } } if (llvm::isa(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