1use crate::{
4 Match, MatchFinder, matching,
5 resolving::{ResolvedPath, ResolvedPattern, ResolvedRule},
6};
7use hir::FileRange;
8use ide_db::{
9 FileId, FxHashSet, LocalRoots,
10 defs::Definition,
11 search::{SearchScope, UsageSearchResult},
12};
13use syntax::{AstNode, SyntaxKind, SyntaxNode, ast};
14
15#[derive(Default)]
20pub(crate) struct UsageCache {
21 usages: Vec<(Definition, UsageSearchResult)>,
22}
23
24impl<'db> MatchFinder<'db> {
25 pub(crate) fn find_matches_for_rule(
29 &self,
30 rule: &ResolvedRule<'db>,
31 usage_cache: &mut UsageCache,
32 matches_out: &mut Vec<Match>,
33 ) {
34 if rule.pattern.contains_self {
35 if let Some(current_function) = self.resolution_scope.current_function() {
39 self.slow_scan_node(¤t_function, rule, &None, matches_out);
40 }
41 return;
42 }
43 if pick_path_for_usages(&rule.pattern).is_none() {
44 self.slow_scan(rule, matches_out);
45 return;
46 }
47 self.find_matches_for_pattern_tree(rule, &rule.pattern, usage_cache, matches_out);
48 }
49
50 fn find_matches_for_pattern_tree(
51 &self,
52 rule: &ResolvedRule<'db>,
53 pattern: &ResolvedPattern<'db>,
54 usage_cache: &mut UsageCache,
55 matches_out: &mut Vec<Match>,
56 ) {
57 if let Some(resolved_path) = pick_path_for_usages(pattern) {
58 let definition: Definition = resolved_path.resolution.into();
59 for file_range in self.find_usages(usage_cache, definition).file_ranges() {
60 for node_to_match in self.find_nodes_to_match(resolved_path, file_range) {
61 if !is_search_permitted_ancestors(&node_to_match) {
62 cov_mark::hit!(use_declaration_with_braces);
63 continue;
64 }
65 self.try_add_match(rule, &node_to_match, &None, matches_out);
66 }
67 }
68 }
69 }
70
71 fn find_nodes_to_match(
72 &self,
73 resolved_path: &ResolvedPath,
74 file_range: FileRange,
75 ) -> Vec<SyntaxNode> {
76 let file = self.sema.parse(file_range.file_id);
77 let depth = resolved_path.depth as usize;
78 let offset = file_range.range.start();
79
80 let mut paths = self
81 .sema
82 .find_nodes_at_offset_with_descend::<ast::Path>(file.syntax(), offset)
83 .peekable();
84
85 if paths.peek().is_some() {
86 paths
87 .filter_map(|path| {
88 self.sema.ancestors_with_macros(path.syntax().clone()).nth(depth)
89 })
90 .collect::<Vec<_>>()
91 } else {
92 self.sema
93 .find_nodes_at_offset_with_descend::<ast::MethodCallExpr>(file.syntax(), offset)
94 .filter_map(|path| {
95 const PATH_DEPTH_IN_CALL_EXPR: usize = 2;
102 if depth < PATH_DEPTH_IN_CALL_EXPR {
103 return None;
104 }
105 self.sema
106 .ancestors_with_macros(path.syntax().clone())
107 .nth(depth - PATH_DEPTH_IN_CALL_EXPR)
108 })
109 .collect::<Vec<_>>()
110 }
111 }
112
113 fn find_usages<'a>(
114 &self,
115 usage_cache: &'a mut UsageCache,
116 definition: Definition,
117 ) -> &'a UsageSearchResult {
118 if usage_cache.find(&definition).is_none() {
123 let usages = definition.usages(&self.sema).in_scope(&self.search_scope()).all();
124 usage_cache.usages.push((definition, usages));
125 return &usage_cache.usages.last().unwrap().1;
126 }
127 usage_cache.find(&definition).unwrap()
128 }
129
130 fn search_scope(&self) -> SearchScope {
133 let mut files = Vec::new();
137 self.search_files_do(|file_id| {
138 files.push(self.sema.attach_first_edition(file_id));
139 });
140 SearchScope::files(&files)
141 }
142
143 fn slow_scan(&self, rule: &ResolvedRule<'db>, matches_out: &mut Vec<Match>) {
144 self.search_files_do(|file_id| {
145 let file = self.sema.parse_guess_edition(file_id);
146 let code = file.syntax();
147 self.slow_scan_node(code, rule, &None, matches_out);
148 })
149 }
150
151 fn search_files_do(&self, mut callback: impl FnMut(FileId)) {
152 if self.restrict_ranges.is_empty() {
153 use ide_db::base_db::SourceDatabase;
155 for &root in LocalRoots::get(self.sema.db).roots(self.sema.db).iter() {
156 let sr = self.sema.db.source_root(root).source_root(self.sema.db);
157 for file_id in sr.iter() {
158 callback(file_id);
159 }
160 }
161 } else {
162 let mut files = FxHashSet::default();
164 for range in &self.restrict_ranges {
165 if files.insert(range.file_id) {
166 callback(range.file_id);
167 }
168 }
169 }
170 }
171
172 fn slow_scan_node(
173 &self,
174 code: &SyntaxNode,
175 rule: &ResolvedRule<'db>,
176 restrict_range: &Option<FileRange>,
177 matches_out: &mut Vec<Match>,
178 ) {
179 if !is_search_permitted(code) {
180 return;
181 }
182 self.try_add_match(rule, code, restrict_range, matches_out);
183 if let Some(macro_call) = ast::MacroCall::cast(code.clone())
186 && let Some(expanded) = self.sema.expand_macro_call(¯o_call)
187 && let Some(tt) = macro_call.token_tree()
188 {
189 if let Some(range) = self.sema.original_range_opt(tt.syntax()) {
193 self.slow_scan_node(&expanded.value, rule, &Some(range), matches_out);
194 }
195 }
196 for child in code.children() {
197 self.slow_scan_node(&child, rule, restrict_range, matches_out);
198 }
199 }
200
201 fn try_add_match(
202 &self,
203 rule: &ResolvedRule<'db>,
204 code: &SyntaxNode,
205 restrict_range: &Option<FileRange>,
206 matches_out: &mut Vec<Match>,
207 ) {
208 if !self.within_range_restrictions(code) {
209 cov_mark::hit!(replace_nonpath_within_selection);
210 return;
211 }
212 if let Ok(m) = matching::get_match(false, rule, code, restrict_range, &self.sema) {
213 matches_out.push(m);
214 }
215 }
216
217 fn within_range_restrictions(&self, code: &SyntaxNode) -> bool {
220 if self.restrict_ranges.is_empty() {
221 return true;
223 }
224 let Some(node_range) = self.sema.original_range_opt(code) else { return false };
225 for range in &self.restrict_ranges {
226 if range.file_id == node_range.file_id.file_id(self.sema.db)
227 && range.range.contains_range(node_range.range)
228 {
229 return true;
230 }
231 }
232 false
233 }
234}
235
236fn is_search_permitted_ancestors(node: &SyntaxNode) -> bool {
238 if let Some(parent) = node.parent()
239 && !is_search_permitted_ancestors(&parent)
240 {
241 return false;
242 }
243 is_search_permitted(node)
244}
245
246fn is_search_permitted(node: &SyntaxNode) -> bool {
248 node.kind() != SyntaxKind::USE
253}
254
255impl UsageCache {
256 fn find(&mut self, definition: &Definition) -> Option<&UsageSearchResult> {
257 for (d, refs) in &self.usages {
260 if d == definition {
261 return Some(refs);
262 }
263 }
264 None
265 }
266}
267
268fn pick_path_for_usages<'a>(pattern: &'a ResolvedPattern<'_>) -> Option<&'a ResolvedPath> {
272 pattern
277 .resolved_paths
278 .iter()
279 .filter(|(_, p)| {
280 !matches!(p.resolution, hir::PathResolution::Def(hir::ModuleDef::BuiltinType(_)))
281 })
282 .map(|(node, resolved)| (node.text().len(), resolved))
283 .max_by(|(a, _), (b, _)| a.cmp(b))
284 .map(|(_, resolved)| resolved)
285}