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.rs695
1 files changed, 0 insertions, 695 deletions
diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs
deleted file mode 100644
index 453985de3..000000000
--- a/crates/ra_ide/src/references.rs
+++ /dev/null
@@ -1,695 +0,0 @@
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 rename;
13
14use hir::Semantics;
15use ra_ide_db::{
16 defs::{classify_name, classify_name_ref, Definition},
17 search::SearchScope,
18 RootDatabase,
19};
20use ra_prof::profile;
21use ra_syntax::{
22 algo::find_node_at_offset,
23 ast::{self, NameOwner},
24 AstNode, SyntaxKind, SyntaxNode, TextRange, TokenAtOffset,
25};
26
27use crate::{display::TryToNav, FilePosition, FileRange, NavigationTarget, RangeInfo};
28
29pub(crate) use self::rename::rename;
30
31pub use ra_ide_db::search::{Reference, ReferenceAccess, ReferenceKind};
32
33#[derive(Debug, Clone)]
34pub struct ReferenceSearchResult {
35 declaration: Declaration,
36 references: Vec<Reference>,
37}
38
39#[derive(Debug, Clone)]
40pub struct Declaration {
41 pub nav: NavigationTarget,
42 pub kind: ReferenceKind,
43 pub access: Option<ReferenceAccess>,
44}
45
46impl ReferenceSearchResult {
47 pub fn declaration(&self) -> &Declaration {
48 &self.declaration
49 }
50
51 pub fn decl_target(&self) -> &NavigationTarget {
52 &self.declaration.nav
53 }
54
55 pub fn references(&self) -> &[Reference] {
56 &self.references
57 }
58
59 /// Total number of references
60 /// At least 1 since all valid references should
61 /// Have a declaration
62 pub fn len(&self) -> usize {
63 self.references.len() + 1
64 }
65}
66
67// allow turning ReferenceSearchResult into an iterator
68// over References
69impl IntoIterator for ReferenceSearchResult {
70 type Item = Reference;
71 type IntoIter = std::vec::IntoIter<Reference>;
72
73 fn into_iter(mut self) -> Self::IntoIter {
74 let mut v = Vec::with_capacity(self.len());
75 v.push(Reference {
76 file_range: FileRange {
77 file_id: self.declaration.nav.file_id,
78 range: self.declaration.nav.focus_or_full_range(),
79 },
80 kind: self.declaration.kind,
81 access: self.declaration.access,
82 });
83 v.append(&mut self.references);
84 v.into_iter()
85 }
86}
87
88pub(crate) fn find_all_refs(
89 sema: &Semantics<RootDatabase>,
90 position: FilePosition,
91 search_scope: Option<SearchScope>,
92) -> Option<RangeInfo<ReferenceSearchResult>> {
93 let _p = profile("find_all_refs");
94 let syntax = sema.parse(position.file_id).syntax().clone();
95
96 let (opt_name, search_kind) = if let Some(name) =
97 get_struct_def_name_for_struct_literal_search(&sema, &syntax, position)
98 {
99 (Some(name), ReferenceKind::StructLiteral)
100 } else {
101 (
102 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, position.offset),
103 ReferenceKind::Other,
104 )
105 };
106
107 let RangeInfo { range, info: def } = find_name(&sema, &syntax, position, opt_name)?;
108
109 let references = def
110 .find_usages(sema, search_scope)
111 .into_iter()
112 .filter(|r| search_kind == ReferenceKind::Other || search_kind == r.kind)
113 .collect();
114
115 let decl_range = def.try_to_nav(sema.db)?.focus_or_full_range();
116
117 let declaration = Declaration {
118 nav: def.try_to_nav(sema.db)?,
119 kind: ReferenceKind::Other,
120 access: decl_access(&def, &syntax, decl_range),
121 };
122
123 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
124}
125
126fn find_name(
127 sema: &Semantics<RootDatabase>,
128 syntax: &SyntaxNode,
129 position: FilePosition,
130 opt_name: Option<ast::Name>,
131) -> Option<RangeInfo<Definition>> {
132 if let Some(name) = opt_name {
133 let def = classify_name(sema, &name)?.definition(sema.db);
134 let range = name.syntax().text_range();
135 return Some(RangeInfo::new(range, def));
136 }
137 let name_ref =
138 sema.find_node_at_offset_with_descend::<ast::NameRef>(&syntax, position.offset)?;
139 let def = classify_name_ref(sema, &name_ref)?.definition(sema.db);
140 let range = name_ref.syntax().text_range();
141 Some(RangeInfo::new(range, def))
142}
143
144fn decl_access(def: &Definition, syntax: &SyntaxNode, range: TextRange) -> Option<ReferenceAccess> {
145 match def {
146 Definition::Local(_) | Definition::Field(_) => {}
147 _ => return None,
148 };
149
150 let stmt = find_node_at_offset::<ast::LetStmt>(syntax, range.start())?;
151 if stmt.initializer().is_some() {
152 let pat = stmt.pat()?;
153 if let ast::Pat::IdentPat(it) = pat {
154 if it.mut_token().is_some() {
155 return Some(ReferenceAccess::Write);
156 }
157 }
158 }
159
160 None
161}
162
163fn get_struct_def_name_for_struct_literal_search(
164 sema: &Semantics<RootDatabase>,
165 syntax: &SyntaxNode,
166 position: FilePosition,
167) -> Option<ast::Name> {
168 if let TokenAtOffset::Between(ref left, ref right) = syntax.token_at_offset(position.offset) {
169 if right.kind() != SyntaxKind::L_CURLY && right.kind() != SyntaxKind::L_PAREN {
170 return None;
171 }
172 if let Some(name) =
173 sema.find_node_at_offset_with_descend::<ast::Name>(&syntax, left.text_range().start())
174 {
175 return name.syntax().ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
176 }
177 if sema
178 .find_node_at_offset_with_descend::<ast::GenericParamList>(
179 &syntax,
180 left.text_range().start(),
181 )
182 .is_some()
183 {
184 return left.ancestors().find_map(ast::Struct::cast).and_then(|l| l.name());
185 }
186 }
187 None
188}
189
190#[cfg(test)]
191mod tests {
192 use crate::{
193 mock_analysis::{analysis_and_position, MockAnalysis},
194 Declaration, Reference, ReferenceSearchResult, SearchScope,
195 };
196
197 #[test]
198 fn test_struct_literal_after_space() {
199 let refs = get_all_refs(
200 r#"
201struct Foo <|>{
202 a: i32,
203}
204impl Foo {
205 fn f() -> i32 { 42 }
206}
207fn main() {
208 let f: Foo;
209 f = Foo {a: Foo::f()};
210}
211"#,
212 );
213 check_result(
214 refs,
215 "Foo STRUCT FileId(1) 0..26 7..10 Other",
216 &["FileId(1) 101..104 StructLiteral"],
217 );
218 }
219
220 #[test]
221 fn test_struct_literal_before_space() {
222 let refs = get_all_refs(
223 r#"
224struct Foo<|> {}
225 fn main() {
226 let f: Foo;
227 f = Foo {};
228}
229"#,
230 );
231 check_result(
232 refs,
233 "Foo STRUCT FileId(1) 0..13 7..10 Other",
234 &["FileId(1) 41..44 Other", "FileId(1) 54..57 StructLiteral"],
235 );
236 }
237
238 #[test]
239 fn test_struct_literal_with_generic_type() {
240 let refs = get_all_refs(
241 r#"
242struct Foo<T> <|>{}
243 fn main() {
244 let f: Foo::<i32>;
245 f = Foo {};
246}
247"#,
248 );
249 check_result(
250 refs,
251 "Foo STRUCT FileId(1) 0..16 7..10 Other",
252 &["FileId(1) 64..67 StructLiteral"],
253 );
254 }
255
256 #[test]
257 fn test_struct_literal_for_tuple() {
258 let refs = get_all_refs(
259 r#"
260struct Foo<|>(i32);
261
262fn main() {
263 let f: Foo;
264 f = Foo(1);
265}
266"#,
267 );
268 check_result(
269 refs,
270 "Foo STRUCT FileId(1) 0..16 7..10 Other",
271 &["FileId(1) 54..57 StructLiteral"],
272 );
273 }
274
275 #[test]
276 fn test_find_all_refs_for_local() {
277 let refs = get_all_refs(
278 r#"
279fn main() {
280 let mut i = 1;
281 let j = 1;
282 i = i<|> + j;
283
284 {
285 i = 0;
286 }
287
288 i = 5;
289}"#,
290 );
291 check_result(
292 refs,
293 "i IDENT_PAT FileId(1) 24..25 Other Write",
294 &[
295 "FileId(1) 50..51 Other Write",
296 "FileId(1) 54..55 Other Read",
297 "FileId(1) 76..77 Other Write",
298 "FileId(1) 94..95 Other Write",
299 ],
300 );
301 }
302
303 #[test]
304 fn search_filters_by_range() {
305 let refs = get_all_refs(
306 r#"
307fn foo() {
308 let spam<|> = 92;
309 spam + spam
310}
311fn bar() {
312 let spam = 92;
313 spam + spam
314}
315"#,
316 );
317 check_result(
318 refs,
319 "spam IDENT_PAT FileId(1) 19..23 Other",
320 &["FileId(1) 34..38 Other Read", "FileId(1) 41..45 Other Read"],
321 );
322 }
323
324 #[test]
325 fn test_find_all_refs_for_param_inside() {
326 let refs = get_all_refs(
327 r#"
328fn foo(i : u32) -> u32 {
329 i<|>
330}
331"#,
332 );
333 check_result(refs, "i IDENT_PAT FileId(1) 7..8 Other", &["FileId(1) 29..30 Other Read"]);
334 }
335
336 #[test]
337 fn test_find_all_refs_for_fn_param() {
338 let refs = get_all_refs(
339 r#"
340fn foo(i<|> : u32) -> u32 {
341 i
342}
343"#,
344 );
345 check_result(refs, "i IDENT_PAT FileId(1) 7..8 Other", &["FileId(1) 29..30 Other Read"]);
346 }
347
348 #[test]
349 fn test_find_all_refs_field_name() {
350 let refs = get_all_refs(
351 r#"
352//- /lib.rs
353struct Foo {
354 pub spam<|>: u32,
355}
356
357fn main(s: Foo) {
358 let f = s.spam;
359}
360"#,
361 );
362 check_result(
363 refs,
364 "spam RECORD_FIELD FileId(1) 17..30 21..25 Other",
365 &["FileId(1) 67..71 Other Read"],
366 );
367 }
368
369 #[test]
370 fn test_find_all_refs_impl_item_name() {
371 let refs = get_all_refs(
372 r#"
373struct Foo;
374impl Foo {
375 fn f<|>(&self) { }
376}
377"#,
378 );
379 check_result(refs, "f FN FileId(1) 27..43 30..31 Other", &[]);
380 }
381
382 #[test]
383 fn test_find_all_refs_enum_var_name() {
384 let refs = get_all_refs(
385 r#"
386enum Foo {
387 A,
388 B<|>,
389 C,
390}
391"#,
392 );
393 check_result(refs, "B VARIANT FileId(1) 22..23 22..23 Other", &[]);
394 }
395
396 #[test]
397 fn test_find_all_refs_two_modules() {
398 let (analysis, pos) = analysis_and_position(
399 r#"
400//- /lib.rs
401pub mod foo;
402pub mod bar;
403
404fn f() {
405 let i = foo::Foo { n: 5 };
406}
407
408//- /foo.rs
409use crate::bar;
410
411pub struct Foo {
412 pub n: u32,
413}
414
415fn f() {
416 let i = bar::Bar { n: 5 };
417}
418
419//- /bar.rs
420use crate::foo;
421
422pub struct Bar {
423 pub n: u32,
424}
425
426fn f() {
427 let i = foo::Foo<|> { n: 5 };
428}
429"#,
430 );
431 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
432 check_result(
433 refs,
434 "Foo STRUCT FileId(2) 17..51 28..31 Other",
435 &["FileId(1) 53..56 StructLiteral", "FileId(3) 79..82 StructLiteral"],
436 );
437 }
438
439 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
440 // So, there are two references: the first one is a definition of the `foo` module,
441 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
442 #[test]
443 fn test_find_all_refs_decl_module() {
444 let (analysis, pos) = analysis_and_position(
445 r#"
446//- /lib.rs
447mod foo<|>;
448
449use foo::Foo;
450
451fn f() {
452 let i = Foo { n: 5 };
453}
454
455//- /foo.rs
456pub struct Foo {
457 pub n: u32,
458}
459"#,
460 );
461 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
462 check_result(refs, "foo SOURCE_FILE FileId(2) 0..35 Other", &["FileId(1) 14..17 Other"]);
463 }
464
465 #[test]
466 fn test_find_all_refs_super_mod_vis() {
467 let (analysis, pos) = analysis_and_position(
468 r#"
469//- /lib.rs
470mod foo;
471
472//- /foo.rs
473mod some;
474use some::Foo;
475
476fn f() {
477 let i = Foo { n: 5 };
478}
479
480//- /foo/some.rs
481pub(super) struct Foo<|> {
482 pub n: u32,
483}
484"#,
485 );
486 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
487 check_result(
488 refs,
489 "Foo STRUCT FileId(3) 0..41 18..21 Other",
490 &["FileId(2) 20..23 Other", "FileId(2) 47..50 StructLiteral"],
491 );
492 }
493
494 #[test]
495 fn test_find_all_refs_with_scope() {
496 let code = r#"
497 //- /lib.rs
498 mod foo;
499 mod bar;
500
501 pub fn quux<|>() {}
502
503 //- /foo.rs
504 fn f() { super::quux(); }
505
506 //- /bar.rs
507 fn f() { super::quux(); }
508 "#;
509
510 let (mock, pos) = MockAnalysis::with_files_and_position(code);
511 let bar = mock.id_of("/bar.rs");
512 let analysis = mock.analysis();
513
514 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
515 check_result(
516 refs,
517 "quux FN FileId(1) 19..35 26..30 Other",
518 &["FileId(2) 16..20 StructLiteral", "FileId(3) 16..20 StructLiteral"],
519 );
520
521 let refs =
522 analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap();
523 check_result(
524 refs,
525 "quux FN FileId(1) 19..35 26..30 Other",
526 &["FileId(3) 16..20 StructLiteral"],
527 );
528 }
529
530 #[test]
531 fn test_find_all_refs_macro_def() {
532 let refs = get_all_refs(
533 r#"
534#[macro_export]
535macro_rules! m1<|> { () => (()) }
536
537fn foo() {
538 m1();
539 m1();
540}
541"#,
542 );
543 check_result(
544 refs,
545 "m1 MACRO_CALL FileId(1) 0..46 29..31 Other",
546 &["FileId(1) 63..65 StructLiteral", "FileId(1) 73..75 StructLiteral"],
547 );
548 }
549
550 #[test]
551 fn test_basic_highlight_read_write() {
552 let refs = get_all_refs(
553 r#"
554fn foo() {
555 let mut i<|> = 0;
556 i = i + 1;
557}
558"#,
559 );
560 check_result(
561 refs,
562 "i IDENT_PAT FileId(1) 23..24 Other Write",
563 &["FileId(1) 34..35 Other Write", "FileId(1) 38..39 Other Read"],
564 );
565 }
566
567 #[test]
568 fn test_basic_highlight_field_read_write() {
569 let refs = get_all_refs(
570 r#"
571struct S {
572 f: u32,
573}
574
575fn foo() {
576 let mut s = S{f: 0};
577 s.f<|> = 0;
578}
579"#,
580 );
581 check_result(
582 refs,
583 "f RECORD_FIELD FileId(1) 15..21 15..16 Other",
584 &["FileId(1) 55..56 Other Read", "FileId(1) 68..69 Other Write"],
585 );
586 }
587
588 #[test]
589 fn test_basic_highlight_decl_no_write() {
590 let refs = get_all_refs(
591 r#"
592fn foo() {
593 let i<|>;
594 i = 1;
595}
596"#,
597 );
598 check_result(refs, "i IDENT_PAT FileId(1) 19..20 Other", &["FileId(1) 26..27 Other Write"]);
599 }
600
601 #[test]
602 fn test_find_struct_function_refs_outside_module() {
603 let refs = get_all_refs(
604 r#"
605mod foo {
606 pub struct Foo;
607
608 impl Foo {
609 pub fn new<|>() -> Foo {
610 Foo
611 }
612 }
613}
614
615fn main() {
616 let _f = foo::Foo::new();
617}
618"#,
619 );
620 check_result(
621 refs,
622 "new FN FileId(1) 54..101 61..64 Other",
623 &["FileId(1) 146..149 StructLiteral"],
624 );
625 }
626
627 #[test]
628 fn test_find_all_refs_nested_module() {
629 let code = r#"
630 //- /lib.rs
631 mod foo {
632 mod bar;
633 }
634
635 fn f<|>() {}
636
637 //- /foo/bar.rs
638 use crate::f;
639
640 fn g() {
641 f();
642 }
643 "#;
644
645 let (analysis, pos) = analysis_and_position(code);
646 let refs = analysis.find_all_refs(pos, None).unwrap().unwrap();
647 check_result(
648 refs,
649 "f FN FileId(1) 26..35 29..30 Other",
650 &["FileId(2) 11..12 Other", "FileId(2) 28..29 StructLiteral"],
651 );
652 }
653
654 fn get_all_refs(ra_fixture: &str) -> ReferenceSearchResult {
655 let (analysis, position) = analysis_and_position(ra_fixture);
656 analysis.find_all_refs(position, None).unwrap().unwrap()
657 }
658
659 fn check_result(res: ReferenceSearchResult, expected_decl: &str, expected_refs: &[&str]) {
660 res.declaration().assert_match(expected_decl);
661 assert_eq!(res.references.len(), expected_refs.len());
662 res.references()
663 .iter()
664 .enumerate()
665 .for_each(|(i, r)| ref_assert_match(r, expected_refs[i]));
666 }
667
668 impl Declaration {
669 fn debug_render(&self) -> String {
670 let mut s = format!("{} {:?}", self.nav.debug_render(), self.kind);
671 if let Some(access) = self.access {
672 s.push_str(&format!(" {:?}", access));
673 }
674 s
675 }
676
677 fn assert_match(&self, expected: &str) {
678 let actual = self.debug_render();
679 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
680 }
681 }
682
683 fn ref_debug_render(r: &Reference) -> String {
684 let mut s = format!("{:?} {:?} {:?}", r.file_range.file_id, r.file_range.range, r.kind);
685 if let Some(access) = r.access {
686 s.push_str(&format!(" {:?}", access));
687 }
688 s
689 }
690
691 fn ref_assert_match(r: &Reference, expected: &str) {
692 let actual = ref_debug_render(r);
693 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
694 }
695}