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