brintos

brintos / llvm-project-archived public Read only

0
0
Text · 5.5 KiB · ee4bea1 Raw
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