llvm-project

Форк
0
/
HeaderSourceSwitch.cpp 
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

18
namespace clang {
19
namespace clangd {
20

21
std::optional<Path> getCorrespondingHeaderOrSource(
22
    PathRef OriginalFile, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
23
  llvm::StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
24
                                        ".c++", ".m", ".mm"};
25
  llvm::StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
26

27
  llvm::StringRef PathExt = llvm::sys::path::extension(OriginalFile);
28

29
  // Lookup in a list of known extensions.
30
  bool IsSource = llvm::any_of(SourceExtensions, [&PathExt](PathRef SourceExt) {
31
    return SourceExt.equals_insensitive(PathExt);
32
  });
33

34
  bool IsHeader = llvm::any_of(HeaderExtensions, [&PathExt](PathRef HeaderExt) {
35
    return HeaderExt.equals_insensitive(PathExt);
36
  });
37

38
  // We can only switch between the known extensions.
39
  if (!IsSource && !IsHeader)
40
    return std::nullopt;
41

42
  // Array to lookup extensions for the switch. An opposite of where original
43
  // extension was found.
44
  llvm::ArrayRef<llvm::StringRef> NewExts;
45
  if (IsSource)
46
    NewExts = HeaderExtensions;
47
  else
48
    NewExts = SourceExtensions;
49

50
  // Storage for the new path.
51
  llvm::SmallString<128> NewPath = OriginalFile;
52

53
  // Loop through switched extension candidates.
54
  for (llvm::StringRef NewExt : NewExts) {
55
    llvm::sys::path::replace_extension(NewPath, NewExt);
56
    if (VFS->exists(NewPath))
57
      return Path(NewPath);
58

59
    // Also check NewExt in upper-case, just in case.
60
    llvm::sys::path::replace_extension(NewPath, NewExt.upper());
61
    if (VFS->exists(NewPath))
62
      return Path(NewPath);
63
  }
64
  return std::nullopt;
65
}
66

67
std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
68
                                                   ParsedAST &AST,
69
                                                   const SymbolIndex *Index) {
70
  if (!Index) {
71
    // FIXME: use the AST to do the inference.
72
    return std::nullopt;
73
  }
74
  LookupRequest Request;
75
  // Find all symbols present in the original file.
76
  for (const auto *D : getIndexableLocalDecls(AST)) {
77
    if (auto ID = getSymbolID(D))
78
      Request.IDs.insert(ID);
79
  }
80
  llvm::StringMap<int> Candidates; // Target path => score.
81
  auto AwardTarget = [&](const char *TargetURI) {
82
    if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
83
      if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
84
        ++Candidates[*TargetPath];
85
    } else {
86
      elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
87
    }
88
  };
89
  // If we switch from a header, we are looking for the implementation
90
  // 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 or
94
  // def) from the index, then award that target file.
95
  bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
96
  Index->lookup(Request, [&](const Symbol &Sym) {
97
    if (IsHeader)
98
      AwardTarget(Sym.Definition.FileURI);
99
    else
100
      AwardTarget(Sym.CanonicalDeclaration.FileURI);
101
  });
102
  // FIXME: our index doesn't have any interesting information (this could be
103
  // that the background-index is not finished), we should use the decl/def
104
  // locations from the AST to do the inference (from .cc to .h).
105
  if (Candidates.empty())
106
    return std::nullopt;
107

108
  // Pickup the winner, who contains most of symbols.
109
  // FIXME: should we use other signals (file proximity) to help score?
110
  auto Best = Candidates.begin();
111
  for (auto It = Candidates.begin(); It != Candidates.end(); ++It) {
112
    if (It->second > Best->second)
113
      Best = It;
114
    else if (It->second == Best->second && It->first() < Best->first())
115
      // Select the first one in the lexical order if we have multiple
116
      // candidates.
117
      Best = It;
118
  }
119
  return Path(Best->first());
120
}
121

122
std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
123
  std::vector<const Decl *> Results;
124
  std::function<void(Decl *)> TraverseDecl = [&](Decl *D) {
125
    auto *ND = llvm::dyn_cast<NamedDecl>(D);
126
    if (!ND || ND->isImplicit())
127
      return;
128
    if (!SymbolCollector::shouldCollectSymbol(*ND, D->getASTContext(), {},
129
                                              /*IsMainFileSymbol=*/false))
130
      return;
131
    if (!llvm::isa<FunctionDecl>(ND)) {
132
      // Visit the children, but we skip function decls as we are not interested
133
      // in the function body.
134
      if (auto *Scope = llvm::dyn_cast<DeclContext>(ND)) {
135
        for (auto *D : Scope->decls())
136
          TraverseDecl(D);
137
      }
138
    }
139
    if (llvm::isa<NamespaceDecl>(D))
140
      return; // namespace is indexable, but we're not interested.
141
    Results.push_back(D);
142
  };
143
  // Traverses the ParsedAST directly to collect all decls present in the main
144
  // file.
145
  for (auto *TopLevel : AST.getLocalTopLevelDecls())
146
    TraverseDecl(TopLevel);
147
  return Results;
148
}
149

150
} // namespace clangd
151
} // namespace clang
152

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.