aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/references
diff options
context:
space:
mode:
authorLukas Wirth <[email protected]>2021-01-09 15:59:00 +0000
committerLukas Wirth <[email protected]>2021-01-09 15:59:00 +0000
commit919a1d7b278ada6063c948df7e63d3ef735af343 (patch)
treeea173bf3754294e1b9dd9836a760d0a90a495d18 /crates/ide/src/references
parent939ca83b34f9a5648d196f85e5cc7d844ba22604 (diff)
Refactor rename name checking
Diffstat (limited to 'crates/ide/src/references')
-rw-r--r--crates/ide/src/references/rename.rs156
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;
20use text_edit::TextEdit; 20use text_edit::TextEdit;
21 21
22use crate::{ 22use 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
27type RenameResult<T> = Result<T, RenameError>;
27#[derive(Debug)] 28#[derive(Debug)]
28pub struct RenameError(pub(crate) String); 29pub struct RenameError(pub(crate) String);
29 30
@@ -38,7 +39,7 @@ impl Error for RenameError {}
38pub(crate) fn prepare_rename( 39pub(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)]
105enum IdentifierKind {
106 Ident,
107 Lifetime,
108 ToSelf,
109 Underscore,
110}
111
112fn 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
130fn find_module_at_offset( 134fn 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
162fn 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
158fn source_edit_from_reference( 170fn 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",