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, 119 insertions, 78 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index ebb1ce7dd..64992c72d 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,
@@ -174,69 +167,96 @@ 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) {
173 let root = sema.parse(file_id);
179 let mut edit = TextEdit::builder(); 174 let mut edit = TextEdit::builder();
180 for reference in references { 175 for reference in references {
181 let mut replacement_text = String::new(); 176 let (range, replacement) = match &reference.name_from_syntax(root.syntax()) {
182 let range = match reference.kind { 177 Some(NameLike::Name(_)) => (None, format!("{}", new_name)),
183 ReferenceKind::FieldShorthandForField => { 178 Some(NameLike::NameRef(name_ref)) => source_edit_from_name_ref(name_ref, new_name, def),
184 mark::hit!(test_rename_struct_field_for_shorthand); 179 Some(NameLike::Lifetime(_)) => (None, format!("{}", new_name)),
185 replacement_text.push_str(new_name); 180 None => (None, new_name.to_owned()),
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 }; 181 };
209 edit.replace(range, replacement_text); 182 // FIXME: Some(range) will be incorrect when we are inside macros
183 edit.replace(range.unwrap_or(reference.range), replacement);
210 } 184 }
211 (file_id, edit.finish()) 185 (file_id, edit.finish())
212} 186}
213 187
214fn edit_text_range_for_record_field_expr_or_pat( 188fn source_edit_from_name_ref(
215 sema: &Semantics<RootDatabase>, 189 name_ref: &ast::NameRef,
216 file_range: FileRange,
217 new_name: &str, 190 new_name: &str,
218) -> TextRange { 191 def: Definition,
219 let source_file = sema.parse(file_range.file_id); 192) -> (Option<TextRange>, String) {
220 let file_syntax = source_file.syntax(); 193 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
221 let original_range = file_range.range; 194 let rcf_name_ref = record_field.name_ref();
222 195 let rcf_expr = record_field.expr();
223 syntax::algo::find_node_at_range::<ast::RecordExprField>(file_syntax, original_range) 196 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()) { 197 // 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()), 198 (Some(field_name), Some(init)) => {
226 _ => None, 199 if field_name == *name_ref {
227 }) 200 if init.text() == new_name {
228 .or_else(|| { 201 mark::hit!(test_rename_field_put_init_shorthand);
229 syntax::algo::find_node_at_range::<ast::RecordPatField>(file_syntax, original_range) 202 // same names, we can use a shorthand here instead
230 .and_then(|field_pat| match field_pat.pat() { 203 // we do not want to erase attributes hence this range start
231 Some(ast::Pat::IdentPat(pat)) 204 let s = field_name.syntax().text_range().start();
232 if pat.name().map(|n| n.to_string()).as_deref() == Some(new_name) => 205 let e = record_field.syntax().text_range().end();
233 { 206 return (Some(TextRange::new(s, e)), format!("{}", new_name));
234 Some(field_pat.syntax().text_range())
235 } 207 }
236 _ => None, 208 } else if init == *name_ref {
237 }) 209 if field_name.text() == new_name {
238 }) 210 mark::hit!(test_rename_local_put_init_shorthand);
239 .unwrap_or(original_range) 211 // same names, we can use a shorthand here instead
212 // we do not want to erase attributes hence this range start
213 let s = field_name.syntax().text_range().start();
214 let e = record_field.syntax().text_range().end();
215 return (Some(TextRange::new(s, e)), format!("{}", new_name));
216 }
217 }
218 }
219 // init shorthand
220 (None, Some(_)) => {
221 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
222 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
223 match def {
224 Definition::Field(_) => {
225 mark::hit!(test_rename_field_in_field_shorthand);
226 let s = name_ref.syntax().text_range().start();
227 return (Some(TextRange::empty(s)), format!("{}: ", new_name));
228 }
229 Definition::Local(_) => {
230 mark::hit!(test_rename_local_in_field_shorthand);
231 let s = name_ref.syntax().text_range().end();
232 return (Some(TextRange::empty(s)), format!(": {}", new_name));
233 }
234 _ => {}
235 }
236 }
237 _ => {}
238 }
239 }
240 if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
241 let rcf_name_ref = record_field.name_ref();
242 let rcf_pat = record_field.pat();
243 match (rcf_name_ref, rcf_pat) {
244 // field: rename
245 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
246 // field name is being renamed
247 if pat.name().map_or(false, |it| it.text() == new_name) {
248 mark::hit!(test_rename_field_put_init_shorthand_pat);
249 // same names, we can use a shorthand here instead
250 // we do not want to erase attributes hence this range start
251 let s = field_name.syntax().text_range().start();
252 let e = record_field.syntax().text_range().end();
253 return (Some(TextRange::new(s, e)), format!("{}", new_name));
254 }
255 }
256 _ => {}
257 }
258 }
259 (None, format!("{}", new_name))
240} 260}
241 261
242fn rename_mod( 262fn rename_mod(
@@ -277,7 +297,7 @@ fn rename_mod(
277 let def = Definition::ModuleDef(ModuleDef::Module(module)); 297 let def = Definition::ModuleDef(ModuleDef::Module(module));
278 let usages = def.usages(sema).all(); 298 let usages = def.usages(sema).all();
279 let ref_edits = usages.iter().map(|(&file_id, references)| { 299 let ref_edits = usages.iter().map(|(&file_id, references)| {
280 source_edit_from_references(sema, file_id, references, new_name) 300 source_edit_from_references(sema, file_id, references, def, new_name)
281 }); 301 });
282 source_change.extend(ref_edits); 302 source_change.extend(ref_edits);
283 303
@@ -346,7 +366,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
346 let usages = def.usages(sema).all(); 366 let usages = def.usages(sema).all();
347 let mut source_change = SourceChange::default(); 367 let mut source_change = SourceChange::default();
348 source_change.extend(usages.iter().map(|(&file_id, references)| { 368 source_change.extend(usages.iter().map(|(&file_id, references)| {
349 source_edit_from_references(sema, file_id, references, "self") 369 source_edit_from_references(sema, file_id, references, def, "self")
350 })); 370 }));
351 source_change.insert_source_edit( 371 source_change.insert_source_edit(
352 file_id.original_file(sema.db), 372 file_id.original_file(sema.db),
@@ -403,7 +423,7 @@ fn rename_self_to_param(
403 let mut source_change = SourceChange::default(); 423 let mut source_change = SourceChange::default();
404 source_change.insert_source_edit(file_id.original_file(sema.db), edit); 424 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
405 source_change.extend(usages.iter().map(|(&file_id, references)| { 425 source_change.extend(usages.iter().map(|(&file_id, references)| {
406 source_edit_from_references(sema, file_id, &references, new_name) 426 source_edit_from_references(sema, file_id, &references, def, new_name)
407 })); 427 }));
408 Ok(source_change) 428 Ok(source_change)
409} 429}
@@ -457,7 +477,7 @@ fn rename_reference(
457 } 477 }
458 let mut source_change = SourceChange::default(); 478 let mut source_change = SourceChange::default();
459 source_change.extend(usages.iter().map(|(&file_id, references)| { 479 source_change.extend(usages.iter().map(|(&file_id, references)| {
460 source_edit_from_references(sema, file_id, &references, new_name) 480 source_edit_from_references(sema, file_id, &references, def, new_name)
461 })); 481 }));
462 482
463 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; 483 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
@@ -545,10 +565,8 @@ mod tests {
545 565
546 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { 566 fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) {
547 let (analysis, position) = fixture::position(ra_fixture); 567 let (analysis, position) = fixture::position(ra_fixture);
548 let source_change = analysis 568 let source_change =
549 .rename(position, new_name) 569 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) 570 expect.assert_debug_eq(&source_change)
553 } 571 }
554 572
@@ -792,8 +810,8 @@ impl Foo {
792 } 810 }
793 811
794 #[test] 812 #[test]
795 fn test_rename_struct_field_for_shorthand() { 813 fn test_rename_field_in_field_shorthand() {
796 mark::check!(test_rename_struct_field_for_shorthand); 814 mark::check!(test_rename_field_in_field_shorthand);
797 check( 815 check(
798 "j", 816 "j",
799 r#" 817 r#"
@@ -818,8 +836,8 @@ impl Foo {
818 } 836 }
819 837
820 #[test] 838 #[test]
821 fn test_rename_local_for_field_shorthand() { 839 fn test_rename_local_in_field_shorthand() {
822 mark::check!(test_rename_local_for_field_shorthand); 840 mark::check!(test_rename_local_in_field_shorthand);
823 check( 841 check(
824 "j", 842 "j",
825 r#" 843 r#"
@@ -1417,8 +1435,8 @@ impl Foo {
1417 } 1435 }
1418 1436
1419 #[test] 1437 #[test]
1420 fn test_initializer_use_field_init_shorthand() { 1438 fn test_rename_field_put_init_shorthand() {
1421 mark::check!(test_rename_field_expr_pat); 1439 mark::check!(test_rename_field_put_init_shorthand);
1422 check( 1440 check(
1423 "bar", 1441 "bar",
1424 r#" 1442 r#"
@@ -1439,7 +1457,30 @@ fn foo(bar: i32) -> Foo {
1439 } 1457 }
1440 1458
1441 #[test] 1459 #[test]
1460 fn test_rename_local_put_init_shorthand() {
1461 mark::check!(test_rename_local_put_init_shorthand);
1462 check(
1463 "i",
1464 r#"
1465struct Foo { i: i32 }
1466
1467fn foo(bar$0: i32) -> Foo {
1468 Foo { i: bar }
1469}
1470"#,
1471 r#"
1472struct Foo { i: i32 }
1473
1474fn foo(i: i32) -> Foo {
1475 Foo { i }
1476}
1477"#,
1478 );
1479 }
1480
1481 #[test]
1442 fn test_struct_field_destructure_into_shorthand() { 1482 fn test_struct_field_destructure_into_shorthand() {
1483 mark::check!(test_rename_field_put_init_shorthand_pat);
1443 check( 1484 check(
1444 "baz", 1485 "baz",
1445 r#" 1486 r#"