aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/references.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/references.rs')
-rw-r--r--crates/ra_ide_api/src/references.rs524
1 files changed, 197 insertions, 327 deletions
diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs
index 84c2eb793..f35d835ac 100644
--- a/crates/ra_ide_api/src/references.rs
+++ b/crates/ra_ide_api/src/references.rs
@@ -1,13 +1,29 @@
1//! FIXME: write short doc here 1//! This module implements a reference search.
2 2//! First, the element at the cursor position must be either an `ast::Name`
3use hir::{Either, ModuleSource}; 3//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we
4use ra_db::SourceDatabase; 4//! try to resolve the direct tree parent of this element, otherwise we
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode}; 5//! already have a definition and just need to get its HIR together with
6use relative_path::{RelativePath, RelativePathBuf}; 6//! some information that is needed for futher steps of searching.
7 7//! After that, we collect files that might contain references and look
8use crate::{ 8//! for text occurrences of the identifier. If there's an `ast::NameRef`
9 db::RootDatabase, FileId, FilePosition, FileRange, FileSystemEdit, NavigationTarget, RangeInfo, 9//! at the index that the match starts at and its tree parent is
10 SourceChange, SourceFileEdit, TextRange, 10//! resolved to the search element definition, we get a reference.
11
12mod classify;
13mod name_definition;
14mod rename;
15mod search_scope;
16
17use once_cell::unsync::Lazy;
18use ra_db::{SourceDatabase, SourceDatabaseExt};
19use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit};
20
21use crate::{db::RootDatabase, FilePosition, FileRange, NavigationTarget, RangeInfo};
22
23pub(crate) use self::{
24 classify::{classify_name, classify_name_ref},
25 name_definition::{NameDefinition, NameKind},
26 rename::rename,
11}; 27};
12 28
13#[derive(Debug, Clone)] 29#[derive(Debug, Clone)]
@@ -52,161 +68,82 @@ pub(crate) fn find_all_refs(
52 position: FilePosition, 68 position: FilePosition,
53) -> Option<RangeInfo<ReferenceSearchResult>> { 69) -> Option<RangeInfo<ReferenceSearchResult>> {
54 let parse = db.parse(position.file_id); 70 let parse = db.parse(position.file_id);
55 let RangeInfo { range, info: (binding, analyzer) } = find_binding(db, &parse.tree(), position)?; 71 let syntax = parse.tree().syntax().clone();
56 let declaration = NavigationTarget::from_bind_pat(position.file_id, &binding); 72 let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?;
57 73
58 let references = analyzer 74 let declaration = match def.kind {
59 .find_all_refs(&binding) 75 NameKind::Macro(mac) => NavigationTarget::from_macro_def(db, mac),
60 .into_iter() 76 NameKind::Field(field) => NavigationTarget::from_field(db, field),
61 .map(move |ref_desc| FileRange { file_id: position.file_id, range: ref_desc.range }) 77 NameKind::AssocItem(assoc) => NavigationTarget::from_assoc_item(db, assoc),
62 .collect::<Vec<_>>(); 78 NameKind::Def(def) => NavigationTarget::from_def(db, def)?,
63 79 NameKind::SelfType(ref ty) => match ty.as_adt() {
64 return Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })); 80 Some((def_id, _)) => NavigationTarget::from_adt_def(db, def_id),
65 81 None => return None,
66 fn find_binding<'a>( 82 },
67 db: &RootDatabase, 83 NameKind::Pat((_, pat)) => NavigationTarget::from_pat(db, position.file_id, pat),
68 source_file: &SourceFile, 84 NameKind::SelfParam(par) => NavigationTarget::from_self_param(position.file_id, par),
69 position: FilePosition, 85 NameKind::GenericParam(_) => return None,
70 ) -> Option<RangeInfo<(ast::BindPat, hir::SourceAnalyzer)>> { 86 };
71 let syntax = source_file.syntax();
72 if let Some(binding) = find_node_at_offset::<ast::BindPat>(syntax, position.offset) {
73 let range = binding.syntax().text_range();
74 let analyzer = hir::SourceAnalyzer::new(db, position.file_id, binding.syntax(), None);
75 return Some(RangeInfo::new(range, (binding, analyzer)));
76 };
77 let name_ref = find_node_at_offset::<ast::NameRef>(syntax, position.offset)?;
78 let range = name_ref.syntax().text_range();
79 let analyzer = hir::SourceAnalyzer::new(db, position.file_id, name_ref.syntax(), None);
80 let resolved = analyzer.resolve_local_name(&name_ref)?;
81 if let Either::A(ptr) = resolved.ptr() {
82 if let ast::Pat::BindPat(binding) = ptr.to_node(source_file.syntax()) {
83 return Some(RangeInfo::new(range, (binding, analyzer)));
84 }
85 }
86 None
87 }
88}
89 87
90pub(crate) fn rename( 88 let references = process_definition(db, def, name);
91 db: &RootDatabase, 89
92 position: FilePosition, 90 Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references }))
93 new_name: &str,
94) -> Option<RangeInfo<SourceChange>> {
95 let parse = db.parse(position.file_id);
96 if let Some((ast_name, ast_module)) =
97 find_name_and_module_at_offset(parse.tree().syntax(), position)
98 {
99 let range = ast_name.syntax().text_range();
100 rename_mod(db, &ast_name, &ast_module, position, new_name)
101 .map(|info| RangeInfo::new(range, info))
102 } else {
103 rename_reference(db, position, new_name)
104 }
105} 91}
106 92
107fn find_name_and_module_at_offset( 93fn find_name<'a>(
94 db: &RootDatabase,
108 syntax: &SyntaxNode, 95 syntax: &SyntaxNode,
109 position: FilePosition, 96 position: FilePosition,
110) -> Option<(ast::Name, ast::Module)> { 97) -> Option<RangeInfo<(String, NameDefinition)>> {
111 let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; 98 if let Some(name) = find_node_at_offset::<ast::Name>(&syntax, position.offset) {
112 let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; 99 let def = classify_name(db, position.file_id, &name)?;
113 Some((ast_name, ast_module)) 100 let range = name.syntax().text_range();
114} 101 return Some(RangeInfo::new(range, (name.text().to_string(), def)));
115
116fn source_edit_from_file_id_range(
117 file_id: FileId,
118 range: TextRange,
119 new_name: &str,
120) -> SourceFileEdit {
121 SourceFileEdit {
122 file_id,
123 edit: {
124 let mut builder = ra_text_edit::TextEditBuilder::default();
125 builder.replace(range, new_name.into());
126 builder.finish()
127 },
128 } 102 }
103 let name_ref = find_node_at_offset::<ast::NameRef>(&syntax, position.offset)?;
104 let def = classify_name_ref(db, position.file_id, &name_ref)?;
105 let range = name_ref.syntax().text_range();
106 Some(RangeInfo::new(range, (name_ref.text().to_string(), def)))
129} 107}
130 108
131fn rename_mod( 109fn process_definition(db: &RootDatabase, def: NameDefinition, name: String) -> Vec<FileRange> {
132 db: &RootDatabase, 110 let pat = name.as_str();
133 ast_name: &ast::Name, 111 let scope = def.search_scope(db);
134 ast_module: &ast::Module, 112 let mut refs = vec![];
135 position: FilePosition, 113
136 new_name: &str, 114 for (file_id, search_range) in scope {
137) -> Option<SourceChange> { 115 let text = db.file_text(file_id);
138 let mut source_file_edits = Vec::new(); 116 let parse = Lazy::new(|| SourceFile::parse(&text));
139 let mut file_system_edits = Vec::new(); 117
140 let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() }; 118 for (idx, _) in text.match_indices(pat) {
141 if let Some(module) = hir::Module::from_declaration(db, module_src) { 119 let offset = TextUnit::from_usize(idx);
142 let src = module.definition_source(db); 120
143 let file_id = src.file_id.as_original_file(); 121 if let Some(name_ref) =
144 match src.ast { 122 find_node_at_offset::<ast::NameRef>(parse.tree().syntax(), offset)
145 ModuleSource::SourceFile(..) => { 123 {
146 let mod_path: RelativePathBuf = db.file_relative_path(file_id); 124 let range = name_ref.syntax().text_range();
147 // mod is defined in path/to/dir/mod.rs 125 if let Some(search_range) = search_range {
148 let dst_path = if mod_path.file_stem() == Some("mod") { 126 if !range.is_subrange(&search_range) {
149 mod_path 127 continue;
150 .parent() 128 }
151 .and_then(|p| p.parent()) 129 }
152 .or_else(|| Some(RelativePath::new(""))) 130 if let Some(d) = classify_name_ref(db, file_id, &name_ref) {
153 .map(|p| p.join(new_name).join("mod.rs")) 131 if d == def {
154 } else { 132 refs.push(FileRange { file_id, range });
155 Some(mod_path.with_file_name(new_name).with_extension("rs")) 133 }
156 };
157 if let Some(path) = dst_path {
158 let move_file = FileSystemEdit::MoveFile {
159 src: file_id,
160 dst_source_root: db.file_source_root(position.file_id),
161 dst_path: path,
162 };
163 file_system_edits.push(move_file);
164 } 134 }
165 } 135 }
166 ModuleSource::Module(..) => {}
167 } 136 }
168 } 137 }
169 138 refs
170 let edit = SourceFileEdit {
171 file_id: position.file_id,
172 edit: {
173 let mut builder = ra_text_edit::TextEditBuilder::default();
174 builder.replace(ast_name.syntax().text_range(), new_name.into());
175 builder.finish()
176 },
177 };
178 source_file_edits.push(edit);
179
180 Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits))
181}
182
183fn rename_reference(
184 db: &RootDatabase,
185 position: FilePosition,
186 new_name: &str,
187) -> Option<RangeInfo<SourceChange>> {
188 let RangeInfo { range, info: refs } = find_all_refs(db, position)?;
189
190 let edit = refs
191 .into_iter()
192 .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name))
193 .collect::<Vec<_>>();
194
195 if edit.is_empty() {
196 return None;
197 }
198
199 Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit)))
200} 139}
201 140
202#[cfg(test)] 141#[cfg(test)]
203mod tests { 142mod tests {
204 use crate::{ 143 use crate::{
205 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 144 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position,
206 ReferenceSearchResult, 145 ReferenceSearchResult,
207 }; 146 };
208 use insta::assert_debug_snapshot;
209 use test_utils::assert_eq_text;
210 147
211 #[test] 148 #[test]
212 fn test_find_all_refs_for_local() { 149 fn test_find_all_refs_for_local() {
@@ -249,211 +186,144 @@ mod tests {
249 assert_eq!(refs.len(), 2); 186 assert_eq!(refs.len(), 2);
250 } 187 }
251 188
252 fn get_all_refs(text: &str) -> ReferenceSearchResult {
253 let (analysis, position) = single_file_with_position(text);
254 analysis.find_all_refs(position).unwrap().unwrap()
255 }
256
257 #[test] 189 #[test]
258 fn test_rename_for_local() { 190 fn test_find_all_refs_field_name() {
259 test_rename( 191 let code = r#"
260 r#" 192 //- /lib.rs
261 fn main() { 193 struct Foo {
262 let mut i = 1; 194 pub spam<|>: u32,
263 let j = 1; 195 }
264 i = i<|> + j;
265
266 {
267 i = 0;
268 }
269
270 i = 5;
271 }"#,
272 "k",
273 r#"
274 fn main() {
275 let mut k = 1;
276 let j = 1;
277 k = k + j;
278 196
279 { 197 fn main(s: Foo) {
280 k = 0; 198 let f = s.spam;
281 } 199 }
200 "#;
282 201
283 k = 5; 202 let refs = get_all_refs(code);
284 }"#, 203 assert_eq!(refs.len(), 2);
285 );
286 } 204 }
287 205
288 #[test] 206 #[test]
289 fn test_rename_for_param_inside() { 207 fn test_find_all_refs_impl_item_name() {
290 test_rename( 208 let code = r#"
291 r#" 209 //- /lib.rs
292 fn foo(i : u32) -> u32 { 210 struct Foo;
293 i<|> 211 impl Foo {
294 }"#, 212 fn f<|>(&self) { }
295 "j", 213 }
296 r#" 214 "#;
297 fn foo(j : u32) -> u32 {
298 j
299 }"#,
300 );
301 }
302 215
303 #[test] 216 let refs = get_all_refs(code);
304 fn test_rename_refs_for_fn_param() { 217 assert_eq!(refs.len(), 1);
305 test_rename(
306 r#"
307 fn foo(i<|> : u32) -> u32 {
308 i
309 }"#,
310 "new_name",
311 r#"
312 fn foo(new_name : u32) -> u32 {
313 new_name
314 }"#,
315 );
316 } 218 }
317 219
318 #[test] 220 #[test]
319 fn test_rename_for_mut_param() { 221 fn test_find_all_refs_enum_var_name() {
320 test_rename( 222 let code = r#"
321 r#" 223 //- /lib.rs
322 fn foo(mut i<|> : u32) -> u32 { 224 enum Foo {
323 i 225 A,
324 }"#, 226 B<|>,
325 "new_name", 227 C,
326 r#" 228 }
327 fn foo(mut new_name : u32) -> u32 { 229 "#;
328 new_name 230
329 }"#, 231 let refs = get_all_refs(code);
330 ); 232 assert_eq!(refs.len(), 1);
331 } 233 }
332 234
333 #[test] 235 #[test]
334 fn test_rename_mod() { 236 fn test_find_all_refs_two_modules() {
335 let (analysis, position) = analysis_and_position( 237 let code = r#"
336 "
337 //- /lib.rs 238 //- /lib.rs
338 mod bar; 239 pub mod foo;
240 pub mod bar;
241
242 fn f() {
243 let i = foo::Foo { n: 5 };
244 }
245
246 //- /foo.rs
247 use crate::bar;
248
249 pub struct Foo {
250 pub n: u32,
251 }
252
253 fn f() {
254 let i = bar::Bar { n: 5 };
255 }
339 256
340 //- /bar.rs 257 //- /bar.rs
258 use crate::foo;
259
260 pub struct Bar {
261 pub n: u32,
262 }
263
264 fn f() {
265 let i = foo::Foo<|> { n: 5 };
266 }
267 "#;
268
269 let (analysis, pos) = analysis_and_position(code);
270 let refs = analysis.find_all_refs(pos).unwrap().unwrap();
271 assert_eq!(refs.len(), 3);
272 }
273
274 // `mod foo;` is not in the results because `foo` is an `ast::Name`.
275 // So, there are two references: the first one is a definition of the `foo` module,
276 // which is the whole `foo.rs`, and the second one is in `use foo::Foo`.
277 #[test]
278 fn test_find_all_refs_decl_module() {
279 let code = r#"
280 //- /lib.rs
341 mod foo<|>; 281 mod foo<|>;
342 282
343 //- /bar/foo.rs 283 use foo::Foo;
344 // emtpy 284
345 ", 285 fn f() {
346 ); 286 let i = Foo { n: 5 };
347 let new_name = "foo2"; 287 }
348 let source_change = analysis.rename(position, new_name).unwrap(); 288
349 assert_debug_snapshot!(&source_change, 289 //- /foo.rs
350@r###" 290 pub struct Foo {
351 Some( 291 pub n: u32,
352 RangeInfo { 292 }
353 range: [4; 7), 293 "#;
354 info: SourceChange { 294
355 label: "rename", 295 let (analysis, pos) = analysis_and_position(code);
356 source_file_edits: [ 296 let refs = analysis.find_all_refs(pos).unwrap().unwrap();
357 SourceFileEdit { 297 assert_eq!(refs.len(), 2);
358 file_id: FileId(
359 2,
360 ),
361 edit: TextEdit {
362 atoms: [
363 AtomTextEdit {
364 delete: [4; 7),
365 insert: "foo2",
366 },
367 ],
368 },
369 },
370 ],
371 file_system_edits: [
372 MoveFile {
373 src: FileId(
374 3,
375 ),
376 dst_source_root: SourceRootId(
377 0,
378 ),
379 dst_path: "bar/foo2.rs",
380 },
381 ],
382 cursor_position: None,
383 },
384 },
385 )
386 "###);
387 } 298 }
388 299
389 #[test] 300 #[test]
390 fn test_rename_mod_in_dir() { 301 fn test_find_all_refs_super_mod_vis() {
391 let (analysis, position) = analysis_and_position( 302 let code = r#"
392 "
393 //- /lib.rs 303 //- /lib.rs
394 mod fo<|>o; 304 mod foo;
395 //- /foo/mod.rs 305
396 // emtpy 306 //- /foo.rs
397 ", 307 mod some;
398 ); 308 use some::Foo;
399 let new_name = "foo2"; 309
400 let source_change = analysis.rename(position, new_name).unwrap(); 310 fn f() {
401 assert_debug_snapshot!(&source_change, 311 let i = Foo { n: 5 };
402 @r###" 312 }
403 Some( 313
404 RangeInfo { 314 //- /foo/some.rs
405 range: [4; 7), 315 pub(super) struct Foo<|> {
406 info: SourceChange { 316 pub n: u32,
407 label: "rename", 317 }
408 source_file_edits: [ 318 "#;
409 SourceFileEdit { 319
410 file_id: FileId( 320 let (analysis, pos) = analysis_and_position(code);
411 1, 321 let refs = analysis.find_all_refs(pos).unwrap().unwrap();
412 ), 322 assert_eq!(refs.len(), 3);
413 edit: TextEdit {
414 atoms: [
415 AtomTextEdit {
416 delete: [4; 7),
417 insert: "foo2",
418 },
419 ],
420 },
421 },
422 ],
423 file_system_edits: [
424 MoveFile {
425 src: FileId(
426 2,
427 ),
428 dst_source_root: SourceRootId(
429 0,
430 ),
431 dst_path: "foo2/mod.rs",
432 },
433 ],
434 cursor_position: None,
435 },
436 },
437 )
438 "###
439 );
440 } 323 }
441 324
442 fn test_rename(text: &str, new_name: &str, expected: &str) { 325 fn get_all_refs(text: &str) -> ReferenceSearchResult {
443 let (analysis, position) = single_file_with_position(text); 326 let (analysis, position) = single_file_with_position(text);
444 let source_change = analysis.rename(position, new_name).unwrap(); 327 analysis.find_all_refs(position).unwrap().unwrap()
445 let mut text_edit_builder = ra_text_edit::TextEditBuilder::default();
446 let mut file_id: Option<FileId> = None;
447 if let Some(change) = source_change {
448 for edit in change.info.source_file_edits {
449 file_id = Some(edit.file_id);
450 for atom in edit.edit.as_atoms() {
451 text_edit_builder.replace(atom.delete, atom.insert.clone());
452 }
453 }
454 }
455 let result =
456 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap());
457 assert_eq_text!(expected, &*result);
458 } 328 }
459} 329}