aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api/src/references/rename.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api/src/references/rename.rs')
-rw-r--r--crates/ra_ide_api/src/references/rename.rs469
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
3use hir::ModuleSource;
4use ra_db::{SourceDatabase, SourceDatabaseExt};
5use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode};
6use relative_path::{RelativePath, RelativePathBuf};
7
8use crate::{
9 db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange,
10 SourceFileEdit, TextRange,
11};
12
13use super::find_all_refs;
14
15pub(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
32fn 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
41fn 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
56fn 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
108fn 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)]
128mod 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}