diff options
author | Aleksey Kladov <[email protected]> | 2019-11-27 18:32:33 +0000 |
---|---|---|
committer | Aleksey Kladov <[email protected]> | 2019-11-27 18:35:06 +0000 |
commit | 757e593b253b4df7e6fc8bf15a4d4f34c9d484c5 (patch) | |
tree | d972d3a7e6457efdb5e0c558a8350db1818d07ae /crates/ra_ide/src/references.rs | |
parent | d9a36a736bfb91578a36505e7237212959bb55fe (diff) |
rename ra_ide_api -> ra_ide
Diffstat (limited to 'crates/ra_ide/src/references.rs')
-rw-r--r-- | crates/ra_ide/src/references.rs | 389 |
1 files changed, 389 insertions, 0 deletions
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs new file mode 100644 index 000000000..21a1ea69e --- /dev/null +++ b/crates/ra_ide/src/references.rs | |||
@@ -0,0 +1,389 @@ | |||
1 | //! This module implements a reference search. | ||
2 | //! First, the element at the cursor position must be either an `ast::Name` | ||
3 | //! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we | ||
4 | //! try to resolve the direct tree parent of this element, otherwise we | ||
5 | //! already have a definition and just need to get its HIR together with | ||
6 | //! some information that is needed for futher steps of searching. | ||
7 | //! After that, we collect files that might contain references and look | ||
8 | //! for text occurrences of the identifier. If there's an `ast::NameRef` | ||
9 | //! at the index that the match starts at and its tree parent is | ||
10 | //! resolved to the search element definition, we get a reference. | ||
11 | |||
12 | mod classify; | ||
13 | mod name_definition; | ||
14 | mod rename; | ||
15 | mod search_scope; | ||
16 | |||
17 | use hir::Source; | ||
18 | use once_cell::unsync::Lazy; | ||
19 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | ||
20 | use ra_prof::profile; | ||
21 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit}; | ||
22 | |||
23 | use crate::{ | ||
24 | db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, | ||
25 | }; | ||
26 | |||
27 | pub(crate) use self::{ | ||
28 | classify::{classify_name, classify_name_ref}, | ||
29 | name_definition::{NameDefinition, NameKind}, | ||
30 | rename::rename, | ||
31 | }; | ||
32 | |||
33 | pub use self::search_scope::SearchScope; | ||
34 | |||
35 | #[derive(Debug, Clone)] | ||
36 | pub struct ReferenceSearchResult { | ||
37 | declaration: NavigationTarget, | ||
38 | references: Vec<FileRange>, | ||
39 | } | ||
40 | |||
41 | impl ReferenceSearchResult { | ||
42 | pub fn declaration(&self) -> &NavigationTarget { | ||
43 | &self.declaration | ||
44 | } | ||
45 | |||
46 | pub fn references(&self) -> &[FileRange] { | ||
47 | &self.references | ||
48 | } | ||
49 | |||
50 | /// Total number of references | ||
51 | /// At least 1 since all valid references should | ||
52 | /// Have a declaration | ||
53 | pub fn len(&self) -> usize { | ||
54 | self.references.len() + 1 | ||
55 | } | ||
56 | } | ||
57 | |||
58 | // allow turning ReferenceSearchResult into an iterator | ||
59 | // over FileRanges | ||
60 | impl IntoIterator for ReferenceSearchResult { | ||
61 | type Item = FileRange; | ||
62 | type IntoIter = std::vec::IntoIter<FileRange>; | ||
63 | |||
64 | fn into_iter(mut self) -> Self::IntoIter { | ||
65 | let mut v = Vec::with_capacity(self.len()); | ||
66 | v.push(FileRange { file_id: self.declaration.file_id(), range: self.declaration.range() }); | ||
67 | v.append(&mut self.references); | ||
68 | v.into_iter() | ||
69 | } | ||
70 | } | ||
71 | |||
72 | pub(crate) fn find_all_refs( | ||
73 | db: &RootDatabase, | ||
74 | position: FilePosition, | ||
75 | search_scope: Option<SearchScope>, | ||
76 | ) -> Option<RangeInfo<ReferenceSearchResult>> { | ||
77 | let parse = db.parse(position.file_id); | ||
78 | let syntax = parse.tree().syntax().clone(); | ||
79 | let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?; | ||
80 | |||
81 | let declaration = match def.kind { | ||
82 | NameKind::Macro(mac) => mac.to_nav(db), | ||
83 | NameKind::Field(field) => field.to_nav(db), | ||
84 | NameKind::AssocItem(assoc) => assoc.to_nav(db), | ||
85 | NameKind::Def(def) => NavigationTarget::from_def(db, def)?, | ||
86 | NameKind::SelfType(imp) => imp.to_nav(db), | ||
87 | NameKind::Local(local) => local.to_nav(db), | ||
88 | NameKind::GenericParam(_) => return None, | ||
89 | }; | ||
90 | |||
91 | let search_scope = { | ||
92 | let base = def.search_scope(db); | ||
93 | match search_scope { | ||
94 | None => base, | ||
95 | Some(scope) => base.intersection(&scope), | ||
96 | } | ||
97 | }; | ||
98 | |||
99 | let references = process_definition(db, def, name, search_scope); | ||
100 | |||
101 | Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) | ||
102 | } | ||
103 | |||
104 | fn find_name<'a>( | ||
105 | db: &RootDatabase, | ||
106 | syntax: &SyntaxNode, | ||
107 | position: FilePosition, | ||
108 | ) -> Option<RangeInfo<(String, NameDefinition)>> { | ||
109 | if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) { | ||
110 | let def = classify_name(db, Source::new(position.file_id.into(), &name))?; | ||
111 | let range = name.syntax().text_range(); | ||
112 | return Some(RangeInfo::new(range, (name.text().to_string(), def))); | ||
113 | } | ||
114 | let name_ref = find_node_at_offset::<ast::NameRef>(&syntax, position.offset)?; | ||
115 | let def = classify_name_ref(db, Source::new(position.file_id.into(), &name_ref))?; | ||
116 | let range = name_ref.syntax().text_range(); | ||
117 | Some(RangeInfo::new(range, (name_ref.text().to_string(), def))) | ||
118 | } | ||
119 | |||
120 | fn process_definition( | ||
121 | db: &RootDatabase, | ||
122 | def: NameDefinition, | ||
123 | name: String, | ||
124 | scope: SearchScope, | ||
125 | ) -> Vec<FileRange> { | ||
126 | let _p = profile("process_definition"); | ||
127 | |||
128 | let pat = name.as_str(); | ||
129 | let mut refs = vec![]; | ||
130 | |||
131 | for (file_id, search_range) in scope { | ||
132 | let text = db.file_text(file_id); | ||
133 | let parse = Lazy::new(|| SourceFile::parse(&text)); | ||
134 | |||
135 | for (idx, _) in text.match_indices(pat) { | ||
136 | let offset = TextUnit::from_usize(idx); | ||
137 | |||
138 | if let Some(name_ref) = | ||
139 | find_node_at_offset::<ast::NameRef>(parse.tree().syntax(), offset) | ||
140 | { | ||
141 | let range = name_ref.syntax().text_range(); | ||
142 | if let Some(search_range) = search_range { | ||
143 | if !range.is_subrange(&search_range) { | ||
144 | continue; | ||
145 | } | ||
146 | } | ||
147 | if let Some(d) = classify_name_ref(db, Source::new(file_id.into(), &name_ref)) { | ||
148 | if d == def { | ||
149 | refs.push(FileRange { file_id, range }); | ||
150 | } | ||
151 | } | ||
152 | } | ||
153 | } | ||
154 | } | ||
155 | refs | ||
156 | } | ||
157 | |||
158 | #[cfg(test)] | ||
159 | mod tests { | ||
160 | use crate::{ | ||
161 | mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, | ||
162 | ReferenceSearchResult, SearchScope, | ||
163 | }; | ||
164 | |||
165 | #[test] | ||
166 | fn test_find_all_refs_for_local() { | ||
167 | let code = r#" | ||
168 | fn main() { | ||
169 | let mut i = 1; | ||
170 | let j = 1; | ||
171 | i = i<|> + j; | ||
172 | |||
173 | { | ||
174 | i = 0; | ||
175 | } | ||
176 | |||
177 | i = 5; | ||
178 | }"#; | ||
179 | |||
180 | let refs = get_all_refs(code); | ||
181 | assert_eq!(refs.len(), 5); | ||
182 | } | ||
183 | |||
184 | #[test] | ||
185 | fn test_find_all_refs_for_param_inside() { | ||
186 | let code = r#" | ||
187 | fn foo(i : u32) -> u32 { | ||
188 | i<|> | ||
189 | }"#; | ||
190 | |||
191 | let refs = get_all_refs(code); | ||
192 | assert_eq!(refs.len(), 2); | ||
193 | } | ||
194 | |||
195 | #[test] | ||
196 | fn test_find_all_refs_for_fn_param() { | ||
197 | let code = r#" | ||
198 | fn foo(i<|> : u32) -> u32 { | ||
199 | i | ||
200 | }"#; | ||
201 | |||
202 | let refs = get_all_refs(code); | ||
203 | assert_eq!(refs.len(), 2); | ||
204 | } | ||
205 | |||
206 | #[test] | ||
207 | fn test_find_all_refs_field_name() { | ||
208 | let code = r#" | ||
209 | //- /lib.rs | ||
210 | struct Foo { | ||
211 | pub spam<|>: u32, | ||
212 | } | ||
213 | |||
214 | fn main(s: Foo) { | ||
215 | let f = s.spam; | ||
216 | } | ||
217 | "#; | ||
218 | |||
219 | let refs = get_all_refs(code); | ||
220 | assert_eq!(refs.len(), 2); | ||
221 | } | ||
222 | |||
223 | #[test] | ||
224 | fn test_find_all_refs_impl_item_name() { | ||
225 | let code = r#" | ||
226 | //- /lib.rs | ||
227 | struct Foo; | ||
228 | impl Foo { | ||
229 | fn f<|>(&self) { } | ||
230 | } | ||
231 | "#; | ||
232 | |||
233 | let refs = get_all_refs(code); | ||
234 | assert_eq!(refs.len(), 1); | ||
235 | } | ||
236 | |||
237 | #[test] | ||
238 | fn test_find_all_refs_enum_var_name() { | ||
239 | let code = r#" | ||
240 | //- /lib.rs | ||
241 | enum Foo { | ||
242 | A, | ||
243 | B<|>, | ||
244 | C, | ||
245 | } | ||
246 | "#; | ||
247 | |||
248 | let refs = get_all_refs(code); | ||
249 | assert_eq!(refs.len(), 1); | ||
250 | } | ||
251 | |||
252 | #[test] | ||
253 | fn test_find_all_refs_two_modules() { | ||
254 | let code = r#" | ||
255 | //- /lib.rs | ||
256 | pub mod foo; | ||
257 | pub mod bar; | ||
258 | |||
259 | fn f() { | ||
260 | let i = foo::Foo { n: 5 }; | ||
261 | } | ||
262 | |||
263 | //- /foo.rs | ||
264 | use crate::bar; | ||
265 | |||
266 | pub struct Foo { | ||
267 | pub n: u32, | ||
268 | } | ||
269 | |||
270 | fn f() { | ||
271 | let i = bar::Bar { n: 5 }; | ||
272 | } | ||
273 | |||
274 | //- /bar.rs | ||
275 | use crate::foo; | ||
276 | |||
277 | pub struct Bar { | ||
278 | pub n: u32, | ||
279 | } | ||
280 | |||
281 | fn f() { | ||
282 | let i = foo::Foo<|> { n: 5 }; | ||
283 | } | ||
284 | "#; | ||
285 | |||
286 | let (analysis, pos) = analysis_and_position(code); | ||
287 | let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); | ||
288 | assert_eq!(refs.len(), 3); | ||
289 | } | ||
290 | |||
291 | // `mod foo;` is not in the results because `foo` is an `ast::Name`. | ||
292 | // So, there are two references: the first one is a definition of the `foo` module, | ||
293 | // which is the whole `foo.rs`, and the second one is in `use foo::Foo`. | ||
294 | #[test] | ||
295 | fn test_find_all_refs_decl_module() { | ||
296 | let code = r#" | ||
297 | //- /lib.rs | ||
298 | mod foo<|>; | ||
299 | |||
300 | use foo::Foo; | ||
301 | |||
302 | fn f() { | ||
303 | let i = Foo { n: 5 }; | ||
304 | } | ||
305 | |||
306 | //- /foo.rs | ||
307 | pub struct Foo { | ||
308 | pub n: u32, | ||
309 | } | ||
310 | "#; | ||
311 | |||
312 | let (analysis, pos) = analysis_and_position(code); | ||
313 | let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); | ||
314 | assert_eq!(refs.len(), 2); | ||
315 | } | ||
316 | |||
317 | #[test] | ||
318 | fn test_find_all_refs_super_mod_vis() { | ||
319 | let code = r#" | ||
320 | //- /lib.rs | ||
321 | mod foo; | ||
322 | |||
323 | //- /foo.rs | ||
324 | mod some; | ||
325 | use some::Foo; | ||
326 | |||
327 | fn f() { | ||
328 | let i = Foo { n: 5 }; | ||
329 | } | ||
330 | |||
331 | //- /foo/some.rs | ||
332 | pub(super) struct Foo<|> { | ||
333 | pub n: u32, | ||
334 | } | ||
335 | "#; | ||
336 | |||
337 | let (analysis, pos) = analysis_and_position(code); | ||
338 | let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); | ||
339 | assert_eq!(refs.len(), 3); | ||
340 | } | ||
341 | |||
342 | #[test] | ||
343 | fn test_find_all_refs_with_scope() { | ||
344 | let code = r#" | ||
345 | //- /lib.rs | ||
346 | mod foo; | ||
347 | mod bar; | ||
348 | |||
349 | pub fn quux<|>() {} | ||
350 | |||
351 | //- /foo.rs | ||
352 | fn f() { super::quux(); } | ||
353 | |||
354 | //- /bar.rs | ||
355 | fn f() { super::quux(); } | ||
356 | "#; | ||
357 | |||
358 | let (mock, pos) = MockAnalysis::with_files_and_position(code); | ||
359 | let bar = mock.id_of("/bar.rs"); | ||
360 | let analysis = mock.analysis(); | ||
361 | |||
362 | let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); | ||
363 | assert_eq!(refs.len(), 3); | ||
364 | |||
365 | let refs = | ||
366 | analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap(); | ||
367 | assert_eq!(refs.len(), 2); | ||
368 | } | ||
369 | |||
370 | #[test] | ||
371 | fn test_find_all_refs_macro_def() { | ||
372 | let code = r#" | ||
373 | #[macro_export] | ||
374 | macro_rules! m1<|> { () => (()) } | ||
375 | |||
376 | fn foo() { | ||
377 | m1(); | ||
378 | m1(); | ||
379 | }"#; | ||
380 | |||
381 | let refs = get_all_refs(code); | ||
382 | assert_eq!(refs.len(), 3); | ||
383 | } | ||
384 | |||
385 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | ||
386 | let (analysis, position) = single_file_with_position(text); | ||
387 | analysis.find_all_refs(position, None).unwrap().unwrap() | ||
388 | } | ||
389 | } | ||