diff options
author | Lukas Wirth <[email protected]> | 2021-02-07 17:38:12 +0000 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2021-02-12 17:58:28 +0000 |
commit | d644728d82df10b034d0ea736590c781afa2ba15 (patch) | |
tree | 4591c44955de42387165e7a59789d7e636284bc6 /crates/ide/src/references | |
parent | 43ccbf4360271d5da2fd3688a04b34c66357e0b6 (diff) |
Refactor reference searching to work with the ast
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r-- | crates/ide/src/references/rename.rs | 197 |
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}; | |||
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, |
@@ -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 | ||
214 | fn edit_text_range_for_record_field_expr_or_pat( | 188 | fn 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 | ||
242 | fn rename_mod( | 262 | fn 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#" | ||
1465 | struct Foo { i: i32 } | ||
1466 | |||
1467 | fn foo(bar$0: i32) -> Foo { | ||
1468 | Foo { i: bar } | ||
1469 | } | ||
1470 | "#, | ||
1471 | r#" | ||
1472 | struct Foo { i: i32 } | ||
1473 | |||
1474 | fn 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#" |