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.rs478
1 files changed, 265 insertions, 213 deletions
diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs
index 175ddd759..1e378279d 100644
--- a/crates/ide/src/references/rename.rs
+++ b/crates/ide/src/references/rename.rs
@@ -14,14 +14,14 @@ use syntax::{
14 ast::{self, NameOwner}, 14 ast::{self, NameOwner},
15 lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T, 15 lex_single_syntax_kind, AstNode, SyntaxKind, SyntaxNode, T,
16}; 16};
17use test_utils::mark; 17
18use text_edit::TextEdit; 18use text_edit::TextEdit;
19 19
20use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange}; 20use crate::{display::TryToNav, FilePosition, FileSystemEdit, RangeInfo, SourceChange, TextRange};
21 21
22type RenameResult<T> = Result<T, RenameError>; 22type RenameResult<T> = Result<T, RenameError>;
23#[derive(Debug)] 23#[derive(Debug)]
24pub struct RenameError(pub(crate) String); 24pub struct RenameError(String);
25 25
26impl fmt::Display for RenameError { 26impl fmt::Display for RenameError {
27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 27 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -47,18 +47,26 @@ pub(crate) fn prepare_rename(
47 let sema = Semantics::new(db); 47 let sema = Semantics::new(db);
48 let source_file = sema.parse(position.file_id); 48 let source_file = sema.parse(position.file_id);
49 let syntax = source_file.syntax(); 49 let syntax = source_file.syntax();
50 let range = match &sema 50 let name_like = sema
51 .find_node_at_offset_with_descend(&syntax, position.offset) 51 .find_node_at_offset_with_descend(&syntax, position.offset)
52 .ok_or_else(|| format_err!("No references found at position"))? 52 .ok_or_else(|| format_err!("No references found at position"))?;
53 { 53 let node = match &name_like {
54 ast::NameLike::Name(it) => it.syntax(), 54 ast::NameLike::Name(it) => it.syntax(),
55 ast::NameLike::NameRef(it) => it.syntax(), 55 ast::NameLike::NameRef(it) => it.syntax(),
56 ast::NameLike::Lifetime(it) => it.syntax(), 56 ast::NameLike::Lifetime(it) => it.syntax(),
57 } 57 };
58 .text_range(); 58 Ok(RangeInfo::new(sema.original_range(node).range, ()))
59 Ok(RangeInfo::new(range, ())) 59}
60} 60
61 61// Feature: Rename
62//
63// Renames the item below the cursor and all of its references
64//
65// |===
66// | Editor | Shortcut
67//
68// | VS Code | kbd:[F2]
69// |===
62pub(crate) fn rename( 70pub(crate) fn rename(
63 db: &RootDatabase, 71 db: &RootDatabase,
64 position: FilePosition, 72 position: FilePosition,
@@ -79,10 +87,13 @@ pub(crate) fn rename_with_semantics(
79 let def = find_definition(sema, syntax, position)?; 87 let def = find_definition(sema, syntax, position)?;
80 match def { 88 match def {
81 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name), 89 Definition::ModuleDef(ModuleDef::Module(module)) => rename_mod(&sema, module, new_name),
90 Definition::SelfType(_) => bail!("Cannot rename `Self`"),
91 Definition::ModuleDef(ModuleDef::BuiltinType(_)) => bail!("Cannot rename builtin type"),
82 def => rename_reference(sema, def, new_name), 92 def => rename_reference(sema, def, new_name),
83 } 93 }
84} 94}
85 95
96/// Called by the client when it is about to rename a file.
86pub(crate) fn will_rename_file( 97pub(crate) fn will_rename_file(
87 db: &RootDatabase, 98 db: &RootDatabase,
88 file_id: FileId, 99 file_id: FileId,
@@ -113,7 +124,7 @@ fn check_identifier(new_name: &str) -> RenameResult<IdentifierKind> {
113 Ok(IdentifierKind::Lifetime) 124 Ok(IdentifierKind::Lifetime)
114 } 125 }
115 (SyntaxKind::LIFETIME_IDENT, _) => { 126 (SyntaxKind::LIFETIME_IDENT, _) => {
116 bail!("Invalid name `{0}`: Cannot rename lifetime to {0}", new_name) 127 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
117 } 128 }
118 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error), 129 (_, Some(syntax_error)) => bail!("Invalid name `{}`: {}", new_name, syntax_error),
119 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name), 130 (_, None) => bail!("Invalid name `{}`: not an identifier", new_name),
@@ -153,119 +164,6 @@ fn find_definition(
153 .ok_or_else(|| format_err!("No references found at position")) 164 .ok_or_else(|| format_err!("No references found at position"))
154} 165}
155 166
156fn source_edit_from_references(
157 _sema: &Semantics<RootDatabase>,
158 file_id: FileId,
159 references: &[FileReference],
160 def: Definition,
161 new_name: &str,
162) -> (FileId, TextEdit) {
163 let mut edit = TextEdit::builder();
164 for reference in references {
165 let (range, replacement) = match &reference.name {
166 // if the ranges differ then the node is inside a macro call, we can't really attempt
167 // to make special rewrites like shorthand syntax and such, so just rename the node in
168 // the macro input
169 ast::NameLike::NameRef(name_ref)
170 if name_ref.syntax().text_range() == reference.range =>
171 {
172 source_edit_from_name_ref(name_ref, new_name, def)
173 }
174 ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
175 source_edit_from_name(name, new_name)
176 }
177 _ => None,
178 }
179 .unwrap_or_else(|| (reference.range, new_name.to_string()));
180 edit.replace(range, replacement);
181 }
182 (file_id, edit.finish())
183}
184
185fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
186 if let Some(_) = ast::RecordPatField::for_field_name(name) {
187 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
188 return Some((
189 TextRange::empty(ident_pat.syntax().text_range().start()),
190 format!("{}: ", new_name),
191 ));
192 }
193 }
194 None
195}
196
197fn source_edit_from_name_ref(
198 name_ref: &ast::NameRef,
199 new_name: &str,
200 def: Definition,
201) -> Option<(TextRange, String)> {
202 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
203 let rcf_name_ref = record_field.name_ref();
204 let rcf_expr = record_field.expr();
205 match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
206 // field: init-expr, check if we can use a field init shorthand
207 (Some(field_name), Some(init)) => {
208 if field_name == *name_ref {
209 if init.text() == new_name {
210 mark::hit!(test_rename_field_put_init_shorthand);
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), new_name.to_owned()));
216 }
217 } else if init == *name_ref {
218 if field_name.text() == new_name {
219 mark::hit!(test_rename_local_put_init_shorthand);
220 // same names, we can use a shorthand here instead.
221 // we do not want to erase attributes hence this range start
222 let s = field_name.syntax().text_range().start();
223 let e = record_field.syntax().text_range().end();
224 return Some((TextRange::new(s, e), new_name.to_owned()));
225 }
226 }
227 None
228 }
229 // init shorthand
230 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
231 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
232 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
233 mark::hit!(test_rename_field_in_field_shorthand);
234 let s = name_ref.syntax().text_range().start();
235 Some((TextRange::empty(s), format!("{}: ", new_name)))
236 }
237 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
238 mark::hit!(test_rename_local_in_field_shorthand);
239 let s = name_ref.syntax().text_range().end();
240 Some((TextRange::empty(s), format!(": {}", new_name)))
241 }
242 _ => None,
243 }
244 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
245 let rcf_name_ref = record_field.name_ref();
246 let rcf_pat = record_field.pat();
247 match (rcf_name_ref, rcf_pat) {
248 // field: rename
249 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
250 // field name is being renamed
251 if pat.name().map_or(false, |it| it.text() == new_name) {
252 mark::hit!(test_rename_field_put_init_shorthand_pat);
253 // same names, we can use a shorthand here instead/
254 // we do not want to erase attributes hence this range start
255 let s = field_name.syntax().text_range().start();
256 let e = record_field.syntax().text_range().end();
257 Some((TextRange::new(s, e), pat.to_string()))
258 } else {
259 None
260 }
261 }
262 _ => None,
263 }
264 } else {
265 None
266 }
267}
268
269fn rename_mod( 167fn rename_mod(
270 sema: &Semantics<RootDatabase>, 168 sema: &Semantics<RootDatabase>,
271 module: Module, 169 module: Module,
@@ -299,18 +197,77 @@ fn rename_mod(
299 TextEdit::replace(name.syntax().text_range(), new_name.to_string()), 197 TextEdit::replace(name.syntax().text_range(), new_name.to_string()),
300 ), 198 ),
301 _ => unreachable!(), 199 _ => unreachable!(),
302 }; 200 }
303 } 201 }
304 let def = Definition::ModuleDef(ModuleDef::Module(module)); 202 let def = Definition::ModuleDef(ModuleDef::Module(module));
305 let usages = def.usages(sema).all(); 203 let usages = def.usages(sema).all();
306 let ref_edits = usages.iter().map(|(&file_id, references)| { 204 let ref_edits = usages.iter().map(|(&file_id, references)| {
307 source_edit_from_references(sema, file_id, references, def, new_name) 205 (file_id, source_edit_from_references(references, def, new_name))
308 }); 206 });
309 source_change.extend(ref_edits); 207 source_change.extend(ref_edits);
310 208
311 Ok(source_change) 209 Ok(source_change)
312} 210}
313 211
212fn rename_reference(
213 sema: &Semantics<RootDatabase>,
214 def: Definition,
215 new_name: &str,
216) -> RenameResult<SourceChange> {
217 let ident_kind = check_identifier(new_name)?;
218
219 let def_is_lbl_or_lt = matches!(
220 def,
221 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_)
222 );
223 match (ident_kind, def) {
224 (IdentifierKind::ToSelf, _)
225 | (IdentifierKind::Underscore, _)
226 | (IdentifierKind::Ident, _)
227 if def_is_lbl_or_lt =>
228 {
229 cov_mark::hit!(rename_not_a_lifetime_ident_ref);
230 bail!("Invalid name `{}`: not a lifetime identifier", new_name)
231 }
232 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => cov_mark::hit!(rename_lifetime),
233 (IdentifierKind::Lifetime, _) => {
234 cov_mark::hit!(rename_not_an_ident_ref);
235 bail!("Invalid name `{}`: not an identifier", new_name)
236 }
237 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
238 // no-op
239 cov_mark::hit!(rename_self_to_self);
240 return Ok(SourceChange::default());
241 }
242 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
243 cov_mark::hit!(rename_self_to_param);
244 return rename_self_to_param(sema, local, new_name, ident_kind);
245 }
246 (IdentifierKind::ToSelf, Definition::Local(local)) => {
247 cov_mark::hit!(rename_to_self);
248 return rename_to_self(sema, local);
249 }
250 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name),
251 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => {
252 cov_mark::hit!(rename_ident)
253 }
254 }
255
256 let usages = def.usages(sema).all();
257 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore {
258 cov_mark::hit!(rename_underscore_multiple);
259 bail!("Cannot rename reference to `_` as it is being referenced multiple times");
260 }
261 let mut source_change = SourceChange::default();
262 source_change.extend(usages.iter().map(|(&file_id, references)| {
263 (file_id, source_edit_from_references(&references, def, new_name))
264 }));
265
266 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?;
267 source_change.insert_source_edit(file_id, edit);
268 Ok(source_change)
269}
270
314fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> { 271fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameResult<SourceChange> {
315 if never!(local.is_self(sema.db)) { 272 if never!(local.is_self(sema.db)) {
316 bail!("rename_to_self invoked on self"); 273 bail!("rename_to_self invoked on self");
@@ -375,7 +332,7 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
375 let usages = def.usages(sema).all(); 332 let usages = def.usages(sema).all();
376 let mut source_change = SourceChange::default(); 333 let mut source_change = SourceChange::default();
377 source_change.extend(usages.iter().map(|(&file_id, references)| { 334 source_change.extend(usages.iter().map(|(&file_id, references)| {
378 source_edit_from_references(sema, file_id, references, def, "self") 335 (file_id, source_edit_from_references(references, def, "self"))
379 })); 336 }));
380 source_change.insert_source_edit( 337 source_change.insert_source_edit(
381 file_id.original_file(sema.db), 338 file_id.original_file(sema.db),
@@ -385,29 +342,6 @@ fn rename_to_self(sema: &Semantics<RootDatabase>, local: hir::Local) -> RenameRe
385 Ok(source_change) 342 Ok(source_change)
386} 343}
387 344
388fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
389 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
390 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
391 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
392 }
393 None
394 }
395
396 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
397 let type_name = target_type_name(&impl_def)?;
398
399 let mut replacement_text = String::from(new_name);
400 replacement_text.push_str(": ");
401 match (self_param.amp_token(), self_param.mut_token()) {
402 (None, None) => (),
403 (Some(_), None) => replacement_text.push('&'),
404 (_, Some(_)) => replacement_text.push_str("&mut "),
405 };
406 replacement_text.push_str(type_name.as_str());
407
408 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
409}
410
411fn rename_self_to_param( 345fn rename_self_to_param(
412 sema: &Semantics<RootDatabase>, 346 sema: &Semantics<RootDatabase>,
413 local: hir::Local, 347 local: hir::Local,
@@ -432,66 +366,143 @@ fn rename_self_to_param(
432 let mut source_change = SourceChange::default(); 366 let mut source_change = SourceChange::default();
433 source_change.insert_source_edit(file_id.original_file(sema.db), edit); 367 source_change.insert_source_edit(file_id.original_file(sema.db), edit);
434 source_change.extend(usages.iter().map(|(&file_id, references)| { 368 source_change.extend(usages.iter().map(|(&file_id, references)| {
435 source_edit_from_references(sema, file_id, &references, def, new_name) 369 (file_id, source_edit_from_references(&references, def, new_name))
436 })); 370 }));
437 Ok(source_change) 371 Ok(source_change)
438} 372}
439 373
440fn rename_reference( 374fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option<TextEdit> {
441 sema: &Semantics<RootDatabase>, 375 fn target_type_name(impl_def: &ast::Impl) -> Option<String> {
376 if let Some(ast::Type::PathType(p)) = impl_def.self_ty() {
377 return Some(p.path()?.segment()?.name_ref()?.text().to_string());
378 }
379 None
380 }
381
382 let impl_def = self_param.syntax().ancestors().find_map(|it| ast::Impl::cast(it))?;
383 let type_name = target_type_name(&impl_def)?;
384
385 let mut replacement_text = String::from(new_name);
386 replacement_text.push_str(": ");
387 match (self_param.amp_token(), self_param.mut_token()) {
388 (Some(_), None) => replacement_text.push('&'),
389 (Some(_), Some(_)) => replacement_text.push_str("&mut "),
390 (_, _) => (),
391 };
392 replacement_text.push_str(type_name.as_str());
393
394 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))
395}
396
397fn source_edit_from_references(
398 references: &[FileReference],
442 def: Definition, 399 def: Definition,
443 new_name: &str, 400 new_name: &str,
444) -> RenameResult<SourceChange> { 401) -> TextEdit {
445 let ident_kind = check_identifier(new_name)?; 402 let mut edit = TextEdit::builder();
446 403 for reference in references {
447 let def_is_lbl_or_lt = matches!( 404 let (range, replacement) = match &reference.name {
448 def, 405 // if the ranges differ then the node is inside a macro call, we can't really attempt
449 Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) 406 // to make special rewrites like shorthand syntax and such, so just rename the node in
450 ); 407 // the macro input
451 match (ident_kind, def) { 408 ast::NameLike::NameRef(name_ref)
452 (IdentifierKind::ToSelf, _) 409 if name_ref.syntax().text_range() == reference.range =>
453 | (IdentifierKind::Underscore, _) 410 {
454 | (IdentifierKind::Ident, _) 411 source_edit_from_name_ref(name_ref, new_name, def)
455 if def_is_lbl_or_lt => 412 }
456 { 413 ast::NameLike::Name(name) if name.syntax().text_range() == reference.range => {
457 mark::hit!(rename_not_a_lifetime_ident_ref); 414 source_edit_from_name(name, new_name)
458 bail!("Invalid name `{}`: not a lifetime identifier", new_name) 415 }
459 } 416 _ => None,
460 (IdentifierKind::Lifetime, _) if def_is_lbl_or_lt => mark::hit!(rename_lifetime),
461 (IdentifierKind::Lifetime, _) => {
462 mark::hit!(rename_not_an_ident_ref);
463 bail!("Invalid name `{}`: not an identifier", new_name)
464 }
465 (IdentifierKind::ToSelf, Definition::Local(local)) if local.is_self(sema.db) => {
466 // no-op
467 mark::hit!(rename_self_to_self);
468 return Ok(SourceChange::default());
469 }
470 (ident_kind, Definition::Local(local)) if local.is_self(sema.db) => {
471 mark::hit!(rename_self_to_param);
472 return rename_self_to_param(sema, local, new_name, ident_kind);
473 }
474 (IdentifierKind::ToSelf, Definition::Local(local)) => {
475 mark::hit!(rename_to_self);
476 return rename_to_self(sema, local);
477 } 417 }
478 (IdentifierKind::ToSelf, _) => bail!("Invalid name `{}`: not an identifier", new_name), 418 .unwrap_or_else(|| (reference.range, new_name.to_string()));
479 (IdentifierKind::Ident, _) | (IdentifierKind::Underscore, _) => mark::hit!(rename_ident), 419 edit.replace(range, replacement);
480 } 420 }
421 edit.finish()
422}
481 423
482 let usages = def.usages(sema).all(); 424fn source_edit_from_name(name: &ast::Name, new_name: &str) -> Option<(TextRange, String)> {
483 if !usages.is_empty() && ident_kind == IdentifierKind::Underscore { 425 if let Some(_) = ast::RecordPatField::for_field_name(name) {
484 mark::hit!(rename_underscore_multiple); 426 if let Some(ident_pat) = name.syntax().parent().and_then(ast::IdentPat::cast) {
485 bail!("Cannot rename reference to `_` as it is being referenced multiple times"); 427 return Some((
428 TextRange::empty(ident_pat.syntax().text_range().start()),
429 [new_name, ": "].concat(),
430 ));
431 }
486 } 432 }
487 let mut source_change = SourceChange::default(); 433 None
488 source_change.extend(usages.iter().map(|(&file_id, references)| { 434}
489 source_edit_from_references(sema, file_id, &references, def, new_name)
490 }));
491 435
492 let (file_id, edit) = source_edit_from_def(sema, def, new_name)?; 436fn source_edit_from_name_ref(
493 source_change.insert_source_edit(file_id, edit); 437 name_ref: &ast::NameRef,
494 Ok(source_change) 438 new_name: &str,
439 def: Definition,
440) -> Option<(TextRange, String)> {
441 if let Some(record_field) = ast::RecordExprField::for_name_ref(name_ref) {
442 let rcf_name_ref = record_field.name_ref();
443 let rcf_expr = record_field.expr();
444 match (rcf_name_ref, rcf_expr.and_then(|it| it.name_ref())) {
445 // field: init-expr, check if we can use a field init shorthand
446 (Some(field_name), Some(init)) => {
447 if field_name == *name_ref {
448 if init.text() == new_name {
449 cov_mark::hit!(test_rename_field_put_init_shorthand);
450 // same names, we can use a shorthand here instead.
451 // we do not want to erase attributes hence this range start
452 let s = field_name.syntax().text_range().start();
453 let e = record_field.syntax().text_range().end();
454 return Some((TextRange::new(s, e), new_name.to_owned()));
455 }
456 } else if init == *name_ref {
457 if field_name.text() == new_name {
458 cov_mark::hit!(test_rename_local_put_init_shorthand);
459 // same names, we can use a shorthand here instead.
460 // we do not want to erase attributes hence this range start
461 let s = field_name.syntax().text_range().start();
462 let e = record_field.syntax().text_range().end();
463 return Some((TextRange::new(s, e), new_name.to_owned()));
464 }
465 }
466 None
467 }
468 // init shorthand
469 // FIXME: instead of splitting the shorthand, recursively trigger a rename of the
470 // other name https://github.com/rust-analyzer/rust-analyzer/issues/6547
471 (None, Some(_)) if matches!(def, Definition::Field(_)) => {
472 cov_mark::hit!(test_rename_field_in_field_shorthand);
473 let s = name_ref.syntax().text_range().start();
474 Some((TextRange::empty(s), format!("{}: ", new_name)))
475 }
476 (None, Some(_)) if matches!(def, Definition::Local(_)) => {
477 cov_mark::hit!(test_rename_local_in_field_shorthand);
478 let s = name_ref.syntax().text_range().end();
479 Some((TextRange::empty(s), format!(": {}", new_name)))
480 }
481 _ => None,
482 }
483 } else if let Some(record_field) = ast::RecordPatField::for_field_name_ref(name_ref) {
484 let rcf_name_ref = record_field.name_ref();
485 let rcf_pat = record_field.pat();
486 match (rcf_name_ref, rcf_pat) {
487 // field: rename
488 (Some(field_name), Some(ast::Pat::IdentPat(pat))) if field_name == *name_ref => {
489 // field name is being renamed
490 if pat.name().map_or(false, |it| it.text() == new_name) {
491 cov_mark::hit!(test_rename_field_put_init_shorthand_pat);
492 // same names, we can use a shorthand here instead/
493 // we do not want to erase attributes hence this range start
494 let s = field_name.syntax().text_range().start();
495 let e = record_field.syntax().text_range().end();
496 Some((TextRange::new(s, e), pat.to_string()))
497 } else {
498 None
499 }
500 }
501 _ => None,
502 }
503 } else {
504 None
505 }
495} 506}
496 507
497fn source_edit_from_def( 508fn source_edit_from_def(
@@ -529,11 +540,13 @@ fn source_edit_from_def(
529mod tests { 540mod tests {
530 use expect_test::{expect, Expect}; 541 use expect_test::{expect, Expect};
531 use stdx::trim_indent; 542 use stdx::trim_indent;
532 use test_utils::{assert_eq_text, mark}; 543 use test_utils::assert_eq_text;
533 use text_edit::TextEdit; 544 use text_edit::TextEdit;
534 545
535 use crate::{fixture, FileId}; 546 use crate::{fixture, FileId};
536 547
548 use super::{RangeInfo, RenameError};
549
537 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { 550 fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
538 let ra_fixture_after = &trim_indent(ra_fixture_after); 551 let ra_fixture_after = &trim_indent(ra_fixture_after);
539 let (analysis, position) = fixture::position(ra_fixture_before); 552 let (analysis, position) = fixture::position(ra_fixture_before);
@@ -579,6 +592,45 @@ mod tests {
579 expect.assert_debug_eq(&source_change) 592 expect.assert_debug_eq(&source_change)
580 } 593 }
581 594
595 fn check_prepare(ra_fixture: &str, expect: Expect) {
596 let (analysis, position) = fixture::position(ra_fixture);
597 let result = analysis
598 .prepare_rename(position)
599 .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {}", err));
600 match result {
601 Ok(RangeInfo { range, info: () }) => {
602 let source = analysis.file_text(position.file_id).unwrap();
603 expect.assert_eq(&format!("{:?}: {}", range, &source[range]))
604 }
605 Err(RenameError(err)) => expect.assert_eq(&err),
606 };
607 }
608
609 #[test]
610 fn test_prepare_rename_namelikes() {
611 check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);
612 check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]);
613 check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);
614 }
615
616 #[test]
617 fn test_prepare_rename_in_macro() {
618 check_prepare(
619 r"macro_rules! foo {
620 ($ident:ident) => {
621 pub struct $ident;
622 }
623}
624foo!(Foo$0);",
625 expect![[r#"83..86: Foo"#]],
626 );
627 }
628
629 #[test]
630 fn test_prepare_rename_keyword() {
631 check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);
632 }
633
582 #[test] 634 #[test]
583 fn test_rename_to_underscore() { 635 fn test_rename_to_underscore() {
584 check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#); 636 check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);
@@ -618,7 +670,7 @@ mod tests {
618 670
619 #[test] 671 #[test]
620 fn test_rename_to_invalid_identifier_lifetime() { 672 fn test_rename_to_invalid_identifier_lifetime() {
621 mark::check!(rename_not_an_ident_ref); 673 cov_mark::check!(rename_not_an_ident_ref);
622 check( 674 check(
623 "'foo", 675 "'foo",
624 r#"fn main() { let i$0 = 1; }"#, 676 r#"fn main() { let i$0 = 1; }"#,
@@ -628,7 +680,7 @@ mod tests {
628 680
629 #[test] 681 #[test]
630 fn test_rename_to_invalid_identifier_lifetime2() { 682 fn test_rename_to_invalid_identifier_lifetime2() {
631 mark::check!(rename_not_a_lifetime_ident_ref); 683 cov_mark::check!(rename_not_a_lifetime_ident_ref);
632 check( 684 check(
633 "foo", 685 "foo",
634 r#"fn main<'a>(_: &'a$0 ()) {}"#, 686 r#"fn main<'a>(_: &'a$0 ()) {}"#,
@@ -638,7 +690,7 @@ mod tests {
638 690
639 #[test] 691 #[test]
640 fn test_rename_to_underscore_invalid() { 692 fn test_rename_to_underscore_invalid() {
641 mark::check!(rename_underscore_multiple); 693 cov_mark::check!(rename_underscore_multiple);
642 check( 694 check(
643 "_", 695 "_",
644 r#"fn main(foo$0: ()) {foo;}"#, 696 r#"fn main(foo$0: ()) {foo;}"#,
@@ -657,7 +709,7 @@ mod tests {
657 709
658 #[test] 710 #[test]
659 fn test_rename_for_local() { 711 fn test_rename_for_local() {
660 mark::check!(rename_ident); 712 cov_mark::check!(rename_ident);
661 check( 713 check(
662 "k", 714 "k",
663 r#" 715 r#"
@@ -820,7 +872,7 @@ impl Foo {
820 872
821 #[test] 873 #[test]
822 fn test_rename_field_in_field_shorthand() { 874 fn test_rename_field_in_field_shorthand() {
823 mark::check!(test_rename_field_in_field_shorthand); 875 cov_mark::check!(test_rename_field_in_field_shorthand);
824 check( 876 check(
825 "j", 877 "j",
826 r#" 878 r#"
@@ -846,7 +898,7 @@ impl Foo {
846 898
847 #[test] 899 #[test]
848 fn test_rename_local_in_field_shorthand() { 900 fn test_rename_local_in_field_shorthand() {
849 mark::check!(test_rename_local_in_field_shorthand); 901 cov_mark::check!(test_rename_local_in_field_shorthand);
850 check( 902 check(
851 "j", 903 "j",
852 r#" 904 r#"
@@ -1252,7 +1304,7 @@ fn foo(f: foo::Foo) {
1252 1304
1253 #[test] 1305 #[test]
1254 fn test_parameter_to_self() { 1306 fn test_parameter_to_self() {
1255 mark::check!(rename_to_self); 1307 cov_mark::check!(rename_to_self);
1256 check( 1308 check(
1257 "self", 1309 "self",
1258 r#" 1310 r#"
@@ -1392,7 +1444,7 @@ impl Foo {
1392 1444
1393 #[test] 1445 #[test]
1394 fn test_owned_self_to_parameter() { 1446 fn test_owned_self_to_parameter() {
1395 mark::check!(rename_self_to_param); 1447 cov_mark::check!(rename_self_to_param);
1396 check( 1448 check(
1397 "foo", 1449 "foo",
1398 r#" 1450 r#"
@@ -1445,7 +1497,7 @@ impl Foo {
1445 1497
1446 #[test] 1498 #[test]
1447 fn test_rename_field_put_init_shorthand() { 1499 fn test_rename_field_put_init_shorthand() {
1448 mark::check!(test_rename_field_put_init_shorthand); 1500 cov_mark::check!(test_rename_field_put_init_shorthand);
1449 check( 1501 check(
1450 "bar", 1502 "bar",
1451 r#" 1503 r#"
@@ -1467,7 +1519,7 @@ fn foo(bar: i32) -> Foo {
1467 1519
1468 #[test] 1520 #[test]
1469 fn test_rename_local_put_init_shorthand() { 1521 fn test_rename_local_put_init_shorthand() {
1470 mark::check!(test_rename_local_put_init_shorthand); 1522 cov_mark::check!(test_rename_local_put_init_shorthand);
1471 check( 1523 check(
1472 "i", 1524 "i",
1473 r#" 1525 r#"
@@ -1489,7 +1541,7 @@ fn foo(i: i32) -> Foo {
1489 1541
1490 #[test] 1542 #[test]
1491 fn test_struct_field_pat_into_shorthand() { 1543 fn test_struct_field_pat_into_shorthand() {
1492 mark::check!(test_rename_field_put_init_shorthand_pat); 1544 cov_mark::check!(test_rename_field_put_init_shorthand_pat);
1493 check( 1545 check(
1494 "baz", 1546 "baz",
1495 r#" 1547 r#"
@@ -1601,7 +1653,7 @@ fn foo(foo: Foo) {
1601 1653
1602 #[test] 1654 #[test]
1603 fn test_rename_lifetimes() { 1655 fn test_rename_lifetimes() {
1604 mark::check!(rename_lifetime); 1656 cov_mark::check!(rename_lifetime);
1605 check( 1657 check(
1606 "'yeeee", 1658 "'yeeee",
1607 r#" 1659 r#"
@@ -1689,7 +1741,7 @@ fn foo<'a>() -> &'a () {
1689 1741
1690 #[test] 1742 #[test]
1691 fn test_self_to_self() { 1743 fn test_self_to_self() {
1692 mark::check!(rename_self_to_self); 1744 cov_mark::check!(rename_self_to_self);
1693 check( 1745 check(
1694 "self", 1746 "self",
1695 r#" 1747 r#"