clang-tools  15.0.0git
HeaderSourceSwitch.cpp
Go to the documentation of this file.
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 
17 namespace clang {
18 namespace clangd {
19 
20 llvm::Optional<Path> getCorrespondingHeaderOrSource(
21  PathRef OriginalFile, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
22  llvm::StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
23  ".c++", ".m", ".mm"};
24  llvm::StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
25 
26  llvm::StringRef PathExt = llvm::sys::path::extension(OriginalFile);
27 
28  // Lookup in a list of known extensions.
29  auto *SourceIter =
30  llvm::find_if(SourceExtensions, [&PathExt](PathRef SourceExt) {
31  return SourceExt.equals_insensitive(PathExt);
32  });
33  bool IsSource = SourceIter != std::end(SourceExtensions);
34 
35  auto *HeaderIter =
36  llvm::find_if(HeaderExtensions, [&PathExt](PathRef HeaderExt) {
37  return HeaderExt.equals_insensitive(PathExt);
38  });
39  bool IsHeader = HeaderIter != std::end(HeaderExtensions);
40 
41  // We can only switch between the known extensions.
42  if (!IsSource && !IsHeader)
43  return None;
44 
45  // Array to lookup extensions for the switch. An opposite of where original
46  // extension was found.
47  llvm::ArrayRef<llvm::StringRef> NewExts;
48  if (IsSource)
49  NewExts = HeaderExtensions;
50  else
51  NewExts = SourceExtensions;
52 
53  // Storage for the new path.
54  llvm::SmallString<128> NewPath = OriginalFile;
55 
56  // Loop through switched extension candidates.
57  for (llvm::StringRef NewExt : NewExts) {
58  llvm::sys::path::replace_extension(NewPath, NewExt);
59  if (VFS->exists(NewPath))
60  return Path(NewPath);
61 
62  // Also check NewExt in upper-case, just in case.
63  llvm::sys::path::replace_extension(NewPath, NewExt.upper());
64  if (VFS->exists(NewPath))
65  return Path(NewPath);
66  }
67  return None;
68 }
69 
70 llvm::Optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
71  ParsedAST &AST,
72  const SymbolIndex *Index) {
73  if (!Index) {
74  // FIXME: use the AST to do the inference.
75  return None;
76  }
77  LookupRequest Request;
78  // Find all symbols present in the original file.
79  for (const auto *D : getIndexableLocalDecls(AST)) {
80  if (auto ID = getSymbolID(D))
81  Request.IDs.insert(ID);
82  }
83  llvm::StringMap<int> Candidates; // Target path => score.
84  auto AwardTarget = [&](const char *TargetURI) {
85  if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
86  if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
87  ++Candidates[*TargetPath];
88  } else {
89  elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
90  }
91  };
92  // If we switch from a header, we are looking for the implementation
93  // file, so we use the definition loc; otherwise we look for the header file,
94  // we use the decl loc;
95  //
96  // For each symbol in the original file, we get its target location (decl or
97  // def) from the index, then award that target file.
98  bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
99  Index->lookup(Request, [&](const Symbol &Sym) {
100  if (IsHeader)
101  AwardTarget(Sym.Definition.FileURI);
102  else
103  AwardTarget(Sym.CanonicalDeclaration.FileURI);
104  });
105  // FIXME: our index doesn't have any interesting information (this could be
106  // that the background-index is not finished), we should use the decl/def
107  // locations from the AST to do the inference (from .cc to .h).
108  if (Candidates.empty())
109  return None;
110 
111  // Pickup the winner, who contains most of symbols.
112  // FIXME: should we use other signals (file proximity) to help score?
113  auto Best = Candidates.begin();
114  for (auto It = Candidates.begin(); It != Candidates.end(); ++It) {
115  if (It->second > Best->second)
116  Best = It;
117  else if (It->second == Best->second && It->first() < Best->first())
118  // Select the first one in the lexical order if we have multiple
119  // candidates.
120  Best = It;
121  }
122  return Path(Best->first());
123 }
124 
125 std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
126  std::vector<const Decl *> Results;
127  std::function<void(Decl *)> TraverseDecl = [&](Decl *D) {
128  auto *ND = llvm::dyn_cast<NamedDecl>(D);
129  if (!ND || ND->isImplicit())
130  return;
131  if (!SymbolCollector::shouldCollectSymbol(*ND, D->getASTContext(), {},
132  /*IsMainFileSymbol=*/false))
133  return;
134  if (!llvm::isa<FunctionDecl>(ND)) {
135  // Visit the children, but we skip function decls as we are not interested
136  // in the function body.
137  if (auto *Scope = llvm::dyn_cast<DeclContext>(ND)) {
138  for (auto *D : Scope->decls())
139  TraverseDecl(D);
140  }
141  }
142  if (llvm::isa<NamespaceDecl>(D))
143  return; // namespace is indexable, but we're not interested.
144  Results.push_back(D);
145  };
146  // Traverses the ParsedAST directly to collect all decls present in the main
147  // file.
148  for (auto *TopLevel : AST.getLocalTopLevelDecls())
149  TraverseDecl(TopLevel);
150  return Results;
151 }
152 
153 } // namespace clangd
154 } // namespace clang
clang::clangd::Path
std::string Path
A typedef to represent a file path.
Definition: Path.h:26
Path.h
clang::clangd::SymbolCollector::shouldCollectSymbol
static bool shouldCollectSymbol(const NamedDecl &ND, const ASTContext &ASTCtx, const Options &Opts, bool IsMainFileSymbol)
Returns true is ND should be collected.
Definition: SymbolCollector.cpp:466
clang::clangd::pathEqual
bool pathEqual(PathRef A, PathRef B)
Definition: Path.cpp:19
HeaderSourceSwitch.h
clang::clangd::ParsedAST::getLangOpts
const LangOptions & getLangOpts() const
Definition: ParsedAST.h:80
clang::clangd::URI::resolve
static llvm::Expected< std::string > resolve(const URI &U, llvm::StringRef HintPath="")
Resolves the absolute path of U.
Definition: URI.cpp:245
ns1::ns2::D
@ D
Definition: CategoricalFeature.h:3
clang::clangd::SymbolIndex::lookup
virtual void lookup(const LookupRequest &Req, llvm::function_ref< void(const Symbol &)> Callback) const =0
Looks up symbols with any of the given symbol IDs and applies Callback on each matched symbol.
Decl
const FunctionDecl * Decl
Definition: AvoidBindCheck.cpp:100
Logger.h
clang::clangd::Symbol
The class presents a C++ symbol, e.g.
Definition: Symbol.h:36
clang::clangd::SymbolLocation::FileURI
const char * FileURI
Definition: SymbolLocation.h:64
Results
std::vector< CodeCompletionResult > Results
Definition: CodeComplete.cpp:784
clang::clangd::Symbol::CanonicalDeclaration
SymbolLocation CanonicalDeclaration
The location of the preferred declaration of the symbol.
Definition: Symbol.h:56
clang::clangd::getIndexableLocalDecls
std::vector< const Decl * > getIndexableLocalDecls(ParsedAST &AST)
Returns all indexable decls that are present in the main file of the AST.
Definition: HeaderSourceSwitch.cpp:125
clang::clangd::LookupRequest::IDs
llvm::DenseSet< SymbolID > IDs
Definition: Index.h:65
clang::clangd::getSymbolID
SymbolID getSymbolID(const Decl *D)
Gets the symbol ID for a declaration. Returned SymbolID might be null.
Definition: AST.cpp:337
SourceCode.h
Index
const SymbolIndex * Index
Definition: Dexp.cpp:98
clang::clangd::SymbolIndex
Interface for symbol indexes that can be used for searching or matching symbols among a set of symbol...
Definition: Index.h:113
clang::clangd::LookupRequest
Definition: Index.h:64
clang::clangd::isHeaderFile
bool isHeaderFile(llvm::StringRef FileName, llvm::Optional< LangOptions > LangOpts)
Infers whether this is a header from the FileName and LangOpts (if presents).
Definition: SourceCode.cpp:1158
ID
static char ID
Definition: Logger.cpp:74
clang::clangd::PathRef
llvm::StringRef PathRef
A typedef to represent a ref to file path.
Definition: Path.h:29
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
clang::clangd::ParsedAST::getLocalTopLevelDecls
ArrayRef< Decl * > getLocalTopLevelDecls()
This function returns top-level decls present in the main file of the AST.
Definition: ParsedAST.cpp:715
clang::clangd::getCorrespondingHeaderOrSource
llvm::Optional< Path > getCorrespondingHeaderOrSource(PathRef OriginalFile, llvm::IntrusiveRefCntPtr< llvm::vfs::FileSystem > VFS)
Given a header file, returns the best matching source file, and vice visa.
Definition: HeaderSourceSwitch.cpp:20
SymbolCollector.h
clang::clangd::Symbol::Definition
SymbolLocation Definition
The location of the symbol's definition, if one was found.
Definition: Symbol.h:47
clang::clangd::ParsedAST
Stores and provides access to parsed AST.
Definition: ParsedAST.h:45
clang::clangd::elog
void elog(const char *Fmt, Ts &&... Vals)
Definition: Logger.h:61
AST.h