aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r--crates/ide/src/references/rename.rs197
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};
4use either::Either; 4use either::Either;
5use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics}; 5use hir::{HasSource, InFile, Module, ModuleDef, ModuleSource, Semantics};
6use ide_db::{ 6use 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};
12use stdx::never; 12use stdx::never;
@@ -17,10 +17,7 @@ use syntax::{
17use test_utils::mark; 17use test_utils::mark;
18use text_edit::TextEdit; 18use text_edit::TextEdit;
19 19
20use crate::{ 20use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange};
21 display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, ReferenceKind, SourceChange,
22 TextRange,
23};
24 21
25type RenameResult<T> = Result<T, RenameError>; 22type 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.
44pub(crate) fn prepare_rename( 43pub(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
126enum NameLike {
127 Name(ast::Name),
128 NameRef(ast::NameRef),
129 Lifetime(ast::Lifetime),
130}
131
132fn find_name_like( 125fn 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
173fn source_edit_from_references( 166fn 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
214fn edit_text_range_for_record_field_expr_or_pat( 186fn 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
242fn rename_mod( 260fn 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#"
1463struct Foo { i: i32 }
1464
1465fn foo(bar$0: i32) -> Foo {
1466 Foo { i: bar }
1467}
1468"#,
1469 r#"
1470struct Foo { i: i32 }
1471
1472fn 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#"