workflows: Fix typo from 2d3d0f50ceb938c155a7283e684f28190d24d6ba
[llvm-project.git] / clang-tools-extra / clangd / HeaderSourceSwitch.cpp
blob039457dff72d6502e0fddcadb17a3f25b6865157
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 //===----------------------------------------------------------------------===//
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"
17 namespace clang {
18 namespace clangd {
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"};
26 llvm::StringRef PathExt = llvm::sys::path::extension(OriginalFile);
28 // Lookup in a list of known extensions.
29 bool IsSource = llvm::any_of(SourceExtensions, [&PathExt](PathRef SourceExt) {
30 return SourceExt.equals_insensitive(PathExt);
31 });
33 bool IsHeader = llvm::any_of(HeaderExtensions, [&PathExt](PathRef HeaderExt) {
34 return HeaderExt.equals_insensitive(PathExt);
35 });
37 // We can only switch between the known extensions.
38 if (!IsSource && !IsHeader)
39 return None;
41 // Array to lookup extensions for the switch. An opposite of where original
42 // extension was found.
43 llvm::ArrayRef<llvm::StringRef> NewExts;
44 if (IsSource)
45 NewExts = HeaderExtensions;
46 else
47 NewExts = SourceExtensions;
49 // Storage for the new path.
50 llvm::SmallString<128> NewPath = OriginalFile;
52 // Loop through switched extension candidates.
53 for (llvm::StringRef NewExt : NewExts) {
54 llvm::sys::path::replace_extension(NewPath, NewExt);
55 if (VFS->exists(NewPath))
56 return Path(NewPath);
58 // Also check NewExt in upper-case, just in case.
59 llvm::sys::path::replace_extension(NewPath, NewExt.upper());
60 if (VFS->exists(NewPath))
61 return Path(NewPath);
63 return None;
66 llvm::Optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
67 ParsedAST &AST,
68 const SymbolIndex *Index) {
69 if (!Index) {
70 // FIXME: use the AST to do the inference.
71 return None;
73 LookupRequest Request;
74 // Find all symbols present in the original file.
75 for (const auto *D : getIndexableLocalDecls(AST)) {
76 if (auto ID = getSymbolID(D))
77 Request.IDs.insert(ID);
79 llvm::StringMap<int> Candidates; // Target path => score.
80 auto AwardTarget = [&](const char *TargetURI) {
81 if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
82 if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
83 ++Candidates[*TargetPath];
84 } else {
85 elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
88 // If we switch from a header, we are looking for the implementation
89 // file, so we use the definition loc; otherwise we look for the header file,
90 // we use the decl loc;
92 // For each symbol in the original file, we get its target location (decl or
93 // def) from the index, then award that target file.
94 bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
95 Index->lookup(Request, [&](const Symbol &Sym) {
96 if (IsHeader)
97 AwardTarget(Sym.Definition.FileURI);
98 else
99 AwardTarget(Sym.CanonicalDeclaration.FileURI);
101 // FIXME: our index doesn't have any interesting information (this could be
102 // that the background-index is not finished), we should use the decl/def
103 // locations from the AST to do the inference (from .cc to .h).
104 if (Candidates.empty())
105 return None;
107 // Pickup the winner, who contains most of symbols.
108 // FIXME: should we use other signals (file proximity) to help score?
109 auto Best = Candidates.begin();
110 for (auto It = Candidates.begin(); It != Candidates.end(); ++It) {
111 if (It->second > Best->second)
112 Best = It;
113 else if (It->second == Best->second && It->first() < Best->first())
114 // Select the first one in the lexical order if we have multiple
115 // candidates.
116 Best = It;
118 return Path(Best->first());
121 std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
122 std::vector<const Decl *> Results;
123 std::function<void(Decl *)> TraverseDecl = [&](Decl *D) {
124 auto *ND = llvm::dyn_cast<NamedDecl>(D);
125 if (!ND || ND->isImplicit())
126 return;
127 if (!SymbolCollector::shouldCollectSymbol(*ND, D->getASTContext(), {},
128 /*IsMainFileSymbol=*/false))
129 return;
130 if (!llvm::isa<FunctionDecl>(ND)) {
131 // Visit the children, but we skip function decls as we are not interested
132 // in the function body.
133 if (auto *Scope = llvm::dyn_cast<DeclContext>(ND)) {
134 for (auto *D : Scope->decls())
135 TraverseDecl(D);
138 if (llvm::isa<NamespaceDecl>(D))
139 return; // namespace is indexable, but we're not interested.
140 Results.push_back(D);
142 // Traverses the ParsedAST directly to collect all decls present in the main
143 // file.
144 for (auto *TopLevel : AST.getLocalTopLevelDecls())
145 TraverseDecl(TopLevel);
146 return Results;
149 } // namespace clangd
150 } // namespace clang