diff options
author | Lukas Wirth <[email protected]> | 2021-01-09 15:59:00 +0000 |
---|---|---|
committer | Lukas Wirth <[email protected]> | 2021-01-09 15:59:00 +0000 |
commit | 919a1d7b278ada6063c948df7e63d3ef735af343 (patch) | |
tree | ea173bf3754294e1b9dd9836a760d0a90a495d18 /crates/ide/src/references | |
parent | 939ca83b34f9a5648d196f85e5cc7d844ba22604 (diff) |
Refactor rename name checking
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r-- | crates/ide/src/references/rename.rs | 156 |
1 files changed, 103 insertions, 53 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs index 53d79333c..dd322631b 100644 --- a/crates/ide/src/references/rename.rs +++ b/crates/ide/src/references/rename.rs | |||
@@ -20,10 +20,11 @@ use test_utils::mark; | |||
20 | use text_edit::TextEdit; | 20 | use text_edit::TextEdit; |
21 | 21 | ||
22 | use crate::{ | 22 | use crate::{ |
23 | references::find_all_refs, FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, | 23 | FilePosition, FileSystemEdit, RangeInfo, Reference, ReferenceKind, ReferenceSearchResult, |
24 | SourceChange, SourceFileEdit, TextRange, TextSize, | 24 | SourceChange, SourceFileEdit, TextRange, TextSize, |
25 | }; | 25 | }; |
26 | 26 | ||
27 | type RenameResult<T> = Result<T, RenameError>; | ||
27 | #[derive(Debug)] | 28 | #[derive(Debug)] |
28 | pub struct RenameError(pub(crate) String); | 29 | pub struct RenameError(pub(crate) String); |
29 | 30 | ||
@@ -38,7 +39,7 @@ impl Error for RenameError {} | |||
38 | pub(crate) fn prepare_rename( | 39 | pub(crate) fn prepare_rename( |
39 | db: &RootDatabase, | 40 | db: &RootDatabase, |
40 | position: FilePosition, | 41 | position: FilePosition, |
41 | ) -> Result<RangeInfo<()>, RenameError> { | 42 | ) -> RenameResult<RangeInfo<()>> { |
42 | let sema = Semantics::new(db); | 43 | let sema = Semantics::new(db); |
43 | let source_file = sema.parse(position.file_id); | 44 | let source_file = sema.parse(position.file_id); |
44 | let syntax = source_file.syntax(); | 45 | let syntax = source_file.syntax(); |
@@ -49,10 +50,7 @@ pub(crate) fn prepare_rename( | |||
49 | { | 50 | { |
50 | rename_self_to_param(&sema, position, self_token, "dummy") | 51 | rename_self_to_param(&sema, position, self_token, "dummy") |
51 | } else { | 52 | } else { |
52 | let range = match find_all_refs(&sema, position, None) { | 53 | let RangeInfo { range, .. } = find_all_refs(&sema, position)?; |
53 | Some(RangeInfo { range, .. }) => range, | ||
54 | None => return Err(RenameError("No references found at position".to_string())), | ||
55 | }; | ||
56 | Ok(RangeInfo::new(range, SourceChange::from(vec![]))) | 54 | Ok(RangeInfo::new(range, SourceChange::from(vec![]))) |
57 | } | 55 | } |
58 | .map(|info| RangeInfo::new(info.range, ())) | 56 | .map(|info| RangeInfo::new(info.range, ())) |
@@ -62,7 +60,7 @@ pub(crate) fn rename( | |||
62 | db: &RootDatabase, | 60 | db: &RootDatabase, |
63 | position: FilePosition, | 61 | position: FilePosition, |
64 | new_name: &str, | 62 | new_name: &str, |
65 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 63 | ) -> RenameResult<RangeInfo<SourceChange>> { |
66 | let sema = Semantics::new(db); | 64 | let sema = Semantics::new(db); |
67 | rename_with_semantics(&sema, position, new_name) | 65 | rename_with_semantics(&sema, position, new_name) |
68 | } | 66 | } |
@@ -71,42 +69,18 @@ pub(crate) fn rename_with_semantics( | |||
71 | sema: &Semantics<RootDatabase>, | 69 | sema: &Semantics<RootDatabase>, |
72 | position: FilePosition, | 70 | position: FilePosition, |
73 | new_name: &str, | 71 | new_name: &str, |
74 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 72 | ) -> RenameResult<RangeInfo<SourceChange>> { |
75 | let is_lifetime_name = match lex_single_syntax_kind(new_name) { | ||
76 | Some(res) => match res { | ||
77 | (SyntaxKind::IDENT, _) => false, | ||
78 | (SyntaxKind::UNDERSCORE, _) => false, | ||
79 | (SyntaxKind::SELF_KW, _) => return rename_to_self(&sema, position), | ||
80 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => true, | ||
81 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
82 | return Err(RenameError(format!( | ||
83 | "Invalid name `{0}`: Cannot rename lifetime to {0}", | ||
84 | new_name | ||
85 | ))) | ||
86 | } | ||
87 | (_, Some(syntax_error)) => { | ||
88 | return Err(RenameError(format!("Invalid name `{}`: {}", new_name, syntax_error))) | ||
89 | } | ||
90 | (_, None) => { | ||
91 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) | ||
92 | } | ||
93 | }, | ||
94 | None => return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))), | ||
95 | }; | ||
96 | |||
97 | let source_file = sema.parse(position.file_id); | 73 | let source_file = sema.parse(position.file_id); |
98 | let syntax = source_file.syntax(); | 74 | let syntax = source_file.syntax(); |
99 | // this is here to prevent lifetime renames from happening on modules and self | 75 | |
100 | if is_lifetime_name { | 76 | if let Some(module) = find_module_at_offset(&sema, position, syntax) { |
101 | rename_reference(&sema, position, new_name, is_lifetime_name) | ||
102 | } else if let Some(module) = find_module_at_offset(&sema, position, syntax) { | ||
103 | rename_mod(&sema, position, module, new_name) | 77 | rename_mod(&sema, position, module, new_name) |
104 | } else if let Some(self_token) = | 78 | } else if let Some(self_token) = |
105 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) | 79 | syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) |
106 | { | 80 | { |
107 | rename_self_to_param(&sema, position, self_token, new_name) | 81 | rename_self_to_param(&sema, position, self_token, new_name) |
108 | } else { | 82 | } else { |
109 | rename_reference(&sema, position, new_name, is_lifetime_name) | 83 | rename_reference(&sema, position, new_name) |
110 | } | 84 | } |
111 | } | 85 | } |
112 | 86 | ||
@@ -127,6 +101,36 @@ pub(crate) fn will_rename_file( | |||
127 | Some(change) | 101 | Some(change) |
128 | } | 102 | } |
129 | 103 | ||
104 | #[derive(PartialEq)] | ||
105 | enum IdentifierKind { | ||
106 | Ident, | ||
107 | Lifetime, | ||
108 | ToSelf, | ||
109 | Underscore, | ||
110 | } | ||
111 | |||
112 | fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> { | ||
113 | match lex_single_syntax_kind(new_name) { | ||
114 | Some(res) => match res { | ||
115 | (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident), | ||
116 | (SyntaxKind::UNDERSCORE, _) => Ok(IdentifierKind::Underscore), | ||
117 | (SyntaxKind::SELF_KW, _) => Ok(IdentifierKind::ToSelf), | ||
118 | (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => { | ||
119 | Ok(IdentifierKind::Lifetime) | ||
120 | } | ||
121 | (SyntaxKind::LIFETIME_IDENT, _) => { | ||
122 | Err(format!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name)) | ||
123 | } | ||
124 | (_, Some(syntax_error)) => { | ||
125 | Err(format!("Invalid name `{}`: {}", new_name, syntax_error)) | ||
126 | } | ||
127 | (_, None) => Err(format!("Invalid name `{}`: not an identifier", new_name)), | ||
128 | }, | ||
129 | None => Err(format!("Invalid name `{}`: not an identifier", new_name)), | ||
130 | } | ||
131 | .map_err(RenameError) | ||
132 | } | ||
133 | |||
130 | fn find_module_at_offset( | 134 | fn find_module_at_offset( |
131 | sema: &Semantics<RootDatabase>, | 135 | sema: &Semantics<RootDatabase>, |
132 | position: FilePosition, | 136 | position: FilePosition, |
@@ -155,6 +159,14 @@ fn find_module_at_offset( | |||
155 | Some(module) | 159 | Some(module) |
156 | } | 160 | } |
157 | 161 | ||
162 | fn find_all_refs( | ||
163 | sema: &Semantics<RootDatabase>, | ||
164 | position: FilePosition, | ||
165 | ) -> RenameResult<RangeInfo<ReferenceSearchResult>> { | ||
166 | crate::references::find_all_refs(sema, position, None) | ||
167 | .ok_or_else(|| RenameError("No references found at position".to_string())) | ||
168 | } | ||
169 | |||
158 | fn source_edit_from_reference( | 170 | fn source_edit_from_reference( |
159 | sema: &Semantics<RootDatabase>, | 171 | sema: &Semantics<RootDatabase>, |
160 | reference: Reference, | 172 | reference: Reference, |
@@ -223,7 +235,13 @@ fn rename_mod( | |||
223 | position: FilePosition, | 235 | position: FilePosition, |
224 | module: Module, | 236 | module: Module, |
225 | new_name: &str, | 237 | new_name: &str, |
226 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 238 | ) -> RenameResult<RangeInfo<SourceChange>> { |
239 | if IdentifierKind::Ident != check_identifier(new_name)? { | ||
240 | return Err(RenameError(format!( | ||
241 | "Invalid name `{0}`: cannot rename module to {0}", | ||
242 | new_name | ||
243 | ))); | ||
244 | } | ||
227 | let mut source_file_edits = Vec::new(); | 245 | let mut source_file_edits = Vec::new(); |
228 | let mut file_system_edits = Vec::new(); | 246 | let mut file_system_edits = Vec::new(); |
229 | 247 | ||
@@ -254,8 +272,7 @@ fn rename_mod( | |||
254 | source_file_edits.push(edit); | 272 | source_file_edits.push(edit); |
255 | } | 273 | } |
256 | 274 | ||
257 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) | 275 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
258 | .ok_or_else(|| RenameError("No references found at position".to_string()))?; | ||
259 | let ref_edits = refs | 276 | let ref_edits = refs |
260 | .references | 277 | .references |
261 | .into_iter() | 278 | .into_iter() |
@@ -310,8 +327,7 @@ fn rename_to_self( | |||
310 | return Err(RenameError("Parameter type differs from impl block type".to_string())); | 327 | return Err(RenameError("Parameter type differs from impl block type".to_string())); |
311 | } | 328 | } |
312 | 329 | ||
313 | let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) | 330 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
314 | .ok_or_else(|| RenameError("No reference found at position".to_string()))?; | ||
315 | 331 | ||
316 | let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs | 332 | let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs |
317 | .into_iter() | 333 | .into_iter() |
@@ -367,6 +383,17 @@ fn rename_self_to_param( | |||
367 | self_token: SyntaxToken, | 383 | self_token: SyntaxToken, |
368 | new_name: &str, | 384 | new_name: &str, |
369 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 385 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
386 | let ident_kind = check_identifier(new_name)?; | ||
387 | match ident_kind { | ||
388 | IdentifierKind::Lifetime => { | ||
389 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) | ||
390 | } | ||
391 | IdentifierKind::ToSelf => { | ||
392 | // no-op | ||
393 | return Ok(RangeInfo::new(self_token.text_range(), SourceChange::default())); | ||
394 | } | ||
395 | _ => (), | ||
396 | } | ||
370 | let source_file = sema.parse(position.file_id); | 397 | let source_file = sema.parse(position.file_id); |
371 | let syn = source_file.syntax(); | 398 | let syn = source_file.syntax(); |
372 | 399 | ||
@@ -395,6 +422,12 @@ fn rename_self_to_param( | |||
395 | } | 422 | } |
396 | } | 423 | } |
397 | 424 | ||
425 | if edits.len() > 1 && ident_kind == IdentifierKind::Underscore { | ||
426 | return Err(RenameError(format!( | ||
427 | "Cannot rename reference to `_` as it is being referenced multiple times", | ||
428 | ))); | ||
429 | } | ||
430 | |||
398 | let range = ast::SelfParam::cast(self_token.parent()) | 431 | let range = ast::SelfParam::cast(self_token.parent()) |
399 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); | 432 | .map_or(self_token.text_range(), |p| p.syntax().text_range()); |
400 | 433 | ||
@@ -405,24 +438,36 @@ fn rename_reference( | |||
405 | sema: &Semantics<RootDatabase>, | 438 | sema: &Semantics<RootDatabase>, |
406 | position: FilePosition, | 439 | position: FilePosition, |
407 | new_name: &str, | 440 | new_name: &str, |
408 | is_lifetime_name: bool, | ||
409 | ) -> Result<RangeInfo<SourceChange>, RenameError> { | 441 | ) -> Result<RangeInfo<SourceChange>, RenameError> { |
410 | let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { | 442 | let ident_kind = check_identifier(new_name)?; |
411 | Some(range_info) => range_info, | 443 | let RangeInfo { range, info: refs } = find_all_refs(sema, position)?; |
412 | None => return Err(RenameError("No references found at position".to_string())), | ||
413 | }; | ||
414 | 444 | ||
415 | match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { | 445 | match (ident_kind, &refs.declaration.kind) { |
416 | (true, false) => { | 446 | (IdentifierKind::ToSelf, ReferenceKind::Lifetime) |
447 | | (IdentifierKind::Underscore, ReferenceKind::Lifetime) | ||
448 | | (IdentifierKind::Ident, ReferenceKind::Lifetime) => { | ||
417 | return Err(RenameError(format!( | 449 | return Err(RenameError(format!( |
418 | "Invalid name `{}`: not a lifetime identifier", | 450 | "Invalid name `{}`: not a lifetime identifier", |
419 | new_name | 451 | new_name |
420 | ))) | 452 | ))) |
421 | } | 453 | } |
422 | (false, true) => { | 454 | (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => (), |
455 | (IdentifierKind::Lifetime, _) => { | ||
423 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) | 456 | return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) |
424 | } | 457 | } |
425 | _ => (), | 458 | (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => { |
459 | //no-op | ||
460 | return Ok(RangeInfo::new(range, SourceChange::default())); | ||
461 | } | ||
462 | (IdentifierKind::ToSelf, _) => { | ||
463 | return rename_to_self(sema, position); | ||
464 | } | ||
465 | (IdentifierKind::Underscore, _) if !refs.references.is_empty() => { | ||
466 | return Err(RenameError(format!( | ||
467 | "Cannot rename reference to `_` as it is being referenced multiple times", | ||
468 | ))) | ||
469 | } | ||
470 | (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => (), | ||
426 | } | 471 | } |
427 | 472 | ||
428 | let edit = refs | 473 | let edit = refs |
@@ -430,10 +475,6 @@ fn rename_reference( | |||
430 | .map(|reference| source_edit_from_reference(sema, reference, new_name)) | 475 | .map(|reference| source_edit_from_reference(sema, reference, new_name)) |
431 | .collect::<Vec<_>>(); | 476 | .collect::<Vec<_>>(); |
432 | 477 | ||
433 | if edit.is_empty() { | ||
434 | return Err(RenameError("No references found at position".to_string())); | ||
435 | } | ||
436 | |||
437 | Ok(RangeInfo::new(range, SourceChange::from(edit))) | 478 | Ok(RangeInfo::new(range, SourceChange::from(edit))) |
438 | } | 479 | } |
439 | 480 | ||
@@ -547,6 +588,15 @@ mod tests { | |||
547 | } | 588 | } |
548 | 589 | ||
549 | #[test] | 590 | #[test] |
591 | fn test_rename_to_underscore_invalid() { | ||
592 | check( | ||
593 | "_", | ||
594 | r#"fn main(foo$0: ()) {foo;}"#, | ||
595 | "error: Cannot rename reference to `_` as it is being referenced multiple times", | ||
596 | ); | ||
597 | } | ||
598 | |||
599 | #[test] | ||
550 | fn test_rename_for_local() { | 600 | fn test_rename_for_local() { |
551 | check( | 601 | check( |
552 | "k", | 602 | "k", |