aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/references.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/references.rs')
-rw-r--r--crates/ra_ide/src/references.rs389
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
12mod classify;
13mod name_definition;
14mod rename;
15mod search_scope;
16
17use hir::Source;
18use once_cell::unsync::Lazy;
19use ra_db::{SourceDatabase, SourceDatabaseExt};
20use ra_prof::profile;
21use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit};
22
23use crate::{
24 db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo,
25};
26
27pub(crate) use self::{
28 classify::{classify_name, classify_name_ref},
29 name_definition::{NameDefinition, NameKind},
30 rename::rename,
31};
32
33pub use self::search_scope::SearchScope;
34
35#[derive(Debug, Clone)]
36pub struct ReferenceSearchResult {
37 declaration: NavigationTarget,
38 references: Vec<FileRange>,
39}
40
41impl 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
60impl 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
72pub(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
104fn 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
120fn 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)]
159mod 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}