diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2019-10-23 09:20:18 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2019-10-23 09:20:18 +0100 |
commit | 4f4fe14fab96c8b40763f9ed5bef51942fd7e504 (patch) | |
tree | cdf5a5b39600b41a9eee301b9760cde1282aeb33 /crates/ra_ide_api/src/references/rename.rs | |
parent | c15ee97fff4324981d03f65210d794664c28f0e4 (diff) | |
parent | decfd28bd14b56befa17257694caacc57a090939 (diff) |
Merge #1892
1892: Find usages r=matklad a=viorina
Fixes #1622.
Co-authored-by: Ekaterina Babshukova <[email protected]>
Diffstat (limited to 'crates/ra_ide_api/src/references/rename.rs')
-rw-r--r-- | crates/ra_ide_api/src/references/rename.rs | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/references/rename.rs b/crates/ra_ide_api/src/references/rename.rs new file mode 100644 index 000000000..0e2e088e0 --- /dev/null +++ b/crates/ra_ide_api/src/references/rename.rs | |||
@@ -0,0 +1,469 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use hir::ModuleSource; | ||
4 | use ra_db::{SourceDatabase, SourceDatabaseExt}; | ||
5 | use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; | ||
6 | use relative_path::{RelativePath, RelativePathBuf}; | ||
7 | |||
8 | use crate::{ | ||
9 | db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, | ||
10 | SourceFileEdit, TextRange, | ||
11 | }; | ||
12 | |||
13 | use super::find_all_refs; | ||
14 | |||
15 | pub(crate) fn rename( | ||
16 | db: &RootDatabase, | ||
17 | position: FilePosition, | ||
18 | new_name: &str, | ||
19 | ) -> Option<RangeInfo<SourceChange>> { | ||
20 | let parse = db.parse(position.file_id); | ||
21 | if let Some((ast_name, ast_module)) = | ||
22 | find_name_and_module_at_offset(parse.tree().syntax(), position) | ||
23 | { | ||
24 | let range = ast_name.syntax().text_range(); | ||
25 | rename_mod(db, &ast_name, &ast_module, position, new_name) | ||
26 | .map(|info| RangeInfo::new(range, info)) | ||
27 | } else { | ||
28 | rename_reference(db, position, new_name) | ||
29 | } | ||
30 | } | ||
31 | |||
32 | fn find_name_and_module_at_offset( | ||
33 | syntax: &SyntaxNode, | ||
34 | position: FilePosition, | ||
35 | ) -> Option<(ast::Name, ast::Module)> { | ||
36 | let ast_name = find_node_at_offset::<ast::Name>(syntax, position.offset)?; | ||
37 | let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; | ||
38 | Some((ast_name, ast_module)) | ||
39 | } | ||
40 | |||
41 | fn source_edit_from_file_id_range( | ||
42 | file_id: FileId, | ||
43 | range: TextRange, | ||
44 | new_name: &str, | ||
45 | ) -> SourceFileEdit { | ||
46 | SourceFileEdit { | ||
47 | file_id, | ||
48 | edit: { | ||
49 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
50 | builder.replace(range, new_name.into()); | ||
51 | builder.finish() | ||
52 | }, | ||
53 | } | ||
54 | } | ||
55 | |||
56 | fn rename_mod( | ||
57 | db: &RootDatabase, | ||
58 | ast_name: &ast::Name, | ||
59 | ast_module: &ast::Module, | ||
60 | position: FilePosition, | ||
61 | new_name: &str, | ||
62 | ) -> Option<SourceChange> { | ||
63 | let mut source_file_edits = Vec::new(); | ||
64 | let mut file_system_edits = Vec::new(); | ||
65 | let module_src = hir::Source { file_id: position.file_id.into(), ast: ast_module.clone() }; | ||
66 | if let Some(module) = hir::Module::from_declaration(db, module_src) { | ||
67 | let src = module.definition_source(db); | ||
68 | let file_id = src.file_id.original_file(db); | ||
69 | match src.ast { | ||
70 | ModuleSource::SourceFile(..) => { | ||
71 | let mod_path: RelativePathBuf = db.file_relative_path(file_id); | ||
72 | // mod is defined in path/to/dir/mod.rs | ||
73 | let dst_path = if mod_path.file_stem() == Some("mod") { | ||
74 | mod_path | ||
75 | .parent() | ||
76 | .and_then(|p| p.parent()) | ||
77 | .or_else(|| Some(RelativePath::new(""))) | ||
78 | .map(|p| p.join(new_name).join("mod.rs")) | ||
79 | } else { | ||
80 | Some(mod_path.with_file_name(new_name).with_extension("rs")) | ||
81 | }; | ||
82 | if let Some(path) = dst_path { | ||
83 | let move_file = FileSystemEdit::MoveFile { | ||
84 | src: file_id, | ||
85 | dst_source_root: db.file_source_root(position.file_id), | ||
86 | dst_path: path, | ||
87 | }; | ||
88 | file_system_edits.push(move_file); | ||
89 | } | ||
90 | } | ||
91 | ModuleSource::Module(..) => {} | ||
92 | } | ||
93 | } | ||
94 | |||
95 | let edit = SourceFileEdit { | ||
96 | file_id: position.file_id, | ||
97 | edit: { | ||
98 | let mut builder = ra_text_edit::TextEditBuilder::default(); | ||
99 | builder.replace(ast_name.syntax().text_range(), new_name.into()); | ||
100 | builder.finish() | ||
101 | }, | ||
102 | }; | ||
103 | source_file_edits.push(edit); | ||
104 | |||
105 | Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) | ||
106 | } | ||
107 | |||
108 | fn rename_reference( | ||
109 | db: &RootDatabase, | ||
110 | position: FilePosition, | ||
111 | new_name: &str, | ||
112 | ) -> Option<RangeInfo<SourceChange>> { | ||
113 | let RangeInfo { range, info: refs } = find_all_refs(db, position)?; | ||
114 | |||
115 | let edit = refs | ||
116 | .into_iter() | ||
117 | .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) | ||
118 | .collect::<Vec<_>>(); | ||
119 | |||
120 | if edit.is_empty() { | ||
121 | return None; | ||
122 | } | ||
123 | |||
124 | Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) | ||
125 | } | ||
126 | |||
127 | #[cfg(test)] | ||
128 | mod tests { | ||
129 | use crate::{ | ||
130 | mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, | ||
131 | ReferenceSearchResult, | ||
132 | }; | ||
133 | use insta::assert_debug_snapshot; | ||
134 | use test_utils::assert_eq_text; | ||
135 | |||
136 | #[test] | ||
137 | fn test_find_all_refs_for_local() { | ||
138 | let code = r#" | ||
139 | fn main() { | ||
140 | let mut i = 1; | ||
141 | let j = 1; | ||
142 | i = i<|> + j; | ||
143 | |||
144 | { | ||
145 | i = 0; | ||
146 | } | ||
147 | |||
148 | i = 5; | ||
149 | }"#; | ||
150 | |||
151 | let refs = get_all_refs(code); | ||
152 | assert_eq!(refs.len(), 5); | ||
153 | } | ||
154 | |||
155 | #[test] | ||
156 | fn test_find_all_refs_for_param_inside() { | ||
157 | let code = r#" | ||
158 | fn foo(i : u32) -> u32 { | ||
159 | i<|> | ||
160 | }"#; | ||
161 | |||
162 | let refs = get_all_refs(code); | ||
163 | assert_eq!(refs.len(), 2); | ||
164 | } | ||
165 | |||
166 | #[test] | ||
167 | fn test_find_all_refs_for_fn_param() { | ||
168 | let code = r#" | ||
169 | fn foo(i<|> : u32) -> u32 { | ||
170 | i | ||
171 | }"#; | ||
172 | |||
173 | let refs = get_all_refs(code); | ||
174 | assert_eq!(refs.len(), 2); | ||
175 | } | ||
176 | |||
177 | #[test] | ||
178 | fn test_find_all_refs_field_name() { | ||
179 | let code = r#" | ||
180 | //- /lib.rs | ||
181 | struct Foo { | ||
182 | pub spam<|>: u32, | ||
183 | } | ||
184 | |||
185 | fn main(s: Foo) { | ||
186 | let f = s.spam; | ||
187 | } | ||
188 | "#; | ||
189 | |||
190 | let refs = get_all_refs(code); | ||
191 | assert_eq!(refs.len(), 2); | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn test_find_all_refs_impl_item_name() { | ||
196 | let code = r#" | ||
197 | //- /lib.rs | ||
198 | struct Foo; | ||
199 | impl Foo { | ||
200 | fn f<|>(&self) { } | ||
201 | } | ||
202 | "#; | ||
203 | |||
204 | let refs = get_all_refs(code); | ||
205 | assert_eq!(refs.len(), 1); | ||
206 | } | ||
207 | |||
208 | #[test] | ||
209 | fn test_find_all_refs_enum_var_name() { | ||
210 | let code = r#" | ||
211 | //- /lib.rs | ||
212 | enum Foo { | ||
213 | A, | ||
214 | B<|>, | ||
215 | C, | ||
216 | } | ||
217 | "#; | ||
218 | |||
219 | let refs = get_all_refs(code); | ||
220 | assert_eq!(refs.len(), 1); | ||
221 | } | ||
222 | |||
223 | #[test] | ||
224 | fn test_find_all_refs_modules() { | ||
225 | let code = r#" | ||
226 | //- /lib.rs | ||
227 | pub mod foo; | ||
228 | pub mod bar; | ||
229 | |||
230 | fn f() { | ||
231 | let i = foo::Foo { n: 5 }; | ||
232 | } | ||
233 | |||
234 | //- /foo.rs | ||
235 | use crate::bar; | ||
236 | |||
237 | pub struct Foo { | ||
238 | pub n: u32, | ||
239 | } | ||
240 | |||
241 | fn f() { | ||
242 | let i = bar::Bar { n: 5 }; | ||
243 | } | ||
244 | |||
245 | //- /bar.rs | ||
246 | use crate::foo; | ||
247 | |||
248 | pub struct Bar { | ||
249 | pub n: u32, | ||
250 | } | ||
251 | |||
252 | fn f() { | ||
253 | let i = foo::Foo<|> { n: 5 }; | ||
254 | } | ||
255 | "#; | ||
256 | |||
257 | let (analysis, pos) = analysis_and_position(code); | ||
258 | let refs = analysis.find_all_refs(pos).unwrap().unwrap(); | ||
259 | assert_eq!(refs.len(), 3); | ||
260 | } | ||
261 | |||
262 | fn get_all_refs(text: &str) -> ReferenceSearchResult { | ||
263 | let (analysis, position) = single_file_with_position(text); | ||
264 | analysis.find_all_refs(position).unwrap().unwrap() | ||
265 | } | ||
266 | |||
267 | #[test] | ||
268 | fn test_rename_for_local() { | ||
269 | test_rename( | ||
270 | r#" | ||
271 | fn main() { | ||
272 | let mut i = 1; | ||
273 | let j = 1; | ||
274 | i = i<|> + j; | ||
275 | |||
276 | { | ||
277 | i = 0; | ||
278 | } | ||
279 | |||
280 | i = 5; | ||
281 | }"#, | ||
282 | "k", | ||
283 | r#" | ||
284 | fn main() { | ||
285 | let mut k = 1; | ||
286 | let j = 1; | ||
287 | k = k + j; | ||
288 | |||
289 | { | ||
290 | k = 0; | ||
291 | } | ||
292 | |||
293 | k = 5; | ||
294 | }"#, | ||
295 | ); | ||
296 | } | ||
297 | |||
298 | #[test] | ||
299 | fn test_rename_for_param_inside() { | ||
300 | test_rename( | ||
301 | r#" | ||
302 | fn foo(i : u32) -> u32 { | ||
303 | i<|> | ||
304 | }"#, | ||
305 | "j", | ||
306 | r#" | ||
307 | fn foo(j : u32) -> u32 { | ||
308 | j | ||
309 | }"#, | ||
310 | ); | ||
311 | } | ||
312 | |||
313 | #[test] | ||
314 | fn test_rename_refs_for_fn_param() { | ||
315 | test_rename( | ||
316 | r#" | ||
317 | fn foo(i<|> : u32) -> u32 { | ||
318 | i | ||
319 | }"#, | ||
320 | "new_name", | ||
321 | r#" | ||
322 | fn foo(new_name : u32) -> u32 { | ||
323 | new_name | ||
324 | }"#, | ||
325 | ); | ||
326 | } | ||
327 | |||
328 | #[test] | ||
329 | fn test_rename_for_mut_param() { | ||
330 | test_rename( | ||
331 | r#" | ||
332 | fn foo(mut i<|> : u32) -> u32 { | ||
333 | i | ||
334 | }"#, | ||
335 | "new_name", | ||
336 | r#" | ||
337 | fn foo(mut new_name : u32) -> u32 { | ||
338 | new_name | ||
339 | }"#, | ||
340 | ); | ||
341 | } | ||
342 | |||
343 | #[test] | ||
344 | fn test_rename_mod() { | ||
345 | let (analysis, position) = analysis_and_position( | ||
346 | " | ||
347 | //- /lib.rs | ||
348 | mod bar; | ||
349 | |||
350 | //- /bar.rs | ||
351 | mod foo<|>; | ||
352 | |||
353 | //- /bar/foo.rs | ||
354 | // emtpy | ||
355 | ", | ||
356 | ); | ||
357 | let new_name = "foo2"; | ||
358 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
359 | assert_debug_snapshot!(&source_change, | ||
360 | @r###" | ||
361 | Some( | ||
362 | RangeInfo { | ||
363 | range: [4; 7), | ||
364 | info: SourceChange { | ||
365 | label: "rename", | ||
366 | source_file_edits: [ | ||
367 | SourceFileEdit { | ||
368 | file_id: FileId( | ||
369 | 2, | ||
370 | ), | ||
371 | edit: TextEdit { | ||
372 | atoms: [ | ||
373 | AtomTextEdit { | ||
374 | delete: [4; 7), | ||
375 | insert: "foo2", | ||
376 | }, | ||
377 | ], | ||
378 | }, | ||
379 | }, | ||
380 | ], | ||
381 | file_system_edits: [ | ||
382 | MoveFile { | ||
383 | src: FileId( | ||
384 | 3, | ||
385 | ), | ||
386 | dst_source_root: SourceRootId( | ||
387 | 0, | ||
388 | ), | ||
389 | dst_path: "bar/foo2.rs", | ||
390 | }, | ||
391 | ], | ||
392 | cursor_position: None, | ||
393 | }, | ||
394 | }, | ||
395 | ) | ||
396 | "###); | ||
397 | } | ||
398 | |||
399 | #[test] | ||
400 | fn test_rename_mod_in_dir() { | ||
401 | let (analysis, position) = analysis_and_position( | ||
402 | " | ||
403 | //- /lib.rs | ||
404 | mod fo<|>o; | ||
405 | //- /foo/mod.rs | ||
406 | // emtpy | ||
407 | ", | ||
408 | ); | ||
409 | let new_name = "foo2"; | ||
410 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
411 | assert_debug_snapshot!(&source_change, | ||
412 | @r###" | ||
413 | Some( | ||
414 | RangeInfo { | ||
415 | range: [4; 7), | ||
416 | info: SourceChange { | ||
417 | label: "rename", | ||
418 | source_file_edits: [ | ||
419 | SourceFileEdit { | ||
420 | file_id: FileId( | ||
421 | 1, | ||
422 | ), | ||
423 | edit: TextEdit { | ||
424 | atoms: [ | ||
425 | AtomTextEdit { | ||
426 | delete: [4; 7), | ||
427 | insert: "foo2", | ||
428 | }, | ||
429 | ], | ||
430 | }, | ||
431 | }, | ||
432 | ], | ||
433 | file_system_edits: [ | ||
434 | MoveFile { | ||
435 | src: FileId( | ||
436 | 2, | ||
437 | ), | ||
438 | dst_source_root: SourceRootId( | ||
439 | 0, | ||
440 | ), | ||
441 | dst_path: "foo2/mod.rs", | ||
442 | }, | ||
443 | ], | ||
444 | cursor_position: None, | ||
445 | }, | ||
446 | }, | ||
447 | ) | ||
448 | "### | ||
449 | ); | ||
450 | } | ||
451 | |||
452 | fn test_rename(text: &str, new_name: &str, expected: &str) { | ||
453 | let (analysis, position) = single_file_with_position(text); | ||
454 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
455 | let mut text_edit_builder = ra_text_edit::TextEditBuilder::default(); | ||
456 | let mut file_id: Option<FileId> = None; | ||
457 | if let Some(change) = source_change { | ||
458 | for edit in change.info.source_file_edits { | ||
459 | file_id = Some(edit.file_id); | ||
460 | for atom in edit.edit.as_atoms() { | ||
461 | text_edit_builder.replace(atom.delete, atom.insert.clone()); | ||
462 | } | ||
463 | } | ||
464 | } | ||
465 | let result = | ||
466 | text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); | ||
467 | assert_eq_text!(expected, &*result); | ||
468 | } | ||
469 | } | ||