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