aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/references/rename.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src/references/rename.rs')
-rw-r--r--crates/ra_ide/src/references/rename.rs332
1 files changed, 299 insertions, 33 deletions
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index fd17bc9f2..fd2163dad 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -4,17 +4,18 @@ use hir::{ModuleSource, Semantics};
4use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt}; 4use ra_db::{RelativePath, RelativePathBuf, SourceDatabaseExt};
5use ra_ide_db::RootDatabase; 5use ra_ide_db::RootDatabase;
6use ra_syntax::{ 6use ra_syntax::{
7 algo::find_node_at_offset, ast, lex_single_valid_syntax_kind, AstNode, SyntaxKind, SyntaxNode, 7 algo::find_node_at_offset, ast, ast::TypeAscriptionOwner, lex_single_valid_syntax_kind,
8 AstNode, SyntaxKind, SyntaxNode, SyntaxToken,
8}; 9};
9use ra_text_edit::TextEdit; 10use ra_text_edit::TextEdit;
11use std::convert::TryInto;
12use test_utils::mark;
10 13
11use crate::{ 14use crate::{
12 FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, SourceChange, 15 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
13 SourceFileEdit, TextRange, 16 SourceChange, SourceFileEdit, TextRange, TextSize,
14}; 17};
15 18
16use super::find_all_refs;
17
18pub(crate) fn rename( 19pub(crate) fn rename(
19 db: &RootDatabase, 20 db: &RootDatabase,
20 position: FilePosition, 21 position: FilePosition,
@@ -22,17 +23,21 @@ pub(crate) fn rename(
22) -> Option<RangeInfo<SourceChange>> { 23) -> Option<RangeInfo<SourceChange>> {
23 match lex_single_valid_syntax_kind(new_name)? { 24 match lex_single_valid_syntax_kind(new_name)? {
24 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 25 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (),
26 SyntaxKind::SELF_KW => return rename_to_self(db, position),
25 _ => return None, 27 _ => return None,
26 } 28 }
27 29
28 let sema = Semantics::new(db); 30 let sema = Semantics::new(db);
29 let source_file = sema.parse(position.file_id); 31 let source_file = sema.parse(position.file_id);
30 if let Some((ast_name, ast_module)) = 32 let syntax = source_file.syntax();
31 find_name_and_module_at_offset(source_file.syntax(), position) 33 if let Some((ast_name, ast_module)) = find_name_and_module_at_offset(syntax, position) {
32 {
33 let range = ast_name.syntax().text_range(); 34 let range = ast_name.syntax().text_range();
34 rename_mod(&sema, &ast_name, &ast_module, position, new_name) 35 rename_mod(&sema, &ast_name, &ast_module, position, new_name)
35 .map(|info| RangeInfo::new(range, info)) 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)
36 } else { 41 } else {
37 rename_reference(sema.db, position, new_name) 42 rename_reference(sema.db, position, new_name)
38 } 43 }
@@ -52,11 +57,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
52 let file_id = reference.file_range.file_id; 57 let file_id = reference.file_range.file_id;
53 let range = match reference.kind { 58 let range = match reference.kind {
54 ReferenceKind::FieldShorthandForField => { 59 ReferenceKind::FieldShorthandForField => {
60 mark::hit!(test_rename_struct_field_for_shorthand);
55 replacement_text.push_str(new_name); 61 replacement_text.push_str(new_name);
56 replacement_text.push_str(": "); 62 replacement_text.push_str(": ");
57 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) 63 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start())
58 } 64 }
59 ReferenceKind::FieldShorthandForLocal => { 65 ReferenceKind::FieldShorthandForLocal => {
66 mark::hit!(test_rename_local_for_field_shorthand);
60 replacement_text.push_str(": "); 67 replacement_text.push_str(": ");
61 replacement_text.push_str(new_name); 68 replacement_text.push_str(new_name);
62 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) 69 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end())
@@ -121,7 +128,113 @@ fn rename_mod(
121 source_file_edits.extend(ref_edits); 128 source_file_edits.extend(ref_edits);
122 } 129 }
123 130
124 Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) 131 Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits))
132}
133
134fn 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
177fn 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
201fn 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)))
125} 238}
126 239
127fn rename_reference( 240fn rename_reference(
@@ -140,14 +253,14 @@ fn rename_reference(
140 return None; 253 return None;
141 } 254 }
142 255
143 Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) 256 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit)))
144} 257}
145 258
146#[cfg(test)] 259#[cfg(test)]
147mod tests { 260mod tests {
148 use insta::assert_debug_snapshot; 261 use insta::assert_debug_snapshot;
149 use ra_text_edit::TextEditBuilder; 262 use ra_text_edit::TextEditBuilder;
150 use test_utils::assert_eq_text; 263 use test_utils::{assert_eq_text, mark};
151 264
152 use crate::{ 265 use crate::{
153 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, 266 mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId,
@@ -379,6 +492,7 @@ mod tests {
379 492
380 #[test] 493 #[test]
381 fn test_rename_struct_field_for_shorthand() { 494 fn test_rename_struct_field_for_shorthand() {
495 mark::check!(test_rename_struct_field_for_shorthand);
382 test_rename( 496 test_rename(
383 r#" 497 r#"
384 struct Foo { 498 struct Foo {
@@ -408,6 +522,7 @@ mod tests {
408 522
409 #[test] 523 #[test]
410 fn test_rename_local_for_field_shorthand() { 524 fn test_rename_local_for_field_shorthand() {
525 mark::check!(test_rename_local_for_field_shorthand);
411 test_rename( 526 test_rename(
412 r#" 527 r#"
413 struct Foo { 528 struct Foo {
@@ -527,17 +642,17 @@ mod tests {
527 RangeInfo { 642 RangeInfo {
528 range: 4..7, 643 range: 4..7,
529 info: SourceChange { 644 info: SourceChange {
530 label: "rename", 645 label: "Rename",
531 source_file_edits: [ 646 source_file_edits: [
532 SourceFileEdit { 647 SourceFileEdit {
533 file_id: FileId( 648 file_id: FileId(
534 2, 649 2,
535 ), 650 ),
536 edit: TextEdit { 651 edit: TextEdit {
537 atoms: [ 652 indels: [
538 AtomTextEdit { 653 Indel {
539 delete: 4..7,
540 insert: "foo2", 654 insert: "foo2",
655 delete: 4..7,
541 }, 656 },
542 ], 657 ],
543 }, 658 },
@@ -554,7 +669,7 @@ mod tests {
554 dst_path: "bar/foo2.rs", 669 dst_path: "bar/foo2.rs",
555 }, 670 },
556 ], 671 ],
557 cursor_position: None, 672 is_snippet: false,
558 }, 673 },
559 }, 674 },
560 ) 675 )
@@ -579,17 +694,17 @@ mod tests {
579 RangeInfo { 694 RangeInfo {
580 range: 4..7, 695 range: 4..7,
581 info: SourceChange { 696 info: SourceChange {
582 label: "rename", 697 label: "Rename",
583 source_file_edits: [ 698 source_file_edits: [
584 SourceFileEdit { 699 SourceFileEdit {
585 file_id: FileId( 700 file_id: FileId(
586 1, 701 1,
587 ), 702 ),
588 edit: TextEdit { 703 edit: TextEdit {
589 atoms: [ 704 indels: [
590 AtomTextEdit { 705 Indel {
591 delete: 4..7,
592 insert: "foo2", 706 insert: "foo2",
707 delete: 4..7,
593 }, 708 },
594 ], 709 ],
595 }, 710 },
@@ -606,7 +721,7 @@ mod tests {
606 dst_path: "foo2/mod.rs", 721 dst_path: "foo2/mod.rs",
607 }, 722 },
608 ], 723 ],
609 cursor_position: None, 724 is_snippet: false,
610 }, 725 },
611 }, 726 },
612 ) 727 )
@@ -662,17 +777,17 @@ mod tests {
662 RangeInfo { 777 RangeInfo {
663 range: 8..11, 778 range: 8..11,
664 info: SourceChange { 779 info: SourceChange {
665 label: "rename", 780 label: "Rename",
666 source_file_edits: [ 781 source_file_edits: [
667 SourceFileEdit { 782 SourceFileEdit {
668 file_id: FileId( 783 file_id: FileId(
669 2, 784 2,
670 ), 785 ),
671 edit: TextEdit { 786 edit: TextEdit {
672 atoms: [ 787 indels: [
673 AtomTextEdit { 788 Indel {
674 delete: 8..11,
675 insert: "foo2", 789 insert: "foo2",
790 delete: 8..11,
676 }, 791 },
677 ], 792 ],
678 }, 793 },
@@ -682,10 +797,10 @@ mod tests {
682 1, 797 1,
683 ), 798 ),
684 edit: TextEdit { 799 edit: TextEdit {
685 atoms: [ 800 indels: [
686 AtomTextEdit { 801 Indel {
687 delete: 27..30,
688 insert: "foo2", 802 insert: "foo2",
803 delete: 27..30,
689 }, 804 },
690 ], 805 ],
691 }, 806 },
@@ -702,13 +817,164 @@ mod tests {
702 dst_path: "bar/foo2.rs", 817 dst_path: "bar/foo2.rs",
703 }, 818 },
704 ], 819 ],
705 cursor_position: None, 820 is_snippet: false,
706 }, 821 },
707 }, 822 },
708 ) 823 )
709 "###); 824 "###);
710 } 825 }
711 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
712 fn test_rename(text: &str, new_name: &str, expected: &str) { 978 fn test_rename(text: &str, new_name: &str, expected: &str) {
713 let (analysis, position) = single_file_with_position(text); 979 let (analysis, position) = single_file_with_position(text);
714 let source_change = analysis.rename(position, new_name).unwrap(); 980 let source_change = analysis.rename(position, new_name).unwrap();
@@ -717,13 +983,13 @@ mod tests {
717 if let Some(change) = source_change { 983 if let Some(change) = source_change {
718 for edit in change.info.source_file_edits { 984 for edit in change.info.source_file_edits {
719 file_id = Some(edit.file_id); 985 file_id = Some(edit.file_id);
720 for atom in edit.edit.as_atoms() { 986 for indel in edit.edit.into_iter() {
721 text_edit_builder.replace(atom.delete, atom.insert.clone()); 987 text_edit_builder.replace(indel.delete, indel.insert);
722 } 988 }
723 } 989 }
724 } 990 }
725 let result = 991 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
726 text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); 992 text_edit_builder.finish().apply(&mut result);
727 assert_eq_text!(expected, &*result); 993 assert_eq_text!(expected, &*result);
728 } 994 }
729} 995}