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.rs341
1 files changed, 212 insertions, 129 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 53d79333c..c3ae568c2 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -1,29 +1,30 @@
1//! FIXME: write short doc here 1//! FIXME: write short doc here
2use std::{ 2use std::{
3 convert::TryInto, 3 convert::TryInto,
4 error::Error,
5 fmt::{self, Display}, 4 fmt::{self, Display},
6}; 5};
7 6
8use hir::{Module, ModuleDef, ModuleSource, Semantics}; 7use hir::{Module, ModuleDef, ModuleSource, Semantics};
9use ide_db::base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt};
10use ide_db::{ 8use ide_db::{
9 base_db::{AnchoredPathBuf, FileId, FileRange, SourceDatabaseExt},
11 defs::{Definition, NameClass, NameRefClass}, 10 defs::{Definition, NameClass, NameRefClass},
11 search::FileReference,
12 RootDatabase, 12 RootDatabase,
13}; 13};
14use syntax::{ 14use syntax::{
15 algo::find_node_at_offset, 15 algo::find_node_at_offset,
16 ast::{self, NameOwner}, 16 ast::{self, NameOwner},
17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, 17 lex_single_syntax_kind, match_ast, AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T,
18}; 18};
19use test_utils::mark; 19use 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, ReferenceKind, ReferenceSearchResult, SourceChange,
24 SourceChange, SourceFileEdit, TextRange, TextSize, 24 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
@@ -33,26 +34,30 @@ impl fmt::Display for RenameError {
33 } 34 }
34} 35}
35 36
36impl Error for RenameError {} 37macro_rules! format_err {
38 ($fmt:expr) => {RenameError(format!($fmt))};
39 ($fmt:expr, $($arg:tt)+) => {RenameError(format!($fmt, $($arg)+))}
40}
41
42macro_rules! bail {
43 ($($tokens:tt)*) => {return Err(format_err!($($tokens)*))}
44}
37 45
38pub(crate) fn prepare_rename( 46pub(crate) fn prepare_rename(
39 db: &RootDatabase, 47 db: &RootDatabase,
40 position: FilePosition, 48 position: FilePosition,
41) -> Result<RangeInfo<()>, RenameError> { 49) -> RenameResult<RangeInfo<()>> {
42 let sema = Semantics::new(db); 50 let sema = Semantics::new(db);
43 let source_file = sema.parse(position.file_id); 51 let source_file = sema.parse(position.file_id);
44 let syntax = source_file.syntax(); 52 let syntax = source_file.syntax();
45 if let Some(module) = find_module_at_offset(&sema, position, syntax) { 53 if let Some(module) = find_module_at_offset(&sema, position, syntax) {
46 rename_mod(&sema, position, module, "dummy") 54 rename_mod(&sema, position, module, "dummy")
47 } else if let Some(self_token) = 55 } else if let Some(self_token) =
48 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 56 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
49 { 57 {
50 rename_self_to_param(&sema, position, self_token, "dummy") 58 rename_self_to_param(&sema, position, self_token, "dummy")
51 } else { 59 } else {
52 let range = match find_all_refs(&sema, position, None) { 60 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![]))) 61 Ok(RangeInfo::new(range, SourceChange::from(vec![])))
57 } 62 }
58 .map(|info| RangeInfo::new(info.range, ())) 63 .map(|info| RangeInfo::new(info.range, ()))
@@ -62,7 +67,7 @@ pub(crate) fn rename(
62 db: &RootDatabase, 67 db: &RootDatabase,
63 position: FilePosition, 68 position: FilePosition,
64 new_name: &str, 69 new_name: &str,
65) -> Result<RangeInfo<SourceChange>, RenameError> { 70) -> RenameResult<RangeInfo<SourceChange>> {
66 let sema = Semantics::new(db); 71 let sema = Semantics::new(db);
67 rename_with_semantics(&sema, position, new_name) 72 rename_with_semantics(&sema, position, new_name)
68} 73}
@@ -71,42 +76,18 @@ pub(crate) fn rename_with_semantics(
71 sema: &Semantics<RootDatabase>, 76 sema: &Semantics<RootDatabase>,
72 position: FilePosition, 77 position: FilePosition,
73 new_name: &str, 78 new_name: &str,
74) -> Result<RangeInfo<SourceChange>, RenameError> { 79) -> 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); 80 let source_file = sema.parse(position.file_id);
98 let syntax = source_file.syntax(); 81 let syntax = source_file.syntax();
99 // this is here to prevent lifetime renames from happening on modules and self 82
100 if is_lifetime_name { 83 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) 84 rename_mod(&sema, position, module, new_name)
104 } else if let Some(self_token) = 85 } else if let Some(self_token) =
105 syntax.token_at_offset(position.offset).find(|t| t.kind() == SyntaxKind::SELF_KW) 86 syntax.token_at_offset(position.offset).find(|t| t.kind() == T![self])
106 { 87 {
107 rename_self_to_param(&sema, position, self_token, new_name) 88 rename_self_to_param(&sema, position, self_token, new_name)
108 } else { 89 } else {
109 rename_reference(&sema, position, new_name, is_lifetime_name) 90 rename_reference(&sema, position, new_name)
110 } 91 }
111} 92}
112 93
@@ -127,6 +108,33 @@ pub(crate) fn will_rename_file(
127 Some(change) 108 Some(change)
128} 109}
129 110
111#[derive(Debug, PartialEq)]
112enum IdentifierKind {
113 Ident,
114 Lifetime,
115 ToSelf,
116 Underscore,
117}
118
119fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
120 match lex_single_syntax_kind(new_name) {
121 Some(res) => match res {
122 (SyntaxKind::IDENT, _) => Ok(IdentifierKind::Ident),
123 (T![_], _) => Ok(IdentifierKind::Underscore),
124 (T![self], _) => Ok(IdentifierKind::ToSelf),
125 (SyntaxKind::LIFETIME_IDENT, _) if new_name != "'static" && new_name != "'_" => {
126 Ok(IdentifierKind::Lifetime)
127 }
128 (SyntaxKind::LIFETIME_IDENT, _) => {
129 bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name)
130 }
131 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
132 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
133 },
134 None => bail!("Invalid name `{}`: not an identifier", new_name),
135 }
136}
137
130fn find_module_at_offset( 138fn find_module_at_offset(
131 sema: &Semantics<RootDatabase>, 139 sema: &Semantics<RootDatabase>,
132 position: FilePosition, 140 position: FilePosition,
@@ -155,39 +163,54 @@ fn find_module_at_offset(
155 Some(module) 163 Some(module)
156} 164}
157 165
158fn source_edit_from_reference( 166fn find_all_refs(
167 sema: &Semantics<RootDatabase>,
168 position: FilePosition,
169) -> RenameResult<RangeInfo<ReferenceSearchResult>> {
170 crate::references::find_all_refs(sema, position, None)
171 .ok_or_else(|| format_err!("No references found at position"))
172}
173
174fn source_edit_from_references(
159 sema: &Semantics<RootDatabase>, 175 sema: &Semantics<RootDatabase>,
160 reference: Reference, 176 file_id: FileId,
177 references: &[FileReference],
161 new_name: &str, 178 new_name: &str,
162) -> SourceFileEdit { 179) -> SourceFileEdit {
163 let mut replacement_text = String::new(); 180 let mut edit = TextEdit::builder();
164 let range = match reference.kind { 181 for reference in references {
165 ReferenceKind::FieldShorthandForField => { 182 let mut replacement_text = String::new();
166 mark::hit!(test_rename_struct_field_for_shorthand); 183 let range = match reference.kind {
167 replacement_text.push_str(new_name); 184 ReferenceKind::FieldShorthandForField => {
168 replacement_text.push_str(": "); 185 mark::hit!(test_rename_struct_field_for_shorthand);
169 TextRange::new(reference.file_range.range.start(), reference.file_range.range.start()) 186 replacement_text.push_str(new_name);
170 } 187 replacement_text.push_str(": ");
171 ReferenceKind::FieldShorthandForLocal => { 188 TextRange::new(reference.range.start(), reference.range.start())
172 mark::hit!(test_rename_local_for_field_shorthand); 189 }
173 replacement_text.push_str(": "); 190 ReferenceKind::FieldShorthandForLocal => {
174 replacement_text.push_str(new_name); 191 mark::hit!(test_rename_local_for_field_shorthand);
175 TextRange::new(reference.file_range.range.end(), reference.file_range.range.end()) 192 replacement_text.push_str(": ");
176 } 193 replacement_text.push_str(new_name);
177 ReferenceKind::RecordFieldExprOrPat => { 194 TextRange::new(reference.range.end(), reference.range.end())
178 mark::hit!(test_rename_field_expr_pat); 195 }
179 replacement_text.push_str(new_name); 196 ReferenceKind::RecordFieldExprOrPat => {
180 edit_text_range_for_record_field_expr_or_pat(sema, reference.file_range, new_name) 197 mark::hit!(test_rename_field_expr_pat);
181 } 198 replacement_text.push_str(new_name);
182 _ => { 199 edit_text_range_for_record_field_expr_or_pat(
183 replacement_text.push_str(new_name); 200 sema,
184 reference.file_range.range 201 FileRange { file_id, range: reference.range },
185 } 202 new_name,
186 }; 203 )
187 SourceFileEdit { 204 }
188 file_id: reference.file_range.file_id, 205 _ => {
189 edit: TextEdit::replace(range, replacement_text), 206 replacement_text.push_str(new_name);
207 reference.range
208 }
209 };
210 edit.replace(range, replacement_text);
190 } 211 }
212
213 SourceFileEdit { file_id, edit: edit.finish() }
191} 214}
192 215
193fn edit_text_range_for_record_field_expr_or_pat( 216fn edit_text_range_for_record_field_expr_or_pat(
@@ -223,7 +246,10 @@ fn rename_mod(
223 position: FilePosition, 246 position: FilePosition,
224 module: Module, 247 module: Module,
225 new_name: &str, 248 new_name: &str,
226) -> Result<RangeInfo<SourceChange>, RenameError> { 249) -> RenameResult<RangeInfo<SourceChange>> {
250 if IdentifierKind::Ident != check_identifier(new_name)? {
251 bail!("Invalid name `{0}`: cannot rename module to {0}", new_name);
252 }
227 let mut source_file_edits = Vec::new(); 253 let mut source_file_edits = Vec::new();
228 let mut file_system_edits = Vec::new(); 254 let mut file_system_edits = Vec::new();
229 255
@@ -254,12 +280,10 @@ fn rename_mod(
254 source_file_edits.push(edit); 280 source_file_edits.push(edit);
255 } 281 }
256 282
257 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 283 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
258 .ok_or_else(|| RenameError("No references found at position".to_string()))?; 284 let ref_edits = refs.references().iter().map(|(&file_id, references)| {
259 let ref_edits = refs 285 source_edit_from_references(sema, file_id, references, new_name)
260 .references 286 });
261 .into_iter()
262 .map(|reference| source_edit_from_reference(sema, reference, new_name));
263 source_file_edits.extend(ref_edits); 287 source_file_edits.extend(ref_edits);
264 288
265 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits))) 289 Ok(RangeInfo::new(range, SourceChange::from_edits(source_file_edits, file_system_edits)))
@@ -274,27 +298,26 @@ fn rename_to_self(
274 298
275 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset) 299 let (fn_def, fn_ast) = find_node_at_offset::<ast::Fn>(syn, position.offset)
276 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast))) 300 .and_then(|fn_ast| sema.to_def(&fn_ast).zip(Some(fn_ast)))
277 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 301 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
278 let param_range = fn_ast 302 let param_range = fn_ast
279 .param_list() 303 .param_list()
280 .and_then(|p| p.params().next()) 304 .and_then(|p| p.params().next())
281 .ok_or_else(|| RenameError("Method has no parameters".to_string()))? 305 .ok_or_else(|| format_err!("Method has no parameters"))?
282 .syntax() 306 .syntax()
283 .text_range(); 307 .text_range();
284 if !param_range.contains(position.offset) { 308 if !param_range.contains(position.offset) {
285 return Err(RenameError("Only the first parameter can be self".to_string())); 309 bail!("Only the first parameter can be self");
286 } 310 }
287 311
288 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset) 312 let impl_block = find_node_at_offset::<ast::Impl>(syn, position.offset)
289 .and_then(|def| sema.to_def(&def)) 313 .and_then(|def| sema.to_def(&def))
290 .ok_or_else(|| RenameError("No impl block found for function".to_string()))?; 314 .ok_or_else(|| format_err!("No impl block found for function"))?;
291 if fn_def.self_param(sema.db).is_some() { 315 if fn_def.self_param(sema.db).is_some() {
292 return Err(RenameError("Method already has a self parameter".to_string())); 316 bail!("Method already has a self parameter");
293 } 317 }
294 318
295 let params = fn_def.assoc_fn_params(sema.db); 319 let params = fn_def.assoc_fn_params(sema.db);
296 let first_param = 320 let first_param = params.first().ok_or_else(|| format_err!("Method has no parameters"))?;
297 params.first().ok_or_else(|| RenameError("Method has no parameters".into()))?;
298 let first_param_ty = first_param.ty(); 321 let first_param_ty = first_param.ty();
299 let impl_ty = impl_block.target_ty(sema.db); 322 let impl_ty = impl_block.target_ty(sema.db);
300 let (ty, self_param) = if impl_ty.remove_ref().is_some() { 323 let (ty, self_param) = if impl_ty.remove_ref().is_some() {
@@ -307,23 +330,17 @@ fn rename_to_self(
307 }; 330 };
308 331
309 if ty != impl_ty { 332 if ty != impl_ty {
310 return Err(RenameError("Parameter type differs from impl block type".to_string())); 333 bail!("Parameter type differs from impl block type");
311 } 334 }
312 335
313 let RangeInfo { range, info: refs } = find_all_refs(sema, position, None) 336 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
314 .ok_or_else(|| RenameError("No reference found at position".to_string()))?;
315
316 let (param_ref, usages): (Vec<Reference>, Vec<Reference>) = refs
317 .into_iter()
318 .partition(|reference| param_range.intersect(reference.file_range.range).is_some());
319
320 if param_ref.is_empty() {
321 return Err(RenameError("Parameter to rename not found".to_string()));
322 }
323 337
324 let mut edits = usages 338 let mut edits = refs
325 .into_iter() 339 .references()
326 .map(|reference| source_edit_from_reference(sema, reference, "self")) 340 .iter()
341 .map(|(&file_id, references)| {
342 source_edit_from_references(sema, file_id, references, "self")
343 })
327 .collect::<Vec<_>>(); 344 .collect::<Vec<_>>();
328 345
329 edits.push(SourceFileEdit { 346 edits.push(SourceFileEdit {
@@ -367,12 +384,22 @@ fn rename_self_to_param(
367 self_token: SyntaxToken, 384 self_token: SyntaxToken,
368 new_name: &str, 385 new_name: &str,
369) -> Result<RangeInfo<SourceChange>, RenameError> { 386) -> Result<RangeInfo<SourceChange>, RenameError> {
387 let ident_kind = check_identifier(new_name)?;
388 match ident_kind {
389 IdentifierKind::Lifetime => bail!("Invalid name `{}`: not an identifier", new_name),
390 IdentifierKind::ToSelf => {
391 // no-op
392 mark::hit!(rename_self_to_self);
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
373 let text = sema.db.file_text(position.file_id); 400 let text = sema.db.file_text(position.file_id);
374 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset) 401 let fn_def = find_node_at_offset::<ast::Fn>(syn, position.offset)
375 .ok_or_else(|| RenameError("No surrounding method declaration found".to_string()))?; 402 .ok_or_else(|| format_err!("No surrounding method declaration found"))?;
376 let search_range = fn_def.syntax().text_range(); 403 let search_range = fn_def.syntax().text_range();
377 404
378 let mut edits: Vec<SourceFileEdit> = vec![]; 405 let mut edits: Vec<SourceFileEdit> = vec![];
@@ -382,12 +409,10 @@ fn rename_self_to_param(
382 if !search_range.contains_inclusive(offset) { 409 if !search_range.contains_inclusive(offset) {
383 continue; 410 continue;
384 } 411 }
385 if let Some(ref usage) = 412 if let Some(ref usage) = syn.token_at_offset(offset).find(|t| t.kind() == T![self]) {
386 syn.token_at_offset(offset).find(|t| t.kind() == SyntaxKind::SELF_KW)
387 {
388 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) { 413 let edit = if let Some(ref self_param) = ast::SelfParam::cast(usage.parent()) {
389 text_edit_from_self_param(syn, self_param, new_name) 414 text_edit_from_self_param(syn, self_param, new_name)
390 .ok_or_else(|| RenameError("No target type found".to_string()))? 415 .ok_or_else(|| format_err!("No target type found"))?
391 } else { 416 } else {
392 TextEdit::replace(usage.text_range(), String::from(new_name)) 417 TextEdit::replace(usage.text_range(), String::from(new_name))
393 }; 418 };
@@ -395,6 +420,10 @@ fn rename_self_to_param(
395 } 420 }
396 } 421 }
397 422
423 if edits.len() > 1 && ident_kind == IdentifierKind::Underscore {
424 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
425 }
426
398 let range = ast::SelfParam::cast(self_token.parent()) 427 let range = ast::SelfParam::cast(self_token.parent())
399 .map_or(self_token.text_range(), |p| p.syntax().text_range()); 428 .map_or(self_token.text_range(), |p| p.syntax().text_range());
400 429
@@ -405,35 +434,43 @@ fn rename_reference(
405 sema: &Semantics<RootDatabase>, 434 sema: &Semantics<RootDatabase>,
406 position: FilePosition, 435 position: FilePosition,
407 new_name: &str, 436 new_name: &str,
408 is_lifetime_name: bool,
409) -> Result<RangeInfo<SourceChange>, RenameError> { 437) -> Result<RangeInfo<SourceChange>, RenameError> {
410 let RangeInfo { range, info: refs } = match find_all_refs(sema, position, None) { 438 let ident_kind = check_identifier(new_name)?;
411 Some(range_info) => range_info, 439 let RangeInfo { range, info: refs } = find_all_refs(sema, position)?;
412 None => return Err(RenameError("No references found at position".to_string())), 440
413 }; 441 match (ident_kind, &refs.declaration.kind) {
414 442 (IdentifierKind::ToSelf, ReferenceKind::Lifetime)
415 match (refs.declaration.kind == ReferenceKind::Lifetime, is_lifetime_name) { 443 | (IdentifierKind::Underscore, ReferenceKind::Lifetime)
416 (true, false) => { 444 | (IdentifierKind::Ident, ReferenceKind::Lifetime) => {
417 return Err(RenameError(format!( 445 mark::hit!(rename_not_a_lifetime_ident_ref);
418 "Invalid name `{}`: not a lifetime identifier", 446 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
419 new_name
420 )))
421 } 447 }
422 (false, true) => { 448 (IdentifierKind::Lifetime, ReferenceKind::Lifetime) => mark::hit!(rename_lifetime),
423 return Err(RenameError(format!("Invalid name `{}`: not an identifier", new_name))) 449 (IdentifierKind::Lifetime, _) => {
450 mark::hit!(rename_not_an_ident_ref);
451 bail!("Invalid name `{}`: not an identifier", new_name)
424 } 452 }
425 _ => (), 453 (IdentifierKind::ToSelf, ReferenceKind::SelfKw) => {
454 unreachable!("rename_self_to_param should've been called instead")
455 }
456 (IdentifierKind::ToSelf, _) => {
457 mark::hit!(rename_to_self);
458 return rename_to_self(sema, position);
459 }
460 (IdentifierKind::Underscore, _) if !refs.references.is_empty() => {
461 mark::hit!(rename_underscore_multiple);
462 bail!("Cannot rename reference to `_` as it is being referenced multiple times")
463 }
464 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident),
426 } 465 }
427 466
428 let edit = refs 467 let edit = refs
429 .into_iter() 468 .into_iter()
430 .map(|reference| source_edit_from_reference(sema, reference, new_name)) 469 .map(|(file_id, references)| {
470 source_edit_from_references(sema, file_id, &references, new_name)
471 })
431 .collect::<Vec<_>>(); 472 .collect::<Vec<_>>();
432 473
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))) 474 Ok(RangeInfo::new(range, SourceChange::from(edit)))
438} 475}
439 476
@@ -462,9 +499,11 @@ mod tests {
462 text_edit_builder.replace(indel.delete, indel.insert); 499 text_edit_builder.replace(indel.delete, indel.insert);
463 } 500 }
464 } 501 }
465 let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string(); 502 if let Some(file_id) = file_id {
466 text_edit_builder.finish().apply(&mut result); 503 let mut result = analysis.file_text(file_id).unwrap().to_string();
467 assert_eq_text!(ra_fixture_after, &*result); 504 text_edit_builder.finish().apply(&mut result);
505 assert_eq_text!(ra_fixture_after, &*result);
506 }
468 } 507 }
469 Err(err) => { 508 Err(err) => {
470 if ra_fixture_after.starts_with("error:") { 509 if ra_fixture_after.starts_with("error:") {
@@ -530,6 +569,7 @@ mod tests {
530 569
531 #[test] 570 #[test]
532 fn test_rename_to_invalid_identifier_lifetime() { 571 fn test_rename_to_invalid_identifier_lifetime() {
572 mark::check!(rename_not_an_ident_ref);
533 check( 573 check(
534 "'foo", 574 "'foo",
535 r#"fn main() { let i$0 = 1; }"#, 575 r#"fn main() { let i$0 = 1; }"#,
@@ -539,6 +579,7 @@ mod tests {
539 579
540 #[test] 580 #[test]
541 fn test_rename_to_invalid_identifier_lifetime2() { 581 fn test_rename_to_invalid_identifier_lifetime2() {
582 mark::check!(rename_not_a_lifetime_ident_ref);
542 check( 583 check(
543 "foo", 584 "foo",
544 r#"fn main<'a>(_: &'a$0 ()) {}"#, 585 r#"fn main<'a>(_: &'a$0 ()) {}"#,
@@ -547,7 +588,27 @@ mod tests {
547 } 588 }
548 589
549 #[test] 590 #[test]
591 fn test_rename_to_underscore_invalid() {
592 mark::check!(rename_underscore_multiple);
593 check(
594 "_",
595 r#"fn main(foo$0: ()) {foo;}"#,
596 "error: Cannot rename reference to `_` as it is being referenced multiple times",
597 );
598 }
599
600 #[test]
601 fn test_rename_mod_invalid() {
602 check(
603 "'foo",
604 r#"mod foo$0 {}"#,
605 "error: Invalid name `'foo`: cannot rename module to 'foo",
606 );
607 }
608
609 #[test]
550 fn test_rename_for_local() { 610 fn test_rename_for_local() {
611 mark::check!(rename_ident);
551 check( 612 check(
552 "k", 613 "k",
553 r#" 614 r#"
@@ -945,7 +1006,7 @@ use crate::foo$0::FooContent;
945//- /lib.rs 1006//- /lib.rs
946mod fo$0o; 1007mod fo$0o;
947//- /foo/mod.rs 1008//- /foo/mod.rs
948// emtpy 1009// empty
949"#, 1010"#,
950 expect![[r#" 1011 expect![[r#"
951 RangeInfo { 1012 RangeInfo {
@@ -995,7 +1056,7 @@ mod fo$0o;
995mod outer { mod fo$0o; } 1056mod outer { mod fo$0o; }
996 1057
997//- /outer/foo.rs 1058//- /outer/foo.rs
998// emtpy 1059// empty
999"#, 1060"#,
1000 expect![[r#" 1061 expect![[r#"
1001 RangeInfo { 1062 RangeInfo {
@@ -1178,6 +1239,7 @@ fn foo(f: foo::Foo) {
1178 1239
1179 #[test] 1240 #[test]
1180 fn test_parameter_to_self() { 1241 fn test_parameter_to_self() {
1242 mark::check!(rename_to_self);
1181 check( 1243 check(
1182 "self", 1244 "self",
1183 r#" 1245 r#"
@@ -1481,6 +1543,7 @@ fn foo(Foo { i: bar }: foo) -> i32 {
1481 1543
1482 #[test] 1544 #[test]
1483 fn test_rename_lifetimes() { 1545 fn test_rename_lifetimes() {
1546 mark::check!(rename_lifetime);
1484 check( 1547 check(
1485 "'yeeee", 1548 "'yeeee",
1486 r#" 1549 r#"
@@ -1565,4 +1628,24 @@ fn foo<'a>() -> &'a () {
1565"#, 1628"#,
1566 ) 1629 )
1567 } 1630 }
1631
1632 #[test]
1633 fn test_self_to_self() {
1634 mark::check!(rename_self_to_self);
1635 check(
1636 "self",
1637 r#"
1638struct Foo;
1639impl Foo {
1640 fn foo(self$0) {}
1641}
1642"#,
1643 r#"
1644struct Foo;
1645impl Foo {
1646 fn foo(self) {}
1647}
1648"#,
1649 )
1650 }
1568} 1651}