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