diff options
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r-- | crates/ide/src/references/rename.rs | 197 |
1 files changed, 118 insertions, 79 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index ebb1ce7dd..b04214291 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -4,9 +4,9 @@ use std::fmt::{self, Display}; | |||
4 | use either::Either; | 4 | use either::Either; |
5 | use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics}; | 5 | use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics}; |
6 | use ide_db::{ | 6 | use ide_db::{ |
7 | base_db::{AnchoredPathBuf, FileId, FileRange}, | 7 | base_db::{AnchoredPathBuf, FileId}, |
8 | defs::{Definition, NameClass, NameRefClass}, | 8 | defs::{Definition, NameClass, NameRefClass}, |
9 | search::FileReference, | 9 | search::{FileReference, NameLike}, |
10 | RootDatabase, | 10 | RootDatabase, |
11 | }; | 11 | }; |
12 | use stdx::never; | 12 | use stdx::never; |
@@ -17,10 +17,7 @@ use syntax::{ | |||
17 | use test_utils::mark; | 17 | use test_utils::mark; |
18 | use text_edit::TextEdit; | 18 | use text_edit::TextEdit; |
19 | 19 | ||
20 | use crate::{ | 20 | use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; |
21 | display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, SourceChange, | ||
22 | TextRange, | ||
23 | }; | ||
24 | 21 | ||
25 | type RenameResult<T> = Result<T, RenameError>; | 22 | type RenameResult<T> = Result<T, RenameError>; |
26 | #[derive(Debug)] | 23 | #[derive(Debug)] |
@@ -41,6 +38,8 @@ macro_rules! bail { | |||
41 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} | 38 | ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))} |
42 | } | 39 | } |
43 | 40 | ||
41 | /// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is | ||
42 | /// being targeted for a rename. | ||
44 | pub(crate) fn prepare_rename( | 43 | pub(crate) fn prepare_rename( |
45 | db: &RootDatabase, | 44 | db: &RootDatabase, |
46 | position: FilePosition, | 45 | position: FilePosition, |
@@ -123,12 +122,6 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | |||
123 | } | 122 | } |
124 | } | 123 | } |
125 | 124 | ||
126 | enum NameLike { | ||
127 | Name(ast::Name), | ||
128 | NameRef(ast::NameRef), | ||
129 | Lifetime(ast::Lifetime), | ||
130 | } | ||
131 | |||
132 | fn find_name_like( | 125 | fn find_name_like( |
133 | sema: &Semantics<RootDatabase>, | 126 | sema: &Semantics<RootDatabase>, |
134 | syntax: &SyntaxNode, | 127 | syntax: &SyntaxNode, |
@@ -171,72 +164,97 @@ fn find_definition( | |||
171 | } | 164 | } |
172 | 165 | ||
173 | fn source_edit_from_references( | 166 | fn source_edit_from_references( |
174 | sema: &Semantics<RootDatabase>, | 167 | _sema: &Semantics<RootDatabase>, |
175 | file_id: FileId, | 168 | file_id: FileId, |
176 | references: &[FileReference], | 169 | references: &[FileReference], |
170 | def: Definition, | ||
177 | new_name: &str, | 171 | new_name: &str, |
178 | ) -> (FileId, TextEdit) { | 172 | ) -> (FileId, TextEdit) { |
179 | let mut edit = TextEdit::builder(); | 173 | let mut edit = TextEdit::builder(); |
180 | for reference in references { | 174 | for reference in references { |
181 | let mut replacement_text = String::new(); | 175 | let (range, replacement) = match &reference.name { |
182 | let range = match reference.kind { | 176 | NameLike::Name(_) => (None, format!("{}", new_name)), |
183 | ReferenceKind::FieldShorthandForField => { | 177 | NameLike::NameRef(name_ref) => source_edit_from_name_ref(name_ref, new_name, def), |
184 | mark::hit!(test_rename_struct_field_for_shorthand); | 178 | NameLike::Lifetime(_) => (None, format!("{}", new_name)), |
185 | replacement_text.push_str(new_name); | ||
186 | replacement_text.push_str(": "); | ||
187 | TextRange::new(reference.range.start(), reference.range.start()) | ||
188 | } | ||
189 | ReferenceKind::FieldShorthandForLocal => { | ||
190 | mark::hit!(test_rename_local_for_field_shorthand); | ||
191 | replacement_text.push_str(": "); | ||
192 | replacement_text.push_str(new_name); | ||
193 | TextRange::new(reference.range.end(), reference.range.end()) | ||
194 | } | ||
195 | ReferenceKind::RecordFieldExprOrPat => { | ||
196 | mark::hit!(test_rename_field_expr_pat); | ||
197 | replacement_text.push_str(new_name); | ||
198 | edit_text_range_for_record_field_expr_or_pat( | ||
199 | sema, | ||
200 | FileRange { file_id, range: reference.range }, | ||
201 | new_name, | ||
202 | ) | ||
203 | } | ||
204 | _ => { | ||
205 | replacement_text.push_str(new_name); | ||
206 | reference.range | ||
207 | } | ||
208 | }; | 179 | }; |
209 | edit.replace(range, replacement_text); | 180 | // FIXME: Some(range) will be incorrect when we are inside macros |
181 | edit.replace(range.unwrap_or(reference.range), replacement); | ||
210 | } | 182 | } |
211 | (file_id, edit.finish()) | 183 | (file_id, edit.finish()) |
212 | } | 184 | } |
213 | 185 | ||
214 | fn edit_text_range_for_record_field_expr_or_pat( | 186 | fn source_edit_from_name_ref( |
215 | sema: &Semantics<RootDatabase>, | 187 | name_ref: &ast::NameRef, |
216 | file_range: FileRange, | ||
217 | new_name: &str, | 188 | new_name: &str, |
218 | ) -> TextRange { | 189 | def: Definition, |
219 | let source_file = sema.parse(file_range.file_id); | 190 | ) -> (Option<TextRange>, String) { |
220 | let file_syntax = source_file.syntax(); | 191 | if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) { |
221 | let original_range = file_range.range; | 192 | let rcf_name_ref = record_field.name_ref(); |
222 | 193 | let rcf_expr = record_field.expr(); | |
223 | syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range) | 194 | match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) { |
224 | .and_then(|field_expr| match field_expr.expr().and_then(|e| e.name_ref()) { | 195 | // field: init-expr, check if we can use a field init shorthand |
225 | Some(name) if &name.to_string() == new_name => Some(field_expr.syntax().text_range()), | 196 | (Some(field_name), Some(init)) => { |
226 | _ => None, | 197 | if field_name == *name_ref { |
227 | }) | 198 | if init.text() == new_name { |
228 | .or_else(|| { | 199 | mark::hit!(test_rename_field_put_init_shorthand); |
229 | syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range) | 200 | // same names, we can use a shorthand here instead |
230 | .and_then(|field_pat| match field_pat.pat() { | 201 | // we do not want to erase attributes hence this range start |
231 | Some(ast::Pat::IdentPat(pat)) | 202 | let s = field_name.syntax().text_range().start(); |
232 | if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) => | 203 | let e = record_field.syntax().text_range().end(); |
233 | { | 204 | return (Some(TextRange::new(s, e)), format!("{}", new_name)); |
234 | Some(field_pat.syntax().text_range()) | ||
235 | } | 205 | } |
236 | _ => None, | 206 | } else if init == *name_ref { |
237 | }) | 207 | if field_name.text() == new_name { |
238 | }) | 208 | mark::hit!(test_rename_local_put_init_shorthand); |
239 | .unwrap_or(original_range) | 209 | // same names, we can use a shorthand here instead |
210 | // we do not want to erase attributes hence this range start | ||
211 | let s = field_name.syntax().text_range().start(); | ||
212 | let e = record_field.syntax().text_range().end(); | ||
213 | return (Some(TextRange::new(s, e)), format!("{}", new_name)); | ||
214 | } | ||
215 | } | ||
216 | } | ||
217 | // init shorthand | ||
218 | (None, Some(_)) => { | ||
219 | // FIXME: instead of splitting the shorthand, recursively trigger a rename of the | ||
220 | // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547 | ||
221 | match def { | ||
222 | Definition::Field(_) => { | ||
223 | mark::hit!(test_rename_field_in_field_shorthand); | ||
224 | let s = name_ref.syntax().text_range().start(); | ||
225 | return (Some(TextRange::empty(s)), format!("{}: ", new_name)); | ||
226 | } | ||
227 | Definition::Local(_) => { | ||
228 | mark::hit!(test_rename_local_in_field_shorthand); | ||
229 | let s = name_ref.syntax().text_range().end(); | ||
230 | return (Some(TextRange::empty(s)), format!(": {}", new_name)); | ||
231 | } | ||
232 | _ => {} | ||
233 | } | ||
234 | } | ||
235 | _ => {} | ||
236 | } | ||
237 | } | ||
238 | if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) { | ||
239 | let rcf_name_ref = record_field.name_ref(); | ||
240 | let rcf_pat = record_field.pat(); | ||
241 | match (rcf_name_ref, rcf_pat) { | ||
242 | // field: rename | ||
243 | (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => { | ||
244 | // field name is being renamed | ||
245 | if pat.name().map_or(false, |it| it.text() == new_name) { | ||
246 | mark::hit!(test_rename_field_put_init_shorthand_pat); | ||
247 | // same names, we can use a shorthand here instead | ||
248 | // we do not want to erase attributes hence this range start | ||
249 | let s = field_name.syntax().text_range().start(); | ||
250 | let e = record_field.syntax().text_range().end(); | ||
251 | return (Some(TextRange::new(s, e)), format!("{}", new_name)); | ||
252 | } | ||
253 | } | ||
254 | _ => {} | ||
255 | } | ||
256 | } | ||
257 | (None, format!("{}", new_name)) | ||
240 | } | 258 | } |
241 | 259 | ||
242 | fn rename_mod( | 260 | fn rename_mod( |
@@ -277,7 +295,7 @@ fn rename_mod( | |||
277 | let def = Definition::ModuleDef(ModuleDef::Module(module)); | 295 | let def = Definition::ModuleDef(ModuleDef::Module(module)); |
278 | let usages = def.usages(sema).all(); | 296 | let usages = def.usages(sema).all(); |
279 | let ref_edits = usages.iter().map(|(&file_id, references)| { | 297 | let ref_edits = usages.iter().map(|(&file_id, references)| { |
280 | source_edit_from_references(sema, file_id, references, new_name) | 298 | source_edit_from_references(sema, file_id, references, def, new_name) |
281 | }); | 299 | }); |
282 | source_change.extend(ref_edits); | 300 | source_change.extend(ref_edits); |
283 | 301 | ||
@@ -346,7 +364,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe | |||
346 | let usages = def.usages(sema).all(); | 364 | let usages = def.usages(sema).all(); |
347 | let mut source_change = SourceChange::default(); | 365 | let mut source_change = SourceChange::default(); |
348 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 366 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
349 | source_edit_from_references(sema, file_id, references, "self") | 367 | source_edit_from_references(sema, file_id, references, def, "self") |
350 | })); | 368 | })); |
351 | source_change.insert_source_edit( | 369 | source_change.insert_source_edit( |
352 | file_id.original_file(sema.db), | 370 | file_id.original_file(sema.db), |
@@ -403,7 +421,7 @@ fn rename_self_to_param( | |||
403 | let mut source_change = SourceChange::default(); | 421 | let mut source_change = SourceChange::default(); |
404 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); | 422 | source_change.insert_source_edit(file_id.original_file(sema.db), edit); |
405 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 423 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
406 | source_edit_from_references(sema, file_id, &references, new_name) | 424 | source_edit_from_references(sema, file_id, &references, def, new_name) |
407 | })); | 425 | })); |
408 | Ok(source_change) | 426 | Ok(source_change) |
409 | } | 427 | } |
@@ -457,7 +475,7 @@ fn rename_reference( | |||
457 | } | 475 | } |
458 | let mut source_change = SourceChange::default(); | 476 | let mut source_change = SourceChange::default(); |
459 | source_change.extend(usages.iter().map(|(&file_id, references)| { | 477 | source_change.extend(usages.iter().map(|(&file_id, references)| { |
460 | source_edit_from_references(sema, file_id, &references, new_name) | 478 | source_edit_from_references(sema, file_id, &references, def, new_name) |
461 | })); | 479 | })); |
462 | 480 | ||
463 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; | 481 | let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; |
@@ -545,10 +563,8 @@ mod tests { | |||
545 | 563 | ||
546 | fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { | 564 | fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { |
547 | let (analysis, position) = fixture::position(ra_fixture); | 565 | let (analysis, position) = fixture::position(ra_fixture); |
548 | let source_change = analysis | 566 | let source_change = |
549 | .rename(position, new_name) | 567 | analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); |
550 | .unwrap() | ||
551 | .expect("Expect returned RangeInfo to be Some, but was None"); | ||
552 | expect.assert_debug_eq(&source_change) | 568 | expect.assert_debug_eq(&source_change) |
553 | } | 569 | } |
554 | 570 | ||
@@ -792,8 +808,8 @@ impl Foo { | |||
792 | } | 808 | } |
793 | 809 | ||
794 | #[test] | 810 | #[test] |
795 | fn test_rename_struct_field_for_shorthand() { | 811 | fn test_rename_field_in_field_shorthand() { |
796 | mark::check!(test_rename_struct_field_for_shorthand); | 812 | mark::check!(test_rename_field_in_field_shorthand); |
797 | check( | 813 | check( |
798 | "j", | 814 | "j", |
799 | r#" | 815 | r#" |
@@ -818,8 +834,8 @@ impl Foo { | |||
818 | } | 834 | } |
819 | 835 | ||
820 | #[test] | 836 | #[test] |
821 | fn test_rename_local_for_field_shorthand() { | 837 | fn test_rename_local_in_field_shorthand() { |
822 | mark::check!(test_rename_local_for_field_shorthand); | 838 | mark::check!(test_rename_local_in_field_shorthand); |
823 | check( | 839 | check( |
824 | "j", | 840 | "j", |
825 | r#" | 841 | r#" |
@@ -1417,8 +1433,8 @@ impl Foo { | |||
1417 | } | 1433 | } |
1418 | 1434 | ||
1419 | #[test] | 1435 | #[test] |
1420 | fn test_initializer_use_field_init_shorthand() { | 1436 | fn test_rename_field_put_init_shorthand() { |
1421 | mark::check!(test_rename_field_expr_pat); | 1437 | mark::check!(test_rename_field_put_init_shorthand); |
1422 | check( | 1438 | check( |
1423 | "bar", | 1439 | "bar", |
1424 | r#" | 1440 | r#" |
@@ -1439,7 +1455,30 @@ fn foo(bar: i32) -> Foo { | |||
1439 | } | 1455 | } |
1440 | 1456 | ||
1441 | #[test] | 1457 | #[test] |
1458 | fn test_rename_local_put_init_shorthand() { | ||
1459 | mark::check!(test_rename_local_put_init_shorthand); | ||
1460 | check( | ||
1461 | "i", | ||
1462 | r#" | ||
1463 | struct Foo { i: i32 } | ||
1464 | |||
1465 | fn foo(bar$0: i32) -> Foo { | ||
1466 | Foo { i: bar } | ||
1467 | } | ||
1468 | "#, | ||
1469 | r#" | ||
1470 | struct Foo { i: i32 } | ||
1471 | |||
1472 | fn foo(i: i32) -> Foo { | ||
1473 | Foo { i } | ||
1474 | } | ||
1475 | "#, | ||
1476 | ); | ||
1477 | } | ||
1478 | |||
1479 | #[test] | ||
1442 | fn test_struct_field_destructure_into_shorthand() { | 1480 | fn test_struct_field_destructure_into_shorthand() { |
1481 | mark::check!(test_rename_field_put_init_shorthand_pat); | ||
1443 | check( | 1482 | check( |
1444 | "baz", | 1483 | "baz", |
1445 | r#" | 1484 | r#" |