xref: /llvm-project/clang-tools-extra/clangd/HeaderSourceSwitch.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
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 
getCorrespondingHeaderOrSource(PathRef OriginalFile,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)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 
getCorrespondingHeaderOrSource(PathRef OriginalFile,ParsedAST & AST,const SymbolIndex * Index)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 
getIndexableLocalDecls(ParsedAST & AST)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