diff options
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r-- | crates/ide/src/references/rename.rs | 1010 |
1 files changed, 1010 insertions, 0 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs new file mode 100644 index 000000000..d73dc9cd0 --- /dev/null +++ b/crates/ide/src/references/rename.rs | |||
@@ -0,0 +1,1010 @@ | |||
1 | //! FIXME: write short doc here | ||
2 | |||
3 | use base_db::SourceDatabaseExt; | ||
4 | use hir::{Module, ModuleDef, ModuleSource, Semantics}; | ||
5 | use ide_db::{ | ||
6 | defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, | ||
7 | RootDatabase, | ||
8 | }; | ||
9 | use std::convert::TryInto; | ||
10 | use syntax::{ | ||
11 | algo::find_node_at_offset, | ||
12 | ast::{self, NameOwner}, | ||
13 | lex_single_valid_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, | ||
14 | }; | ||
15 | use test_utils::mark; | ||
16 | use text_edit::TextEdit; | ||
17 | |||
18 | use crate::{ | ||
19 | references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, | ||
20 | SourceChange, SourceFileEdit, TextRange, TextSize, | ||
21 | }; | ||
22 | |||
23 | pub(crate) fn rename( | ||
24 | db: &RootDatabase, | ||
25 | position: FilePosition, | ||
26 | new_name: &str, | ||
27 | ) -> Option<RangeInfo<SourceChange>> { | ||
28 | let sema = Semantics::new(db); | ||
29 | |||
30 | match lex_single_valid_syntax_kind(new_name)? { | ||
31 | SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), | ||
32 | SyntaxKind::SELF_KW => return rename_to_self(&sema, position), | ||
33 | _ => return None, | ||
34 | } | ||
35 | |||
36 | let source_file = sema.parse(position.file_id); | ||
37 | let syntax = source_file.syntax(); | ||
38 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { | ||
39 | rename_mod(&sema, position, module, new_name) | ||
40 | } else if let Some(self_token) = | ||
41 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | ||
42 | { | ||
43 | rename_self_to_param(&sema, position, self_token, new_name) | ||
44 | } else { | ||
45 | rename_reference(&sema, position, new_name) | ||
46 | } | ||
47 | } | ||
48 | |||
49 | fn find_module_at_offset( | ||
50 | sema: &Semantics<RootDatabase>, | ||
51 | position: FilePosition, | ||
52 | syntax: &SyntaxNode, | ||
53 | ) -> Option<Module> { | ||
54 | let ident = syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::IDENT)?; | ||
55 | |||
56 | let module = match_ast! { | ||
57 | match (ident.parent()) { | ||
58 | ast::NameRef(name_ref) => { | ||
59 | match classify_name_ref(sema, &name_ref)? { | ||
60 | NameRefClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | ||
61 | _ => return None, | ||
62 | } | ||
63 | }, | ||
64 | ast::Name(name) => { | ||
65 | match classify_name(&sema, &name)? { | ||
66 | NameClass::Definition(Definition::ModuleDef(ModuleDef::Module(module))) => module, | ||
67 | _ => return None, | ||
68 | } | ||
69 | }, | ||
70 | _ => return None, | ||
71 | } | ||
72 | }; | ||
73 | |||
74 | Some(module) | ||
75 | } | ||
76 | |||
77 | fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFileEdit { | ||
78 | let mut replacement_text = String::new(); | ||
79 | let file_id = reference.file_range.file_id; | ||
80 | let range = match reference.kind { | ||
81 | ReferenceKind::FieldShorthandForField => { | ||
82 | mark::hit!(test_rename_struct_field_for_shorthand); | ||
83 | replacement_text.push_str(new_name); | ||
84 | replacement_text.push_str(": "); | ||
85 | TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) | ||
86 | } | ||
87 | ReferenceKind::FieldShorthandForLocal => { | ||
88 | mark::hit!(test_rename_local_for_field_shorthand); | ||
89 | replacement_text.push_str(": "); | ||
90 | replacement_text.push_str(new_name); | ||
91 | TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) | ||
92 | } | ||
93 | _ => { | ||
94 | replacement_text.push_str(new_name); | ||
95 | reference.file_range.range | ||
96 | } | ||
97 | }; | ||
98 | SourceFileEdit { file_id, edit: TextEdit::replace(range, replacement_text) } | ||
99 | } | ||
100 | |||
101 | fn rename_mod( | ||
102 | sema: &Semantics<RootDatabase>, | ||
103 | position: FilePosition, | ||
104 | module: Module, | ||
105 | new_name: &str, | ||
106 | ) -> Option<RangeInfo<SourceChange>> { | ||
107 | let mut source_file_edits = Vec::new(); | ||
108 | let mut file_system_edits = Vec::new(); | ||
109 | |||
110 | let src = module.definition_source(sema.db); | ||
111 | let file_id = src.file_id.original_file(sema.db); | ||
112 | match src.value { | ||
113 | ModuleSource::SourceFile(..) => { | ||
114 | // mod is defined in path/to/dir/mod.rs | ||
115 | let dst = if module.is_mod_rs(sema.db) { | ||
116 | format!("../{}/mod.rs", new_name) | ||
117 | } else { | ||
118 | format!("{}.rs", new_name) | ||
119 | }; | ||
120 | let move_file = FileSystemEdit::MoveFile { src: file_id, anchor: file_id, dst }; | ||
121 | file_system_edits.push(move_file); | ||
122 | } | ||
123 | ModuleSource::Module(..) => {} | ||
124 | } | ||
125 | |||
126 | if let Some(src) = module.declaration_source(sema.db) { | ||
127 | let file_id = src.file_id.original_file(sema.db); | ||
128 | let name = src.value.name()?; | ||
129 | let edit = SourceFileEdit { | ||
130 | file_id, | ||
131 | edit: TextEdit::replace(name.syntax().text_range(), new_name.into()), | ||
132 | }; | ||
133 | source_file_edits.push(edit); | ||
134 | } | ||
135 | |||
136 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; | ||
137 | let ref_edits = refs | ||
138 | .references | ||
139 | .into_iter() | ||
140 | .map(|reference| source_edit_from_reference(reference, new_name)); | ||
141 | source_file_edits.extend(ref_edits); | ||
142 | |||
143 | Some(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) | ||
144 | } | ||
145 | |||
146 | fn rename_to_self( | ||
147 | sema: &Semantics<RootDatabase>, | ||
148 | position: FilePosition, | ||
149 | ) -> Option<RangeInfo<SourceChange>> { | ||
150 | let source_file = sema.parse(position.file_id); | ||
151 | let syn = source_file.syntax(); | ||
152 | |||
153 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; | ||
154 | let params = fn_def.param_list()?; | ||
155 | if params.self_param().is_some() { | ||
156 | return None; // method already has self param | ||
157 | } | ||
158 | let first_param = params.params().next()?; | ||
159 | let mutable = match first_param.ty() { | ||
160 | Some(ast::Type::RefType(rt)) => rt.mut_token().is_some(), | ||
161 | _ => return None, // not renaming other types | ||
162 | }; | ||
163 | |||
164 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; | ||
165 | |||
166 | let param_range = first_param.syntax().text_range(); | ||
167 | let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs | ||
168 | .into_iter() | ||
169 | .partition(|reference| param_range.intersect(reference.file_range.range).is_some()); | ||
170 | |||
171 | if param_ref.is_empty() { | ||
172 | return None; | ||
173 | } | ||
174 | |||
175 | let mut edits = usages | ||
176 | .into_iter() | ||
177 | .map(|reference| source_edit_from_reference(reference, "self")) | ||
178 | .collect::<Vec<_>>(); | ||
179 | |||
180 | edits.push(SourceFileEdit { | ||
181 | file_id: position.file_id, | ||
182 | edit: TextEdit::replace( | ||
183 | param_range, | ||
184 | String::from(if mutable { "&mut self" } else { "&self" }), | ||
185 | ), | ||
186 | }); | ||
187 | |||
188 | Some(RangeInfo::new(range, SourceChange::from(edits))) | ||
189 | } | ||
190 | |||
191 | fn text_edit_from_self_param( | ||
192 | syn: &SyntaxNode, | ||
193 | self_param: &ast::SelfParam, | ||
194 | new_name: &str, | ||
195 | ) -> Option<TextEdit> { | ||
196 | fn target_type_name(impl_def: &ast::Impl) -> Option<String> { | ||
197 | if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { | ||
198 | return Some(p.path()?.segment()?.name_ref()?.text().to_string()); | ||
199 | } | ||
200 | None | ||
201 | } | ||
202 | |||
203 | let impl_def = find_node_at_offset::<ast::Impl>(syn, self_param.syntax().text_range().start())?; | ||
204 | let type_name = target_type_name(&impl_def)?; | ||
205 | |||
206 | let mut replacement_text = String::from(new_name); | ||
207 | replacement_text.push_str(": "); | ||
208 | replacement_text.push_str(self_param.mut_token().map_or("&", |_| "&mut ")); | ||
209 | replacement_text.push_str(type_name.as_str()); | ||
210 | |||
211 | Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) | ||
212 | } | ||
213 | |||
214 | fn rename_self_to_param( | ||
215 | sema: &Semantics<RootDatabase>, | ||
216 | position: FilePosition, | ||
217 | self_token: SyntaxToken, | ||
218 | new_name: &str, | ||
219 | ) -> Option<RangeInfo<SourceChange>> { | ||
220 | let source_file = sema.parse(position.file_id); | ||
221 | let syn = source_file.syntax(); | ||
222 | |||
223 | let text = sema.db.file_text(position.file_id); | ||
224 | let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)?; | ||
225 | let search_range = fn_def.syntax().text_range(); | ||
226 | |||
227 | let mut edits: Vec<SourceFileEdit> = vec![]; | ||
228 | |||
229 | for (idx, _) in text.match_indices("self") { | ||
230 | let offset: TextSize = idx.try_into().unwrap(); | ||
231 | if !search_range.contains_inclusive(offset) { | ||
232 | continue; | ||
233 | } | ||
234 | if let Some(ref usage) = | ||
235 | syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | ||
236 | { | ||
237 | let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { | ||
238 | text_edit_from_self_param(syn, self_param, new_name)? | ||
239 | } else { | ||
240 | TextEdit::replace(usage.text_range(), String::from(new_name)) | ||
241 | }; | ||
242 | edits.push(SourceFileEdit { file_id: position.file_id, edit }); | ||
243 | } | ||
244 | } | ||
245 | |||
246 | let range = ast::SelfParam::cast(self_token.parent()) | ||
247 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); | ||
248 | |||
249 | Some(RangeInfo::new(range, SourceChange::from(edits))) | ||
250 | } | ||
251 | |||
252 | fn rename_reference( | ||
253 | sema: &Semantics<RootDatabase>, | ||
254 | position: FilePosition, | ||
255 | new_name: &str, | ||
256 | ) -> Option<RangeInfo<SourceChange>> { | ||
257 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None)?; | ||
258 | |||
259 | let edit = refs | ||
260 | .into_iter() | ||
261 | .map(|reference| source_edit_from_reference(reference, new_name)) | ||
262 | .collect::<Vec<_>>(); | ||
263 | |||
264 | if edit.is_empty() { | ||
265 | return None; | ||
266 | } | ||
267 | |||
268 | Some(RangeInfo::new(range, SourceChange::from(edit))) | ||
269 | } | ||
270 | |||
271 | #[cfg(test)] | ||
272 | mod tests { | ||
273 | use expect::{expect, Expect}; | ||
274 | use stdx::trim_indent; | ||
275 | use test_utils::{assert_eq_text, mark}; | ||
276 | use text_edit::TextEdit; | ||
277 | |||
278 | use crate::{mock_analysis::analysis_and_position, FileId}; | ||
279 | |||
280 | fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { | ||
281 | let ra_fixture_after = &trim_indent(ra_fixture_after); | ||
282 | let (analysis, position) = analysis_and_position(ra_fixture_before); | ||
283 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
284 | let mut text_edit_builder = TextEdit::builder(); | ||
285 | let mut file_id: Option<FileId> = None; | ||
286 | if let Some(change) = source_change { | ||
287 | for edit in change.info.source_file_edits { | ||
288 | file_id = Some(edit.file_id); | ||
289 | for indel in edit.edit.into_iter() { | ||
290 | text_edit_builder.replace(indel.delete, indel.insert); | ||
291 | } | ||
292 | } | ||
293 | } | ||
294 | let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); | ||
295 | text_edit_builder.finish().apply(&mut result); | ||
296 | assert_eq_text!(ra_fixture_after, &*result); | ||
297 | } | ||
298 | |||
299 | fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { | ||
300 | let (analysis, position) = analysis_and_position(ra_fixture); | ||
301 | let source_change = analysis.rename(position, new_name).unwrap().unwrap(); | ||
302 | expect.assert_debug_eq(&source_change) | ||
303 | } | ||
304 | |||
305 | #[test] | ||
306 | fn test_rename_to_underscore() { | ||
307 | check("_", r#"fn main() { let i<|> = 1; }"#, r#"fn main() { let _ = 1; }"#); | ||
308 | } | ||
309 | |||
310 | #[test] | ||
311 | fn test_rename_to_raw_identifier() { | ||
312 | check("r#fn", r#"fn main() { let i<|> = 1; }"#, r#"fn main() { let r#fn = 1; }"#); | ||
313 | } | ||
314 | |||
315 | #[test] | ||
316 | fn test_rename_to_invalid_identifier() { | ||
317 | let (analysis, position) = analysis_and_position(r#"fn main() { let i<|> = 1; }"#); | ||
318 | let new_name = "invalid!"; | ||
319 | let source_change = analysis.rename(position, new_name).unwrap(); | ||
320 | assert!(source_change.is_none()); | ||
321 | } | ||
322 | |||
323 | #[test] | ||
324 | fn test_rename_for_local() { | ||
325 | check( | ||
326 | "k", | ||
327 | r#" | ||
328 | fn main() { | ||
329 | let mut i = 1; | ||
330 | let j = 1; | ||
331 | i = i<|> + j; | ||
332 | |||
333 | { i = 0; } | ||
334 | |||
335 | i = 5; | ||
336 | } | ||
337 | "#, | ||
338 | r#" | ||
339 | fn main() { | ||
340 | let mut k = 1; | ||
341 | let j = 1; | ||
342 | k = k + j; | ||
343 | |||
344 | { k = 0; } | ||
345 | |||
346 | k = 5; | ||
347 | } | ||
348 | "#, | ||
349 | ); | ||
350 | } | ||
351 | |||
352 | #[test] | ||
353 | fn test_rename_for_macro_args() { | ||
354 | check( | ||
355 | "b", | ||
356 | r#" | ||
357 | macro_rules! foo {($i:ident) => {$i} } | ||
358 | fn main() { | ||
359 | let a<|> = "test"; | ||
360 | foo!(a); | ||
361 | } | ||
362 | "#, | ||
363 | r#" | ||
364 | macro_rules! foo {($i:ident) => {$i} } | ||
365 | fn main() { | ||
366 | let b = "test"; | ||
367 | foo!(b); | ||
368 | } | ||
369 | "#, | ||
370 | ); | ||
371 | } | ||
372 | |||
373 | #[test] | ||
374 | fn test_rename_for_macro_args_rev() { | ||
375 | check( | ||
376 | "b", | ||
377 | r#" | ||
378 | macro_rules! foo {($i:ident) => {$i} } | ||
379 | fn main() { | ||
380 | let a = "test"; | ||
381 | foo!(a<|>); | ||
382 | } | ||
383 | "#, | ||
384 | r#" | ||
385 | macro_rules! foo {($i:ident) => {$i} } | ||
386 | fn main() { | ||
387 | let b = "test"; | ||
388 | foo!(b); | ||
389 | } | ||
390 | "#, | ||
391 | ); | ||
392 | } | ||
393 | |||
394 | #[test] | ||
395 | fn test_rename_for_macro_define_fn() { | ||
396 | check( | ||
397 | "bar", | ||
398 | r#" | ||
399 | macro_rules! define_fn {($id:ident) => { fn $id{} }} | ||
400 | define_fn!(foo); | ||
401 | fn main() { | ||
402 | fo<|>o(); | ||
403 | } | ||
404 | "#, | ||
405 | r#" | ||
406 | macro_rules! define_fn {($id:ident) => { fn $id{} }} | ||
407 | define_fn!(bar); | ||
408 | fn main() { | ||
409 | bar(); | ||
410 | } | ||
411 | "#, | ||
412 | ); | ||
413 | } | ||
414 | |||
415 | #[test] | ||
416 | fn test_rename_for_macro_define_fn_rev() { | ||
417 | check( | ||
418 | "bar", | ||
419 | r#" | ||
420 | macro_rules! define_fn {($id:ident) => { fn $id{} }} | ||
421 | define_fn!(fo<|>o); | ||
422 | fn main() { | ||
423 | foo(); | ||
424 | } | ||
425 | "#, | ||
426 | r#" | ||
427 | macro_rules! define_fn {($id:ident) => { fn $id{} }} | ||
428 | define_fn!(bar); | ||
429 | fn main() { | ||
430 | bar(); | ||
431 | } | ||
432 | "#, | ||
433 | ); | ||
434 | } | ||
435 | |||
436 | #[test] | ||
437 | fn test_rename_for_param_inside() { | ||
438 | check("j", r#"fn foo(i : u32) -> u32 { i<|> }"#, r#"fn foo(j : u32) -> u32 { j }"#); | ||
439 | } | ||
440 | |||
441 | #[test] | ||
442 | fn test_rename_refs_for_fn_param() { | ||
443 | check("j", r#"fn foo(i<|> : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#); | ||
444 | } | ||
445 | |||
446 | #[test] | ||
447 | fn test_rename_for_mut_param() { | ||
448 | check("j", r#"fn foo(mut i<|> : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#); | ||
449 | } | ||
450 | |||
451 | #[test] | ||
452 | fn test_rename_struct_field() { | ||
453 | check( | ||
454 | "j", | ||
455 | r#" | ||
456 | struct Foo { i<|>: i32 } | ||
457 | |||
458 | impl Foo { | ||
459 | fn new(i: i32) -> Self { | ||
460 | Self { i: i } | ||
461 | } | ||
462 | } | ||
463 | "#, | ||
464 | r#" | ||
465 | struct Foo { j: i32 } | ||
466 | |||
467 | impl Foo { | ||
468 | fn new(i: i32) -> Self { | ||
469 | Self { j: i } | ||
470 | } | ||
471 | } | ||
472 | "#, | ||
473 | ); | ||
474 | } | ||
475 | |||
476 | #[test] | ||
477 | fn test_rename_struct_field_for_shorthand() { | ||
478 | mark::check!(test_rename_struct_field_for_shorthand); | ||
479 | check( | ||
480 | "j", | ||
481 | r#" | ||
482 | struct Foo { i<|>: i32 } | ||
483 | |||
484 | impl Foo { | ||
485 | fn new(i: i32) -> Self { | ||
486 | Self { i } | ||
487 | } | ||
488 | } | ||
489 | "#, | ||
490 | r#" | ||
491 | struct Foo { j: i32 } | ||
492 | |||
493 | impl Foo { | ||
494 | fn new(i: i32) -> Self { | ||
495 | Self { j: i } | ||
496 | } | ||
497 | } | ||
498 | "#, | ||
499 | ); | ||
500 | } | ||
501 | |||
502 | #[test] | ||
503 | fn test_rename_local_for_field_shorthand() { | ||
504 | mark::check!(test_rename_local_for_field_shorthand); | ||
505 | check( | ||
506 | "j", | ||
507 | r#" | ||
508 | struct Foo { i: i32 } | ||
509 | |||
510 | impl Foo { | ||
511 | fn new(i<|>: i32) -> Self { | ||
512 | Self { i } | ||
513 | } | ||
514 | } | ||
515 | "#, | ||
516 | r#" | ||
517 | struct Foo { i: i32 } | ||
518 | |||
519 | impl Foo { | ||
520 | fn new(j: i32) -> Self { | ||
521 | Self { i: j } | ||
522 | } | ||
523 | } | ||
524 | "#, | ||
525 | ); | ||
526 | } | ||
527 | |||
528 | #[test] | ||
529 | fn test_field_shorthand_correct_struct() { | ||
530 | check( | ||
531 | "j", | ||
532 | r#" | ||
533 | struct Foo { i<|>: i32 } | ||
534 | struct Bar { i: i32 } | ||
535 | |||
536 | impl Bar { | ||
537 | fn new(i: i32) -> Self { | ||
538 | Self { i } | ||
539 | } | ||
540 | } | ||
541 | "#, | ||
542 | r#" | ||
543 | struct Foo { j: i32 } | ||
544 | struct Bar { i: i32 } | ||
545 | |||
546 | impl Bar { | ||
547 | fn new(i: i32) -> Self { | ||
548 | Self { i } | ||
549 | } | ||
550 | } | ||
551 | "#, | ||
552 | ); | ||
553 | } | ||
554 | |||
555 | #[test] | ||
556 | fn test_shadow_local_for_struct_shorthand() { | ||
557 | check( | ||
558 | "j", | ||
559 | r#" | ||
560 | struct Foo { i: i32 } | ||
561 | |||
562 | fn baz(i<|>: i32) -> Self { | ||
563 | let x = Foo { i }; | ||
564 | { | ||
565 | let i = 0; | ||
566 | Foo { i } | ||
567 | } | ||
568 | } | ||
569 | "#, | ||
570 | r#" | ||
571 | struct Foo { i: i32 } | ||
572 | |||
573 | fn baz(j: i32) -> Self { | ||
574 | let x = Foo { i: j }; | ||
575 | { | ||
576 | let i = 0; | ||
577 | Foo { i } | ||
578 | } | ||
579 | } | ||
580 | "#, | ||
581 | ); | ||
582 | } | ||
583 | |||
584 | #[test] | ||
585 | fn test_rename_mod() { | ||
586 | check_expect( | ||
587 | "foo2", | ||
588 | r#" | ||
589 | //- /lib.rs | ||
590 | mod bar; | ||
591 | |||
592 | //- /bar.rs | ||
593 | mod foo<|>; | ||
594 | |||
595 | //- /bar/foo.rs | ||
596 | // empty | ||
597 | "#, | ||
598 | expect![[r#" | ||
599 | RangeInfo { | ||
600 | range: 4..7, | ||
601 | info: SourceChange { | ||
602 | source_file_edits: [ | ||
603 | SourceFileEdit { | ||
604 | file_id: FileId( | ||
605 | 2, | ||
606 | ), | ||
607 | edit: TextEdit { | ||
608 | indels: [ | ||
609 | Indel { | ||
610 | insert: "foo2", | ||
611 | delete: 4..7, | ||
612 | }, | ||
613 | ], | ||
614 | }, | ||
615 | }, | ||
616 | ], | ||
617 | file_system_edits: [ | ||
618 | MoveFile { | ||
619 | src: FileId( | ||
620 | 3, | ||
621 | ), | ||
622 | anchor: FileId( | ||
623 | 3, | ||
624 | ), | ||
625 | dst: "foo2.rs", | ||
626 | }, | ||
627 | ], | ||
628 | is_snippet: false, | ||
629 | }, | ||
630 | } | ||
631 | "#]], | ||
632 | ); | ||
633 | } | ||
634 | |||
635 | #[test] | ||
636 | fn test_rename_mod_in_use_tree() { | ||
637 | check_expect( | ||
638 | "quux", | ||
639 | r#" | ||
640 | //- /main.rs | ||
641 | pub mod foo; | ||
642 | pub mod bar; | ||
643 | fn main() {} | ||
644 | |||
645 | //- /foo.rs | ||
646 | pub struct FooContent; | ||
647 | |||
648 | //- /bar.rs | ||
649 | use crate::foo<|>::FooContent; | ||
650 | "#, | ||
651 | expect![[r#" | ||
652 | RangeInfo { | ||
653 | range: 11..14, | ||
654 | info: SourceChange { | ||
655 | source_file_edits: [ | ||
656 | SourceFileEdit { | ||
657 | file_id: FileId( | ||
658 | 1, | ||
659 | ), | ||
660 | edit: TextEdit { | ||
661 | indels: [ | ||
662 | Indel { | ||
663 | insert: "quux", | ||
664 | delete: 8..11, | ||
665 | }, | ||
666 | ], | ||
667 | }, | ||
668 | }, | ||
669 | SourceFileEdit { | ||
670 | file_id: FileId( | ||
671 | 3, | ||
672 | ), | ||
673 | edit: TextEdit { | ||
674 | indels: [ | ||
675 | Indel { | ||
676 | insert: "quux", | ||
677 | delete: 11..14, | ||
678 | }, | ||
679 | ], | ||
680 | }, | ||
681 | }, | ||
682 | ], | ||
683 | file_system_edits: [ | ||
684 | MoveFile { | ||
685 | src: FileId( | ||
686 | 2, | ||
687 | ), | ||
688 | anchor: FileId( | ||
689 | 2, | ||
690 | ), | ||
691 | dst: "quux.rs", | ||
692 | }, | ||
693 | ], | ||
694 | is_snippet: false, | ||
695 | }, | ||
696 | } | ||
697 | "#]], | ||
698 | ); | ||
699 | } | ||
700 | |||
701 | #[test] | ||
702 | fn test_rename_mod_in_dir() { | ||
703 | check_expect( | ||
704 | "foo2", | ||
705 | r#" | ||
706 | //- /lib.rs | ||
707 | mod fo<|>o; | ||
708 | //- /foo/mod.rs | ||
709 | // emtpy | ||
710 | "#, | ||
711 | expect![[r#" | ||
712 | RangeInfo { | ||
713 | range: 4..7, | ||
714 | info: SourceChange { | ||
715 | source_file_edits: [ | ||
716 | SourceFileEdit { | ||
717 | file_id: FileId( | ||
718 | 1, | ||
719 | ), | ||
720 | edit: TextEdit { | ||
721 | indels: [ | ||
722 | Indel { | ||
723 | insert: "foo2", | ||
724 | delete: 4..7, | ||
725 | }, | ||
726 | ], | ||
727 | }, | ||
728 | }, | ||
729 | ], | ||
730 | file_system_edits: [ | ||
731 | MoveFile { | ||
732 | src: FileId( | ||
733 | 2, | ||
734 | ), | ||
735 | anchor: FileId( | ||
736 | 2, | ||
737 | ), | ||
738 | dst: "../foo2/mod.rs", | ||
739 | }, | ||
740 | ], | ||
741 | is_snippet: false, | ||
742 | }, | ||
743 | } | ||
744 | "#]], | ||
745 | ); | ||
746 | } | ||
747 | |||
748 | #[test] | ||
749 | fn test_rename_unusually_nested_mod() { | ||
750 | check_expect( | ||
751 | "bar", | ||
752 | r#" | ||
753 | //- /lib.rs | ||
754 | mod outer { mod fo<|>o; } | ||
755 | |||
756 | //- /outer/foo.rs | ||
757 | // emtpy | ||
758 | "#, | ||
759 | expect![[r#" | ||
760 | RangeInfo { | ||
761 | range: 16..19, | ||
762 | info: SourceChange { | ||
763 | source_file_edits: [ | ||
764 | SourceFileEdit { | ||
765 | file_id: FileId( | ||
766 | 1, | ||
767 | ), | ||
768 | edit: TextEdit { | ||
769 | indels: [ | ||
770 | Indel { | ||
771 | insert: "bar", | ||
772 | delete: 16..19, | ||
773 | }, | ||
774 | ], | ||
775 | }, | ||
776 | }, | ||
777 | ], | ||
778 | file_system_edits: [ | ||
779 | MoveFile { | ||
780 | src: FileId( | ||
781 | 2, | ||
782 | ), | ||
783 | anchor: FileId( | ||
784 | 2, | ||
785 | ), | ||
786 | dst: "bar.rs", | ||
787 | }, | ||
788 | ], | ||
789 | is_snippet: false, | ||
790 | }, | ||
791 | } | ||
792 | "#]], | ||
793 | ); | ||
794 | } | ||
795 | |||
796 | #[test] | ||
797 | fn test_module_rename_in_path() { | ||
798 | check( | ||
799 | "baz", | ||
800 | r#" | ||
801 | mod <|>foo { pub fn bar() {} } | ||
802 | |||
803 | fn main() { foo::bar(); } | ||
804 | "#, | ||
805 | r#" | ||
806 | mod baz { pub fn bar() {} } | ||
807 | |||
808 | fn main() { baz::bar(); } | ||
809 | "#, | ||
810 | ); | ||
811 | } | ||
812 | |||
813 | #[test] | ||
814 | fn test_rename_mod_filename_and_path() { | ||
815 | check_expect( | ||
816 | "foo2", | ||
817 | r#" | ||
818 | //- /lib.rs | ||
819 | mod bar; | ||
820 | fn f() { | ||
821 | bar::foo::fun() | ||
822 | } | ||
823 | |||
824 | //- /bar.rs | ||
825 | pub mod foo<|>; | ||
826 | |||
827 | //- /bar/foo.rs | ||
828 | // pub fn fun() {} | ||
829 | "#, | ||
830 | expect![[r#" | ||
831 | RangeInfo { | ||
832 | range: 8..11, | ||
833 | info: SourceChange { | ||
834 | source_file_edits: [ | ||
835 | SourceFileEdit { | ||
836 | file_id: FileId( | ||
837 | 2, | ||
838 | ), | ||
839 | edit: TextEdit { | ||
840 | indels: [ | ||
841 | Indel { | ||
842 | insert: "foo2", | ||
843 | delete: 8..11, | ||
844 | }, | ||
845 | ], | ||
846 | }, | ||
847 | }, | ||
848 | SourceFileEdit { | ||
849 | file_id: FileId( | ||
850 | 1, | ||
851 | ), | ||
852 | edit: TextEdit { | ||
853 | indels: [ | ||
854 | Indel { | ||
855 | insert: "foo2", | ||
856 | delete: 27..30, | ||
857 | }, | ||
858 | ], | ||
859 | }, | ||
860 | }, | ||
861 | ], | ||
862 | file_system_edits: [ | ||
863 | MoveFile { | ||
864 | src: FileId( | ||
865 | 3, | ||
866 | ), | ||
867 | anchor: FileId( | ||
868 | 3, | ||
869 | ), | ||
870 | dst: "foo2.rs", | ||
871 | }, | ||
872 | ], | ||
873 | is_snippet: false, | ||
874 | }, | ||
875 | } | ||
876 | "#]], | ||
877 | ); | ||
878 | } | ||
879 | |||
880 | #[test] | ||
881 | fn test_enum_variant_from_module_1() { | ||
882 | check( | ||
883 | "Baz", | ||
884 | r#" | ||
885 | mod foo { | ||
886 | pub enum Foo { Bar<|> } | ||
887 | } | ||
888 | |||
889 | fn func(f: foo::Foo) { | ||
890 | match f { | ||
891 | foo::Foo::Bar => {} | ||
892 | } | ||
893 | } | ||
894 | "#, | ||
895 | r#" | ||
896 | mod foo { | ||
897 | pub enum Foo { Baz } | ||
898 | } | ||
899 | |||
900 | fn func(f: foo::Foo) { | ||
901 | match f { | ||
902 | foo::Foo::Baz => {} | ||
903 | } | ||
904 | } | ||
905 | "#, | ||
906 | ); | ||
907 | } | ||
908 | |||
909 | #[test] | ||
910 | fn test_enum_variant_from_module_2() { | ||
911 | check( | ||
912 | "baz", | ||
913 | r#" | ||
914 | mod foo { | ||
915 | pub struct Foo { pub bar<|>: uint } | ||
916 | } | ||
917 | |||
918 | fn foo(f: foo::Foo) { | ||
919 | let _ = f.bar; | ||
920 | } | ||
921 | "#, | ||
922 | r#" | ||
923 | mod foo { | ||
924 | pub struct Foo { pub baz: uint } | ||
925 | } | ||
926 | |||
927 | fn foo(f: foo::Foo) { | ||
928 | let _ = f.baz; | ||
929 | } | ||
930 | "#, | ||
931 | ); | ||
932 | } | ||
933 | |||
934 | #[test] | ||
935 | fn test_parameter_to_self() { | ||
936 | check( | ||
937 | "self", | ||
938 | r#" | ||
939 | struct Foo { i: i32 } | ||
940 | |||
941 | impl Foo { | ||
942 | fn f(foo<|>: &mut Foo) -> i32 { | ||
943 | foo.i | ||
944 | } | ||
945 | } | ||
946 | "#, | ||
947 | r#" | ||
948 | struct Foo { i: i32 } | ||
949 | |||
950 | impl Foo { | ||
951 | fn f(&mut self) -> i32 { | ||
952 | self.i | ||
953 | } | ||
954 | } | ||
955 | "#, | ||
956 | ); | ||
957 | } | ||
958 | |||
959 | #[test] | ||
960 | fn test_self_to_parameter() { | ||
961 | check( | ||
962 | "foo", | ||
963 | r#" | ||
964 | struct Foo { i: i32 } | ||
965 | |||
966 | impl Foo { | ||
967 | fn f(&mut <|>self) -> i32 { | ||
968 | self.i | ||
969 | } | ||
970 | } | ||
971 | "#, | ||
972 | r#" | ||
973 | struct Foo { i: i32 } | ||
974 | |||
975 | impl Foo { | ||
976 | fn f(foo: &mut Foo) -> i32 { | ||
977 | foo.i | ||
978 | } | ||
979 | } | ||
980 | "#, | ||
981 | ); | ||
982 | } | ||
983 | |||
984 | #[test] | ||
985 | fn test_self_in_path_to_parameter() { | ||
986 | check( | ||
987 | "foo", | ||
988 | r#" | ||
989 | struct Foo { i: i32 } | ||
990 | |||
991 | impl Foo { | ||
992 | fn f(&self) -> i32 { | ||
993 | let self_var = 1; | ||
994 | self<|>.i | ||
995 | } | ||
996 | } | ||
997 | "#, | ||
998 | r#" | ||
999 | struct Foo { i: i32 } | ||
1000 | |||
1001 | impl Foo { | ||
1002 | fn f(foo: &Foo) -> i32 { | ||
1003 | let self_var = 1; | ||
1004 | foo.i | ||
1005 | } | ||
1006 | } | ||
1007 | "#, | ||
1008 | ); | ||
1009 | } | ||
1010 | } | ||