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" , ".inc" }; |
26 | |
27 | llvm::StringRef PathExt = llvm::sys::path::extension(path: OriginalFile); |
28 | |
29 | // Lookup in a list of known extensions. |
30 | bool IsSource = llvm::any_of(Range&: SourceExtensions, P: [&PathExt](PathRef SourceExt) { |
31 | return SourceExt.equals_insensitive(RHS: PathExt); |
32 | }); |
33 | |
34 | bool = llvm::any_of(Range&: HeaderExtensions, P: [&PathExt](PathRef ) { |
35 | return HeaderExt.equals_insensitive(RHS: 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(path&: NewPath, extension: NewExt); |
56 | if (VFS->exists(Path: NewPath)) |
57 | return Path(NewPath); |
58 | |
59 | // Also check NewExt in upper-case, just in case. |
60 | llvm::sys::path::replace_extension(path&: NewPath, extension: NewExt.upper()); |
61 | if (VFS->exists(Path: NewPath)) |
62 | return Path(NewPath); |
63 | } |
64 | return std::nullopt; |
65 | } |
66 | |
67 | std::optional<Path> (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(V: ID); |
79 | } |
80 | llvm::StringMap<int> Candidates; // Target path => score. |
81 | auto AwardTarget = [&](const char *TargetURI) { |
82 | if (auto TargetPath = URI::resolve(FileURI: TargetURI, HintPath: OriginalFile)) { |
83 | if (!pathEqual(*TargetPath, OriginalFile)) // exclude the original file. |
84 | ++Candidates[*TargetPath]; |
85 | } else { |
86 | elog(Fmt: "Failed to resolve URI {0}: {1}" , Vals&: TargetURI, Vals: 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 = isHeaderFile(FileName: OriginalFile, LangOpts: AST.getLangOpts()); |
96 | Index->lookup(Req: Request, Callback: [&](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 | |
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>(Val: D); |
126 | if (!ND || ND->isImplicit()) |
127 | return; |
128 | if (!SymbolCollector::shouldCollectSymbol(ND: *ND, ASTCtx: D->getASTContext(), Opts: {}, |
129 | /*IsMainFileSymbol=*/false)) |
130 | return; |
131 | if (!llvm::isa<FunctionDecl>(Val: 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>(Val: ND)) { |
135 | for (auto *D : Scope->decls()) |
136 | TraverseDecl(D); |
137 | } |
138 | } |
139 | if (llvm::isa<NamespaceDecl>(Val: D)) |
140 | return; // namespace is indexable, but we're not interested. |
141 | Results.push_back(x: 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 | |