xref: /llvm-project/clang-tools-extra/clangd/HeaderSourceSwitch.cpp (revision f71ffd3b735b4d6ae3c12be1806cdd6205b3b378)
1e259a5fdSHaojian Wu //===--- HeaderSourceSwitch.cpp - --------------------------------*- C++-*-===//
2e259a5fdSHaojian Wu //
3e259a5fdSHaojian Wu // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4e259a5fdSHaojian Wu // See https://llvm.org/LICENSE.txt for license information.
5e259a5fdSHaojian Wu // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6e259a5fdSHaojian Wu //
7e259a5fdSHaojian Wu //===----------------------------------------------------------------------===//
8e259a5fdSHaojian Wu 
9e259a5fdSHaojian Wu #include "HeaderSourceSwitch.h"
10fdbe5b4bSHaojian Wu #include "AST.h"
11b221c9d0SHaojian Wu #include "SourceCode.h"
12fdbe5b4bSHaojian Wu #include "index/SymbolCollector.h"
13ad97ccf6SSam McCall #include "support/Logger.h"
14164a10dcSKadir Cetinkaya #include "support/Path.h"
15fdbe5b4bSHaojian Wu #include "clang/AST/Decl.h"
1671f55735SKazu Hirata #include <optional>
17e259a5fdSHaojian Wu 
18e259a5fdSHaojian Wu namespace clang {
19e259a5fdSHaojian Wu namespace clangd {
20e259a5fdSHaojian Wu 
getCorrespondingHeaderOrSource(PathRef OriginalFile,llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS)21*f71ffd3bSKazu Hirata std::optional<Path> getCorrespondingHeaderOrSource(
223505d8dcSNathan James     PathRef OriginalFile, llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> VFS) {
23e259a5fdSHaojian Wu   llvm::StringRef SourceExtensions[] = {".cpp", ".c", ".cc", ".cxx",
24e259a5fdSHaojian Wu                                         ".c++", ".m", ".mm"};
25e259a5fdSHaojian Wu   llvm::StringRef HeaderExtensions[] = {".h", ".hh", ".hpp", ".hxx", ".inc"};
26e259a5fdSHaojian Wu 
27e259a5fdSHaojian Wu   llvm::StringRef PathExt = llvm::sys::path::extension(OriginalFile);
28e259a5fdSHaojian Wu 
29e259a5fdSHaojian Wu   // Lookup in a list of known extensions.
3012b29900SKazu Hirata   bool IsSource = llvm::any_of(SourceExtensions, [&PathExt](PathRef SourceExt) {
3186029e4cSMartin Storsjö     return SourceExt.equals_insensitive(PathExt);
32e259a5fdSHaojian Wu   });
33e259a5fdSHaojian Wu 
3412b29900SKazu Hirata   bool IsHeader = llvm::any_of(HeaderExtensions, [&PathExt](PathRef HeaderExt) {
3586029e4cSMartin Storsjö     return HeaderExt.equals_insensitive(PathExt);
36e259a5fdSHaojian Wu   });
37e259a5fdSHaojian Wu 
38e259a5fdSHaojian Wu   // We can only switch between the known extensions.
39e259a5fdSHaojian Wu   if (!IsSource && !IsHeader)
40059a23c0SKazu Hirata     return std::nullopt;
41e259a5fdSHaojian Wu 
42e259a5fdSHaojian Wu   // Array to lookup extensions for the switch. An opposite of where original
43e259a5fdSHaojian Wu   // extension was found.
44e259a5fdSHaojian Wu   llvm::ArrayRef<llvm::StringRef> NewExts;
45e259a5fdSHaojian Wu   if (IsSource)
46e259a5fdSHaojian Wu     NewExts = HeaderExtensions;
47e259a5fdSHaojian Wu   else
48e259a5fdSHaojian Wu     NewExts = SourceExtensions;
49e259a5fdSHaojian Wu 
50e259a5fdSHaojian Wu   // Storage for the new path.
513505d8dcSNathan James   llvm::SmallString<128> NewPath = OriginalFile;
52e259a5fdSHaojian Wu 
53e259a5fdSHaojian Wu   // Loop through switched extension candidates.
54e259a5fdSHaojian Wu   for (llvm::StringRef NewExt : NewExts) {
55e259a5fdSHaojian Wu     llvm::sys::path::replace_extension(NewPath, NewExt);
56e259a5fdSHaojian Wu     if (VFS->exists(NewPath))
573505d8dcSNathan James       return Path(NewPath);
58e259a5fdSHaojian Wu 
59e259a5fdSHaojian Wu     // Also check NewExt in upper-case, just in case.
60e259a5fdSHaojian Wu     llvm::sys::path::replace_extension(NewPath, NewExt.upper());
61e259a5fdSHaojian Wu     if (VFS->exists(NewPath))
623505d8dcSNathan James       return Path(NewPath);
63e259a5fdSHaojian Wu   }
64059a23c0SKazu Hirata   return std::nullopt;
65e259a5fdSHaojian Wu }
66e259a5fdSHaojian Wu 
getCorrespondingHeaderOrSource(PathRef OriginalFile,ParsedAST & AST,const SymbolIndex * Index)67*f71ffd3bSKazu Hirata std::optional<Path> getCorrespondingHeaderOrSource(PathRef OriginalFile,
68fdbe5b4bSHaojian Wu                                                    ParsedAST &AST,
69fdbe5b4bSHaojian Wu                                                    const SymbolIndex *Index) {
70fdbe5b4bSHaojian Wu   if (!Index) {
71fdbe5b4bSHaojian Wu     // FIXME: use the AST to do the inference.
72059a23c0SKazu Hirata     return std::nullopt;
73fdbe5b4bSHaojian Wu   }
74fdbe5b4bSHaojian Wu   LookupRequest Request;
75fdbe5b4bSHaojian Wu   // Find all symbols present in the original file.
76fdbe5b4bSHaojian Wu   for (const auto *D : getIndexableLocalDecls(AST)) {
77fdbe5b4bSHaojian Wu     if (auto ID = getSymbolID(D))
780df19751SKadir Cetinkaya       Request.IDs.insert(ID);
79fdbe5b4bSHaojian Wu   }
80fdbe5b4bSHaojian Wu   llvm::StringMap<int> Candidates; // Target path => score.
81fdbe5b4bSHaojian Wu   auto AwardTarget = [&](const char *TargetURI) {
82fdbe5b4bSHaojian Wu     if (auto TargetPath = URI::resolve(TargetURI, OriginalFile)) {
83164a10dcSKadir Cetinkaya       if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file.
84fdbe5b4bSHaojian Wu         ++Candidates[*TargetPath];
8577c97002SHaojian Wu     } else {
8677c97002SHaojian Wu       elog("Failed to resolve URI {0}: {1}", TargetURI, TargetPath.takeError());
8777c97002SHaojian Wu     }
88fdbe5b4bSHaojian Wu   };
89fdbe5b4bSHaojian Wu   // If we switch from a header, we are looking for the implementation
90fdbe5b4bSHaojian Wu   // file, so we use the definition loc; otherwise we look for the header file,
91fdbe5b4bSHaojian Wu   // we use the decl loc;
92fdbe5b4bSHaojian Wu   //
93fdbe5b4bSHaojian Wu   // For each symbol in the original file, we get its target location (decl or
94fdbe5b4bSHaojian Wu   // def) from the index, then award that target file.
95c0ee0224SAlex Lorenz   bool IsHeader = isHeaderFile(OriginalFile, AST.getLangOpts());
96fdbe5b4bSHaojian Wu   Index->lookup(Request, [&](const Symbol &Sym) {
97fdbe5b4bSHaojian Wu     if (IsHeader)
98fdbe5b4bSHaojian Wu       AwardTarget(Sym.Definition.FileURI);
99fdbe5b4bSHaojian Wu     else
100fdbe5b4bSHaojian Wu       AwardTarget(Sym.CanonicalDeclaration.FileURI);
101fdbe5b4bSHaojian Wu   });
102fdbe5b4bSHaojian Wu   // FIXME: our index doesn't have any interesting information (this could be
103fdbe5b4bSHaojian Wu   // that the background-index is not finished), we should use the decl/def
104fdbe5b4bSHaojian Wu   // locations from the AST to do the inference (from .cc to .h).
105fdbe5b4bSHaojian Wu   if (Candidates.empty())
106059a23c0SKazu Hirata     return std::nullopt;
107fdbe5b4bSHaojian Wu 
108fdbe5b4bSHaojian Wu   // Pickup the winner, who contains most of symbols.
109fdbe5b4bSHaojian Wu   // FIXME: should we use other signals (file proximity) to help score?
110fdbe5b4bSHaojian Wu   auto Best = Candidates.begin();
111fdbe5b4bSHaojian Wu   for (auto It = Candidates.begin(); It != Candidates.end(); ++It) {
112fdbe5b4bSHaojian Wu     if (It->second > Best->second)
113fdbe5b4bSHaojian Wu       Best = It;
114fdbe5b4bSHaojian Wu     else if (It->second == Best->second && It->first() < Best->first())
115fdbe5b4bSHaojian Wu       // Select the first one in the lexical order if we have multiple
116fdbe5b4bSHaojian Wu       // candidates.
117fdbe5b4bSHaojian Wu       Best = It;
118fdbe5b4bSHaojian Wu   }
1193505d8dcSNathan James   return Path(Best->first());
120fdbe5b4bSHaojian Wu }
121fdbe5b4bSHaojian Wu 
getIndexableLocalDecls(ParsedAST & AST)122fdbe5b4bSHaojian Wu std::vector<const Decl *> getIndexableLocalDecls(ParsedAST &AST) {
123fdbe5b4bSHaojian Wu   std::vector<const Decl *> Results;
124fdbe5b4bSHaojian Wu   std::function<void(Decl *)> TraverseDecl = [&](Decl *D) {
125fdbe5b4bSHaojian Wu     auto *ND = llvm::dyn_cast<NamedDecl>(D);
126fdbe5b4bSHaojian Wu     if (!ND || ND->isImplicit())
127fdbe5b4bSHaojian Wu       return;
128fdbe5b4bSHaojian Wu     if (!SymbolCollector::shouldCollectSymbol(*ND, D->getASTContext(), {},
129fdbe5b4bSHaojian Wu                                               /*IsMainFileSymbol=*/false))
130fdbe5b4bSHaojian Wu       return;
131fdbe5b4bSHaojian Wu     if (!llvm::isa<FunctionDecl>(ND)) {
132fdbe5b4bSHaojian Wu       // Visit the children, but we skip function decls as we are not interested
133fdbe5b4bSHaojian Wu       // in the function body.
134fdbe5b4bSHaojian Wu       if (auto *Scope = llvm::dyn_cast<DeclContext>(ND)) {
135fdbe5b4bSHaojian Wu         for (auto *D : Scope->decls())
136fdbe5b4bSHaojian Wu           TraverseDecl(D);
137fdbe5b4bSHaojian Wu       }
138fdbe5b4bSHaojian Wu     }
139fdbe5b4bSHaojian Wu     if (llvm::isa<NamespaceDecl>(D))
140fdbe5b4bSHaojian Wu       return; // namespace is indexable, but we're not interested.
141fdbe5b4bSHaojian Wu     Results.push_back(D);
142fdbe5b4bSHaojian Wu   };
143fdbe5b4bSHaojian Wu   // Traverses the ParsedAST directly to collect all decls present in the main
144fdbe5b4bSHaojian Wu   // file.
145fdbe5b4bSHaojian Wu   for (auto *TopLevel : AST.getLocalTopLevelDecls())
146fdbe5b4bSHaojian Wu     TraverseDecl(TopLevel);
147fdbe5b4bSHaojian Wu   return Results;
148fdbe5b4bSHaojian Wu }
149fdbe5b4bSHaojian Wu 
150e259a5fdSHaojian Wu } // namespace clangd
151e259a5fdSHaojian Wu } // namespace clang
152