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.rs240
1 files changed, 219 insertions, 21 deletions
diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs
index 2cbb82c1a..28c6349b1 100644
--- a/crates/ra_ide/src/references/rename.rs
+++ b/crates/ra_ide/src/references/rename.rs
@@ -4,14 +4,16 @@ 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;
10use test_utils::tested_by; 11use std::convert::TryInto;
12use test_utils::mark;
11 13
12use crate::{ 14use crate::{
13 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, 15 references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind,
14 SourceChange, SourceFileEdit, TextRange, 16 SourceChange, SourceFileEdit, TextRange, TextSize,
15}; 17};
16 18
17pub(crate) fn rename( 19pub(crate) fn rename(
@@ -21,17 +23,21 @@ pub(crate) fn rename(
21) -> Option<RangeInfo<SourceChange>> { 23) -> Option<RangeInfo<SourceChange>> {
22 match lex_single_valid_syntax_kind(new_name)? { 24 match lex_single_valid_syntax_kind(new_name)? {
23 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (), 25 SyntaxKind::IDENT | SyntaxKind::UNDERSCORE => (),
26 SyntaxKind::SELF_KW => return rename_to_self(db, position),
24 _ => return None, 27 _ => return None,
25 } 28 }
26 29
27 let sema = Semantics::new(db); 30 let sema = Semantics::new(db);
28 let source_file = sema.parse(position.file_id); 31 let source_file = sema.parse(position.file_id);
29 if let Some((ast_name, ast_module)) = 32 let syntax = source_file.syntax();
30 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) {
31 {
32 let range = ast_name.syntax().text_range(); 34 let range = ast_name.syntax().text_range();
33 rename_mod(&sema, &ast_name, &ast_module, position, new_name) 35 rename_mod(&sema, &ast_name, &ast_module, position, new_name)
34 .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)
35 } else { 41 } else {
36 rename_reference(sema.db, position, new_name) 42 rename_reference(sema.db, position, new_name)
37 } 43 }
@@ -51,13 +57,13 @@ fn source_edit_from_reference(reference: Reference, new_name: &str) -> SourceFil
51 let file_id = reference.file_range.file_id; 57 let file_id = reference.file_range.file_id;
52 let range = match reference.kind { 58 let range = match reference.kind {
53 ReferenceKind::FieldShorthandForField => { 59 ReferenceKind::FieldShorthandForField => {
54 tested_by!(test_rename_struct_field_for_shorthand); 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 => {
60 tested_by!(test_rename_local_for_field_shorthand); 66 mark::hit!(test_rename_local_for_field_shorthand);
61 replacement_text.push_str(": "); 67 replacement_text.push_str(": ");
62 replacement_text.push_str(new_name); 68 replacement_text.push_str(new_name);
63 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())
@@ -122,7 +128,113 @@ fn rename_mod(
122 source_file_edits.extend(ref_edits); 128 source_file_edits.extend(ref_edits);
123 } 129 }
124 130
125 Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits)) 131 Some(SourceChange::from_edits(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(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(edits)))
126} 238}
127 239
128fn rename_reference( 240fn rename_reference(
@@ -141,14 +253,14 @@ fn rename_reference(
141 return None; 253 return None;
142 } 254 }
143 255
144 Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit))) 256 Some(RangeInfo::new(range, SourceChange::source_file_edits(edit)))
145} 257}
146 258
147#[cfg(test)] 259#[cfg(test)]
148mod tests { 260mod tests {
149 use insta::assert_debug_snapshot; 261 use insta::assert_debug_snapshot;
150 use ra_text_edit::TextEditBuilder; 262 use ra_text_edit::TextEditBuilder;
151 use test_utils::{assert_eq_text, covers}; 263 use test_utils::{assert_eq_text, mark};
152 264
153 use crate::{ 265 use crate::{
154 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,
@@ -380,7 +492,7 @@ mod tests {
380 492
381 #[test] 493 #[test]
382 fn test_rename_struct_field_for_shorthand() { 494 fn test_rename_struct_field_for_shorthand() {
383 covers!(test_rename_struct_field_for_shorthand); 495 mark::check!(test_rename_struct_field_for_shorthand);
384 test_rename( 496 test_rename(
385 r#" 497 r#"
386 struct Foo { 498 struct Foo {
@@ -410,7 +522,7 @@ mod tests {
410 522
411 #[test] 523 #[test]
412 fn test_rename_local_for_field_shorthand() { 524 fn test_rename_local_for_field_shorthand() {
413 covers!(test_rename_local_for_field_shorthand); 525 mark::check!(test_rename_local_for_field_shorthand);
414 test_rename( 526 test_rename(
415 r#" 527 r#"
416 struct Foo { 528 struct Foo {
@@ -530,7 +642,6 @@ mod tests {
530 RangeInfo { 642 RangeInfo {
531 range: 4..7, 643 range: 4..7,
532 info: SourceChange { 644 info: SourceChange {
533 label: "Rename",
534 source_file_edits: [ 645 source_file_edits: [
535 SourceFileEdit { 646 SourceFileEdit {
536 file_id: FileId( 647 file_id: FileId(
@@ -557,7 +668,7 @@ mod tests {
557 dst_path: "bar/foo2.rs", 668 dst_path: "bar/foo2.rs",
558 }, 669 },
559 ], 670 ],
560 cursor_position: None, 671 is_snippet: false,
561 }, 672 },
562 }, 673 },
563 ) 674 )
@@ -582,7 +693,6 @@ mod tests {
582 RangeInfo { 693 RangeInfo {
583 range: 4..7, 694 range: 4..7,
584 info: SourceChange { 695 info: SourceChange {
585 label: "Rename",
586 source_file_edits: [ 696 source_file_edits: [
587 SourceFileEdit { 697 SourceFileEdit {
588 file_id: FileId( 698 file_id: FileId(
@@ -609,7 +719,7 @@ mod tests {
609 dst_path: "foo2/mod.rs", 719 dst_path: "foo2/mod.rs",
610 }, 720 },
611 ], 721 ],
612 cursor_position: None, 722 is_snippet: false,
613 }, 723 },
614 }, 724 },
615 ) 725 )
@@ -665,7 +775,6 @@ mod tests {
665 RangeInfo { 775 RangeInfo {
666 range: 8..11, 776 range: 8..11,
667 info: SourceChange { 777 info: SourceChange {
668 label: "Rename",
669 source_file_edits: [ 778 source_file_edits: [
670 SourceFileEdit { 779 SourceFileEdit {
671 file_id: FileId( 780 file_id: FileId(
@@ -705,7 +814,7 @@ mod tests {
705 dst_path: "bar/foo2.rs", 814 dst_path: "bar/foo2.rs",
706 }, 815 },
707 ], 816 ],
708 cursor_position: None, 817 is_snippet: false,
709 }, 818 },
710 }, 819 },
711 ) 820 )
@@ -774,6 +883,95 @@ mod tests {
774 ); 883 );
775 } 884 }
776 885
886 #[test]
887 fn test_parameter_to_self() {
888 test_rename(
889 r#"
890 struct Foo {
891 i: i32,
892 }
893
894 impl Foo {
895 fn f(foo<|>: &mut Foo) -> i32 {
896 foo.i
897 }
898 }
899 "#,
900 "self",
901 r#"
902 struct Foo {
903 i: i32,
904 }
905
906 impl Foo {
907 fn f(&mut self) -> i32 {
908 self.i
909 }
910 }
911 "#,
912 );
913 }
914
915 #[test]
916 fn test_self_to_parameter() {
917 test_rename(
918 r#"
919 struct Foo {
920 i: i32,
921 }
922
923 impl Foo {
924 fn f(&mut <|>self) -> i32 {
925 self.i
926 }
927 }
928 "#,
929 "foo",
930 r#"
931 struct Foo {
932 i: i32,
933 }
934
935 impl Foo {
936 fn f(foo: &mut Foo) -> i32 {
937 foo.i
938 }
939 }
940 "#,
941 );
942 }
943
944 #[test]
945 fn test_self_in_path_to_parameter() {
946 test_rename(
947 r#"
948 struct Foo {
949 i: i32,
950 }
951
952 impl Foo {
953 fn f(&self) -> i32 {
954 let self_var = 1;
955 self<|>.i
956 }
957 }
958 "#,
959 "foo",
960 r#"
961 struct Foo {
962 i: i32,
963 }
964
965 impl Foo {
966 fn f(foo: &Foo) -> i32 {
967 let self_var = 1;
968 foo.i
969 }
970 }
971 "#,
972 );
973 }
974
777 fn test_rename(text: &str, new_name: &str, expected: &str) { 975 fn test_rename(text: &str, new_name: &str, expected: &str) {
778 let (analysis, position) = single_file_with_position(text); 976 let (analysis, position) = single_file_with_position(text);
779 let source_change = analysis.rename(position, new_name).unwrap(); 977 let source_change = analysis.rename(position, new_name).unwrap();
@@ -782,8 +980,8 @@ mod tests {
782 if let Some(change) = source_change { 980 if let Some(change) = source_change {
783 for edit in change.info.source_file_edits { 981 for edit in change.info.source_file_edits {
784 file_id = Some(edit.file_id); 982 file_id = Some(edit.file_id);
785 for indel in edit.edit.as_indels() { 983 for indel in edit.edit.into_iter() {
786 text_edit_builder.replace(indel.delete, indel.insert.clone()); 984 text_edit_builder.replace(indel.delete, indel.insert);
787 } 985 }
788 } 986 }
789 } 987 }