File is large — showing lines 1–2,000 of 3,934.
1//! Renaming functionality.2//!3//! This is mostly front-end for [`ide_db::rename`], but it also includes the4//! tests. This module also implements a couple of magic tricks, like renaming5//! `self` and to `self` (to switch between associated function and method).67use hir::{AsAssocItem, FindPathConfig, HasContainer, HirDisplay, InFile, Name, Semantics, sym};8use ide_db::{9 FileId, FileRange, RootDatabase,10 defs::{Definition, NameClass, NameRefClass},11 rename::{IdentifierKind, RenameDefinition, bail, format_err, source_edit_from_references},12 source_change::SourceChangeBuilder,13};14use itertools::Itertools;15use std::fmt::Write;16use stdx::{always, format_to, never};17use syntax::{18 AstNode, SyntaxKind, SyntaxNode, TextRange, TextSize,19 ast::{self, HasArgList, prec::ExprPrecedence},20};2122use ide_db::text_edit::TextEdit;2324use crate::{FilePosition, RangeInfo, SourceChange};2526pub use ide_db::rename::RenameError;2728type RenameResult<T> = Result<T, RenameError>;2930pub struct RenameConfig {31 pub prefer_no_std: bool,32 pub prefer_prelude: bool,33 pub prefer_absolute: bool,34 pub show_conflicts: bool,35}3637impl RenameConfig {38 fn find_path_config(&self) -> FindPathConfig {39 FindPathConfig {40 prefer_no_std: self.prefer_no_std,41 prefer_prelude: self.prefer_prelude,42 prefer_absolute: self.prefer_absolute,43 allow_unstable: true,44 }45 }4647 fn ide_db_config(&self) -> ide_db::rename::RenameConfig {48 ide_db::rename::RenameConfig { show_conflicts: self.show_conflicts }49 }50}5152/// This is similar to `collect::<Result<Vec<_>, _>>`, but unlike it, it succeeds if there is *any* `Ok` item.53fn ok_if_any<T, E>(iter: impl Iterator<Item = Result<T, E>>) -> Result<Vec<T>, E> {54 let mut err = None;55 let oks = iter56 .filter_map(|item| match item {57 Ok(it) => Some(it),58 Err(it) => {59 err = Some(it);60 None61 }62 })63 .collect::<Vec<_>>();64 if !oks.is_empty() {65 Ok(oks)66 } else if let Some(err) = err {67 Err(err)68 } else {69 Ok(Vec::new())70 }71}7273/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is74/// being targeted for a rename.75pub(crate) fn prepare_rename(76 db: &RootDatabase,77 position: FilePosition,78) -> RenameResult<RangeInfo<()>> {79 let sema = Semantics::new(db);80 let source_file = sema.parse_guess_edition(position.file_id);81 let syntax = source_file.syntax();8283 let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))?84 .filter(|(_, _, def, _, _)| def.range_for_rename(&sema).is_some())85 .map(|(frange, kind, _, _, _)| {86 always!(87 frange.range.contains_inclusive(position.offset)88 && frange.file_id == position.file_id89 );9091 Ok(match kind {92 SyntaxKind::LIFETIME => {93 TextRange::new(frange.range.start() + TextSize::from(1), frange.range.end())94 }95 _ => frange.range,96 })97 })98 .reduce(|acc, cur| match (acc, cur) {99 // ensure all ranges are the same100 (Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),101 (e @ Err(_), _) | (_, e @ Err(_)) => e,102 _ => bail!("inconsistent text range"),103 });104105 match res {106 // ensure at least one definition was found107 Some(res) => res.map(|range| RangeInfo::new(range, ())),108 None => bail!("No references found at position"),109 }110}111112// Feature: Rename113//114// Renames the item below the cursor and all of its references115//116// | Editor | Shortcut |117// |---------|----------|118// | VS Code | <kbd>F2</kbd> |119//120// 121pub(crate) fn rename(122 db: &RootDatabase,123 position: FilePosition,124 new_name: &str,125 config: &RenameConfig,126) -> RenameResult<SourceChange> {127 let sema = Semantics::new(db);128 let file_id = sema129 .attach_first_edition_opt(position.file_id)130 .ok_or_else(|| format_err!("No references found at position"))?;131 let source_file = sema.parse(file_id);132 let syntax = source_file.syntax();133134 let edition = file_id.edition(db);135 let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;136137 let defs = find_definitions(&sema, syntax, position, &new_name)?;138 let alias_fallback =139 alias_fallback(syntax, position, &new_name.display(db, edition).to_string());140141 let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {142 Some(_) => ok_if_any(143 defs144 // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can145 // properly find "direct" usages/references.146 .map(|(.., def, new_name, _)| {147 match kind {148 IdentifierKind::Ident => (),149 IdentifierKind::Lifetime => {150 bail!("Cannot alias reference to a lifetime identifier")151 }152 IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),153 IdentifierKind::LowercaseSelf => {154 bail!("Cannot rename alias reference to `self`")155 }156 };157 let mut usages = def.usages(&sema).all();158159 // FIXME: hack - removes the usage that triggered this rename operation.160 match usages.references.get_mut(&file_id).and_then(|refs| {161 refs.iter()162 .position(|ref_| ref_.range.contains_inclusive(position.offset))163 .map(|idx| refs.remove(idx))164 }) {165 Some(_) => (),166 None => never!(),167 };168169 let mut source_change = SourceChange::default();170 source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| {171 (172 position.file_id,173 source_edit_from_references(db, refs, def, &new_name, edition),174 )175 }));176177 Ok(source_change)178 }),179 ),180 None => ok_if_any(defs.map(|(.., def, new_name, rename_def)| {181 if let Definition::Local(local) = def {182 if let Some(self_param) = local.as_self_param(sema.db) {183 cov_mark::hit!(rename_self_to_param);184 return rename_self_to_param(185 &sema,186 local,187 self_param,188 &new_name,189 kind,190 config.find_path_config(),191 );192 }193 if kind == IdentifierKind::LowercaseSelf {194 cov_mark::hit!(rename_to_self);195 return rename_to_self(&sema, local);196 }197 }198 def.rename(&sema, new_name.as_str(), rename_def, &config.ide_db_config())199 })),200 };201202 ops?.into_iter()203 .chain(alias_fallback)204 .reduce(|acc, elem| acc.merge(elem))205 .ok_or_else(|| format_err!("No references found at position"))206}207208/// Called by the client when it is about to rename a file.209pub(crate) fn will_rename_file(210 db: &RootDatabase,211 file_id: FileId,212 new_name_stem: &str,213 config: &RenameConfig,214) -> Option<SourceChange> {215 let sema = Semantics::new(db);216 let module = sema.file_to_module_def(file_id)?;217 let def = Definition::Module(module);218 let mut change =219 def.rename(&sema, new_name_stem, RenameDefinition::Yes, &config.ide_db_config()).ok()?;220 change.file_system_edits.clear();221 Some(change)222}223224// FIXME: Should support `extern crate`.225fn alias_fallback(226 syntax: &SyntaxNode,227 FilePosition { file_id, offset }: FilePosition,228 new_name: &str,229) -> Option<SourceChange> {230 let use_tree = syntax231 .token_at_offset(offset)232 .flat_map(|syntax| syntax.parent_ancestors())233 .find_map(ast::UseTree::cast)?;234235 let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;236 if !last_path_segment.syntax().text_range().contains_inclusive(offset) {237 return None;238 };239240 let mut builder = SourceChangeBuilder::new(file_id);241242 match use_tree.rename() {243 Some(rename) => {244 let offset = rename.syntax().text_range();245 builder.replace(offset, format!("as {new_name}"));246 }247 None => {248 let offset = use_tree.syntax().text_range().end();249 builder.insert(offset, format!(" as {new_name}"));250 }251 }252253 Some(builder.finish())254}255256fn find_definitions(257 sema: &Semantics<'_, RootDatabase>,258 syntax: &SyntaxNode,259 FilePosition { file_id, offset }: FilePosition,260 new_name: &Name,261) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition, Name, RenameDefinition)>>262{263 let maybe_format_args =264 syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));265266 if let Some((range, _, _, Some(resolution))) =267 maybe_format_args.and_then(|token| sema.check_for_format_args_template(token, offset))268 {269 return Ok(vec![(270 FileRange { file_id, range },271 SyntaxKind::STRING,272 Definition::from(resolution),273 new_name.clone(),274 RenameDefinition::Yes,275 )]276 .into_iter());277 }278279 let original_ident = syntax280 .token_at_offset(offset)281 .max_by_key(|t| {282 t.kind().is_any_identifier() || matches!(t.kind(), SyntaxKind::LIFETIME_IDENT)283 })284 .map(|t| {285 if t.kind() == SyntaxKind::LIFETIME_IDENT {286 Name::new_lifetime(t.text())287 } else {288 Name::new_root(t.text())289 }290 })291 .ok_or_else(|| format_err!("No references found at position"))?;292 let symbols =293 sema.find_namelike_at_offset_with_descend(syntax, offset).map(|name_like| {294 let kind = name_like.syntax().kind();295 let range = sema296 .original_range_opt(name_like.syntax())297 .ok_or_else(|| format_err!("No references found at position"))?;298 let res = match &name_like {299 // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet300 ast::NameLike::Name(name)301 if name302 .syntax()303 .parent().is_some_and(|it| ast::Rename::can_cast(it.kind()))304 // FIXME: uncomment this once we resolve to usages to extern crate declarations305 // && name306 // .syntax()307 // .ancestors()308 // .nth(2)309 // .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind()))310 =>311 {312 bail!("Renaming aliases is currently unsupported")313 }314 ast::NameLike::Name(name) => NameClass::classify(sema, name)315 .map(|class| match class {316 NameClass::Definition(it) | NameClass::ConstReference(it) => it,317 NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {318 Definition::Local(local_def)319 }320 })321 .ok_or_else(|| format_err!("No references found at position")),322 ast::NameLike::NameRef(name_ref) => {323 NameRefClass::classify(sema, name_ref)324 .map(|class| match class {325 NameRefClass::Definition(def, _) => def,326 NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ } => {327 Definition::Local(local_ref)328 }329 NameRefClass::ExternCrateShorthand { decl, .. } => {330 Definition::ExternCrateDecl(decl)331 }332 })333 // FIXME: uncomment this once we resolve to usages to extern crate declarations334 .filter(|def| !matches!(def, Definition::ExternCrateDecl(..)))335 .ok_or_else(|| format_err!("No references found at position"))336 .and_then(|def| {337 // if the name differs from the definitions name it has to be an alias338 if def339 .name(sema.db).is_some_and(|it| it.as_str() != name_ref.text().trim_start_matches("r#"))340 {341 Err(format_err!("Renaming aliases is currently unsupported"))342 } else {343 Ok(def)344 }345 })346 }347 ast::NameLike::Lifetime(lifetime) => {348 NameRefClass::classify_lifetime(sema, lifetime)349 .and_then(|class| match class {350 NameRefClass::Definition(def, _) => Some(def),351 _ => None,352 })353 .or_else(|| {354 NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {355 NameClass::Definition(it) => Some(it),356 _ => None,357 })358 })359 .ok_or_else(|| format_err!("No references found at position"))360 }361 };362 res.map(|def| {363 let n = def.name(sema.db)?;364 if n == original_ident {365 Some((range, kind, def, new_name.clone(), RenameDefinition::Yes))366 } else if let Some(suffix) = n.as_str().strip_prefix(original_ident.as_str()) {367 Some((range, kind, def, Name::new_root(&format!("{}{suffix}", new_name.as_str())), RenameDefinition::No))368 } else {369 n.as_str().strip_suffix(original_ident.as_str().trim_start_matches('\''))370 .map(|prefix| (range, kind, def, Name::new_root(&format!("{prefix}{}", new_name.as_str())), RenameDefinition::No))371 }372 })373 });374375 let res: RenameResult<Vec<_>> = ok_if_any(symbols.filter_map(Result::transpose));376 match res {377 Ok(v) => {378 // remove duplicates, comparing `Definition`s379 Ok(v.into_iter()380 .unique_by(|&(.., def, _, _)| def)381 .map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e))382 .collect::<Vec<_>>()383 .into_iter())384 }385 Err(e) => Err(e),386 }387}388389fn transform_assoc_fn_into_method_call(390 sema: &Semantics<'_, RootDatabase>,391 source_change: &mut SourceChange,392 f: hir::Function,393) {394 let calls = Definition::Function(f).usages(sema).all();395 for (_file_id, calls) in calls {396 for call in calls {397 let Some(fn_name) = call.name.as_name_ref() else { continue };398 let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {399 continue;400 };401 let path = path.parent_path();402 // The `PathExpr` is the direct parent, above it is the `CallExpr`.403 let Some(call) =404 path.syntax().parent().and_then(|it| ast::CallExpr::cast(it.parent()?))405 else {406 continue;407 };408409 let Some(arg_list) = call.arg_list() else { continue };410 let mut args = arg_list.args();411 let Some(mut self_arg) = args.next() else { continue };412 let second_arg = args.next();413414 // Strip (de)references, as they will be taken automatically by auto(de)ref.415 loop {416 let self_ = match &self_arg {417 ast::Expr::RefExpr(self_) => self_.expr(),418 ast::Expr::ParenExpr(self_) => self_.expr(),419 ast::Expr::PrefixExpr(self_)420 if self_.op_kind() == Some(ast::UnaryOp::Deref) =>421 {422 self_.expr()423 }424 _ => break,425 };426 self_arg = match self_ {427 Some(it) => it,428 None => break,429 };430 }431432 let self_needs_parens =433 self_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix);434435 let replace_start = path.syntax().text_range().start();436 let replace_end = match second_arg {437 Some(second_arg) => second_arg.syntax().text_range().start(),438 None => arg_list439 .r_paren_token()440 .map(|it| it.text_range().start())441 .unwrap_or_else(|| arg_list.syntax().text_range().end()),442 };443 let replace_range = TextRange::new(replace_start, replace_end);444 let macro_file = sema.hir_file_for(fn_name.syntax());445 let Some((replace_range, _)) =446 InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)447 else {448 continue;449 };450451 let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {452 continue;453 };454 let mut replacement = String::new();455 if self_needs_parens {456 replacement.push('(');457 }458 replacement.push_str(macro_mapped_self.text(sema.db));459 if self_needs_parens {460 replacement.push(')');461 }462 replacement.push('.');463 format_to!(replacement, "{fn_name}");464 replacement.push('(');465466 source_change.insert_source_edit(467 replace_range.file_id.file_id(sema.db),468 TextEdit::replace(replace_range.range, replacement),469 );470 }471 }472}473474fn rename_to_self(475 sema: &Semantics<'_, RootDatabase>,476 local: hir::Local,477) -> RenameResult<SourceChange> {478 if never!(local.is_self(sema.db)) {479 bail!("rename_to_self invoked on self");480 }481482 let fn_def = match local.parent(sema.db) {483 hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,484 _ => bail!("Cannot rename local to self outside of function"),485 };486487 if fn_def.self_param(sema.db).is_some() {488 bail!("Method already has a self parameter");489 }490491 let params = fn_def.assoc_fn_params(sema.db);492 let first_param = params493 .first()494 .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?;495 match first_param.as_local(sema.db) {496 Some(plocal) => {497 if plocal != local {498 bail!("Only the first parameter may be renamed to self");499 }500 }501 None => bail!("rename_to_self invoked on destructuring parameter"),502 }503504 let assoc_item = fn_def505 .as_assoc_item(sema.db)506 .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?;507 let impl_ = match assoc_item.container(sema.db) {508 hir::AssocItemContainer::Trait(_) => {509 bail!("Cannot rename parameter to self for trait functions");510 }511 hir::AssocItemContainer::Impl(impl_) => impl_,512 };513 let first_param_ty = first_param.ty();514 let impl_ty = impl_.self_ty(sema.db);515 let (ty, self_param) = if impl_ty.remove_ref().is_some() {516 // if the impl is a ref to the type we can just match the `&T` with self directly517 (first_param_ty.clone(), "self")518 } else {519 first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| {520 (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })521 })522 };523524 if ty != impl_ty {525 bail!("Parameter type differs from impl block type");526 }527528 let InFile { file_id, value: param_source } = sema529 .source(first_param.clone())530 .ok_or_else(|| format_err!("No source for parameter found"))?;531532 let def = Definition::Local(local);533 let usages = def.usages(sema).all();534 let mut source_change = SourceChange::default();535 source_change.extend(usages.iter().map(|(file_id, references)| {536 (537 file_id.file_id(sema.db),538 source_edit_from_references(539 sema.db,540 references,541 def,542 &Name::new_symbol_root(sym::self_),543 file_id.edition(sema.db),544 ),545 )546 }));547 source_change.insert_source_edit(548 file_id.original_file(sema.db).file_id(sema.db),549 TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),550 );551 transform_assoc_fn_into_method_call(sema, &mut source_change, fn_def);552 Ok(source_change)553}554555#[derive(Debug, Clone, Copy, PartialEq, Eq)]556enum CallReceiverAdjust {557 Deref,558 Ref,559 RefMut,560 None,561}562563fn method_to_assoc_fn_call_self_adjust(564 sema: &Semantics<'_, RootDatabase>,565 self_arg: &ast::Expr,566) -> CallReceiverAdjust {567 let mut result = CallReceiverAdjust::None;568 let self_adjust = sema.expr_adjustments(self_arg);569 if let Some(self_adjust) = self_adjust {570 let mut i = 0;571 while i < self_adjust.len() {572 if matches!(self_adjust[i].kind, hir::Adjust::Deref(..))573 && matches!(574 self_adjust.get(i + 1),575 Some(hir::Adjustment { kind: hir::Adjust::Borrow(..), .. })576 )577 {578 // Deref then ref (reborrow), skip them.579 i += 2;580 continue;581 }582583 match self_adjust[i].kind {584 hir::Adjust::Deref(_) if result == CallReceiverAdjust::None => {585 // Autoref takes precedence over deref, because if given a `&Type` the compiler will deref586 // it automatically.587 result = CallReceiverAdjust::Deref;588 }589 hir::Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => {590 match (result, mutability) {591 (CallReceiverAdjust::RefMut, hir::Mutability::Shared) => {}592 (_, hir::Mutability::Mut) => result = CallReceiverAdjust::RefMut,593 (_, hir::Mutability::Shared) => result = CallReceiverAdjust::Ref,594 }595 }596 _ => {}597 }598599 i += 1;600 }601 }602 result603}604605fn transform_method_call_into_assoc_fn(606 sema: &Semantics<'_, RootDatabase>,607 source_change: &mut SourceChange,608 f: hir::Function,609 find_path_config: FindPathConfig,610) {611 let calls = Definition::Function(f).usages(sema).all();612 for (_file_id, calls) in calls {613 for call in calls {614 let Some(fn_name) = call.name.as_name_ref() else { continue };615 let Some(method_call) = fn_name.syntax().parent().and_then(ast::MethodCallExpr::cast)616 else {617 continue;618 };619 let Some(mut self_arg) = method_call.receiver() else {620 continue;621 };622623 let Some(scope) = sema.scope(fn_name.syntax()) else {624 continue;625 };626 let self_adjust = method_to_assoc_fn_call_self_adjust(sema, &self_arg);627628 // Strip parentheses, function arguments have higher precedence than any operator.629 while let ast::Expr::ParenExpr(it) = &self_arg {630 self_arg = match it.expr() {631 Some(it) => it,632 None => break,633 };634 }635636 let needs_comma = method_call.arg_list().is_some_and(|it| it.args().next().is_some());637638 let self_needs_parens = self_adjust != CallReceiverAdjust::None639 && self_arg.precedence().needs_parentheses_in(ExprPrecedence::Prefix);640641 let replace_start = method_call.syntax().text_range().start();642 let replace_end = method_call643 .arg_list()644 .and_then(|it| it.l_paren_token())645 .map(|it| it.text_range().end())646 .unwrap_or_else(|| method_call.syntax().text_range().end());647 let replace_range = TextRange::new(replace_start, replace_end);648 let macro_file = sema.hir_file_for(fn_name.syntax());649 let Some((replace_range, _)) =650 InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)651 else {652 continue;653 };654655 let fn_container_path = match f.container(sema.db) {656 hir::ItemContainer::Trait(trait_) => {657 // FIXME: We always put it as `Trait::function`. Is it better to use `Type::function` (but658 // that could conflict with an inherent method)? Or maybe `<Type as Trait>::function`?659 // Or let the user decide?660 let Some(path) = scope.module().find_path(661 sema.db,662 hir::ItemInNs::Types(trait_.into()),663 find_path_config,664 ) else {665 continue;666 };667 path.display(sema.db, replace_range.file_id.edition(sema.db)).to_string()668 }669 hir::ItemContainer::Impl(impl_) => {670 let ty = impl_.self_ty(sema.db);671 match ty.as_adt() {672 Some(adt) => {673 let Some(path) = scope.module().find_path(674 sema.db,675 hir::ItemInNs::Types(adt.into()),676 find_path_config,677 ) else {678 continue;679 };680 path.display(sema.db, replace_range.file_id.edition(sema.db))681 .to_string()682 }683 None => {684 let Ok(mut ty) =685 ty.display_source_code(sema.db, scope.module().into(), false)686 else {687 continue;688 };689 ty.insert(0, '<');690 ty.push('>');691 ty692 }693 }694 }695 _ => continue,696 };697698 let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {699 continue;700 };701 let mut replacement = String::new();702 replacement.push_str(&fn_container_path);703 replacement.push_str("::");704 format_to!(replacement, "{fn_name}");705 replacement.push('(');706 replacement.push_str(match self_adjust {707 CallReceiverAdjust::Deref => "*",708 CallReceiverAdjust::Ref => "&",709 CallReceiverAdjust::RefMut => "&mut ",710 CallReceiverAdjust::None => "",711 });712 if self_needs_parens {713 replacement.push('(');714 }715 replacement.push_str(macro_mapped_self.text(sema.db));716 if self_needs_parens {717 replacement.push(')');718 }719 if needs_comma {720 replacement.push_str(", ");721 }722723 source_change.insert_source_edit(724 replace_range.file_id.file_id(sema.db),725 TextEdit::replace(replace_range.range, replacement),726 );727 }728 }729}730731fn rename_self_to_param(732 sema: &Semantics<'_, RootDatabase>,733 local: hir::Local,734 self_param: hir::SelfParam,735 new_name: &Name,736 identifier_kind: IdentifierKind,737 find_path_config: FindPathConfig,738) -> RenameResult<SourceChange> {739 if identifier_kind == IdentifierKind::LowercaseSelf {740 // Let's do nothing rather than complain.741 cov_mark::hit!(rename_self_to_self);742 return Ok(SourceChange::default());743 }744745 let fn_def = match local.parent(sema.db) {746 hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,747 _ => bail!("Cannot rename local to self outside of function"),748 };749750 let InFile { file_id, value: self_param } =751 sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?;752753 let def = Definition::Local(local);754 let usages = def.usages(sema).all();755 let edit = text_edit_from_self_param(756 &self_param,757 new_name.display(sema.db, file_id.edition(sema.db)).to_string(),758 )759 .ok_or_else(|| format_err!("No target type found"))?;760 if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {761 bail!("Cannot rename reference to `_` as it is being referenced multiple times");762 }763 let mut source_change = SourceChange::default();764 source_change.insert_source_edit(file_id.original_file(sema.db).file_id(sema.db), edit);765 source_change.extend(usages.iter().map(|(file_id, references)| {766 (767 file_id.file_id(sema.db),768 source_edit_from_references(769 sema.db,770 references,771 def,772 new_name,773 file_id.edition(sema.db),774 ),775 )776 }));777 transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config);778 Ok(source_change)779}780781fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {782 let mut replacement_text = new_name;783 replacement_text.push_str(": ");784785 if self_param.amp_token().is_some() {786 replacement_text.push('&');787 }788 if let Some(lifetime) = self_param.lifetime() {789 write!(replacement_text, "{lifetime} ").unwrap();790 }791 if self_param.amp_token().and(self_param.mut_token()).is_some() {792 replacement_text.push_str("mut ");793 }794795 replacement_text.push_str("Self");796797 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))798}799800#[cfg(test)]801mod tests {802 use expect_test::{Expect, expect};803 use ide_db::source_change::SourceChange;804 use ide_db::text_edit::TextEdit;805 use itertools::Itertools;806 use stdx::trim_indent;807 use test_utils::assert_eq_text;808809 use crate::fixture;810811 use super::{RangeInfo, RenameConfig, RenameError};812813 const TEST_CONFIG: RenameConfig = RenameConfig {814 prefer_no_std: false,815 prefer_prelude: true,816 prefer_absolute: false,817 show_conflicts: true,818 };819820 #[track_caller]821 fn check(822 new_name: &str,823 #[rust_analyzer::rust_fixture] ra_fixture_before: &str,824 #[rust_analyzer::rust_fixture] ra_fixture_after: &str,825 ) {826 let ra_fixture_after = &trim_indent(ra_fixture_after);827 let (analysis, position) = fixture::position(ra_fixture_before);828 if !ra_fixture_after.starts_with("error: ")829 && let Err(err) = analysis.prepare_rename(position).unwrap()830 {831 panic!("Prepare rename to '{new_name}' was failed: {err}")832 }833 let rename_result = analysis834 .rename(position, new_name, &TEST_CONFIG)835 .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));836 match rename_result {837 Ok(source_change) => {838 let mut text_edit_builder = TextEdit::builder();839 let (&file_id, edit) = match source_change.source_file_edits.len() {840 0 => return,841 1 => source_change.source_file_edits.iter().next().unwrap(),842 _ => panic!(),843 };844 for indel in edit.0.iter() {845 text_edit_builder.replace(indel.delete, indel.insert.clone());846 }847 let mut result = analysis.file_text(file_id).unwrap().to_string();848 text_edit_builder.finish().apply(&mut result);849 assert_eq_text!(ra_fixture_after, &*result);850 }851 Err(err) => {852 if ra_fixture_after.starts_with("error:") {853 let error_message =854 ra_fixture_after.chars().skip("error:".len()).collect::<String>();855 assert_eq!(error_message.trim(), err.to_string());856 } else {857 panic!("Rename to '{new_name}' failed unexpectedly: {err}")858 }859 }860 };861 }862863 #[track_caller]864 fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) {865 let (analysis, position, conflicts) = fixture::annotations(ra_fixture);866 let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap();867 let expected_conflicts = conflicts868 .into_iter()869 .map(|(file_range, _)| (file_range.file_id, file_range.range))870 .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))871 .collect_vec();872 let found_conflicts = source_change873 .source_file_edits874 .iter()875 .filter(|(_, (edit, _))| edit.change_annotation().is_some())876 .flat_map(|(file_id, (edit, _))| {877 edit.into_iter().map(move |edit| (*file_id, edit.delete))878 })879 .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))880 .collect_vec();881 assert_eq!(882 expected_conflicts, found_conflicts,883 "rename conflicts mismatch: {source_change:#?}"884 );885 }886887 fn check_expect(888 new_name: &str,889 #[rust_analyzer::rust_fixture] ra_fixture: &str,890 expect: Expect,891 ) {892 let (analysis, position) = fixture::position(ra_fixture);893 let source_change = analysis894 .rename(position, new_name, &TEST_CONFIG)895 .unwrap()896 .expect("Expect returned a RenameError");897 expect.assert_eq(&filter_expect(source_change))898 }899900 fn check_expect_will_rename_file(901 new_name: &str,902 #[rust_analyzer::rust_fixture] ra_fixture: &str,903 expect: Expect,904 ) {905 let (analysis, position) = fixture::position(ra_fixture);906 let source_change = analysis907 .will_rename_file(position.file_id, new_name, &TEST_CONFIG)908 .unwrap()909 .expect("Expect returned a RenameError");910 expect.assert_eq(&filter_expect(source_change))911 }912913 fn check_prepare(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {914 let (analysis, position) = fixture::position(ra_fixture);915 let result = analysis916 .prepare_rename(position)917 .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {err}"));918 match result {919 Ok(RangeInfo { range, info: () }) => {920 let source = analysis.file_text(position.file_id).unwrap();921 expect.assert_eq(&format!("{range:?}: {}", &source[range]))922 }923 Err(RenameError(err)) => expect.assert_eq(&err),924 };925 }926927 fn filter_expect(source_change: SourceChange) -> String {928 let source_file_edits = source_change929 .source_file_edits930 .into_iter()931 .map(|(id, (text_edit, _))| (id, text_edit.into_iter().collect::<Vec<_>>()))932 .collect::<Vec<_>>();933934 format!(935 "source_file_edits: {:#?}\nfile_system_edits: {:#?}\n",936 source_file_edits, source_change.file_system_edits937 )938 }939940 #[test]941 fn rename_will_shadow() {942 check_conflicts(943 "new_name",944 r#"945fn foo() {946 let mut new_name = 123;947 let old_name$0 = 456;948 // ^^^^^^^^949 new_name = 789 + new_name;950}951 "#,952 );953 }954955 #[test]956 fn rename_will_be_shadowed() {957 check_conflicts(958 "new_name",959 r#"960fn foo() {961 let mut old_name$0 = 456;962 // ^^^^^^^^963 let new_name = 123;964 old_name = 789 + old_name;965 // ^^^^^^^^ ^^^^^^^^966}967 "#,968 );969 }970971 #[test]972 fn test_prepare_rename_namelikes() {973 check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);974 check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"9..17: lifetime"#]]);975 check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);976 }977978 #[test]979 fn test_prepare_rename_in_macro() {980 check_prepare(981 r"macro_rules! foo {982 ($ident:ident) => {983 pub struct $ident;984 }985}986foo!(Foo$0);",987 expect![[r#"83..86: Foo"#]],988 );989 }990991 #[test]992 fn test_prepare_rename_keyword() {993 check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);994 }995996 #[test]997 fn test_prepare_rename_tuple_field() {998 check_prepare(999 r#"1000struct Foo(i32);10011002fn baz() {1003 let mut x = Foo(4);1004 x.0$0 = 5;1005}1006"#,1007 expect![[r#"No references found at position"#]],1008 );1009 }10101011 #[test]1012 fn test_prepare_rename_builtin() {1013 check_prepare(1014 r#"1015fn foo() {1016 let x: i32$0 = 0;1017}1018"#,1019 expect![[r#"No references found at position"#]],1020 );1021 }10221023 #[test]1024 fn test_prepare_rename_self() {1025 check_prepare(1026 r#"1027struct Foo {}10281029impl Foo {1030 fn foo(self) -> Self$0 {1031 self1032 }1033}1034"#,1035 expect![[r#"No references found at position"#]],1036 );1037 }10381039 #[test]1040 fn test_rename_to_underscore() {1041 check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);1042 }10431044 #[test]1045 fn test_rename_to_raw_identifier() {1046 check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);1047 }10481049 #[test]1050 fn test_rename_to_invalid_identifier1() {1051 check(1052 "invalid!",1053 r#"fn main() { let i$0 = 1; }"#,1054 "error: Invalid name `invalid!`: not an identifier",1055 );1056 }10571058 #[test]1059 fn test_rename_to_invalid_identifier2() {1060 check(1061 "multiple tokens",1062 r#"fn main() { let i$0 = 1; }"#,1063 "error: Invalid name `multiple tokens`: not an identifier",1064 );1065 }10661067 #[test]1068 fn test_rename_to_invalid_identifier3() {1069 check(1070 "super",1071 r#"fn main() { let i$0 = 1; }"#,1072 "error: Invalid name `super`: cannot rename to a keyword",1073 );1074 }10751076 #[test]1077 fn test_rename_to_invalid_identifier_lifetime() {1078 cov_mark::check!(rename_not_an_ident_ref);1079 check(1080 "'foo",1081 r#"fn main() { let i$0 = 1; }"#,1082 "error: Invalid name `'foo`: not an identifier",1083 );1084 }10851086 #[test]1087 fn test_rename_to_invalid_identifier_lifetime2() {1088 check(1089 "_",1090 r#"fn main<'a>(_: &'a$0 ()) {}"#,1091 r#"error: Invalid name `_`: not a lifetime identifier"#,1092 );1093 }10941095 #[test]1096 fn test_rename_accepts_lifetime_without_apostrophe() {1097 check("foo", r#"fn main<'a>(_: &'a$0 ()) {}"#, r#"fn main<'foo>(_: &'foo ()) {}"#);1098 }10991100 #[test]1101 fn test_rename_to_underscore_invalid() {1102 cov_mark::check!(rename_underscore_multiple);1103 check(1104 "_",1105 r#"fn main(foo$0: ()) {foo;}"#,1106 "error: Cannot rename reference to `_` as it is being referenced multiple times",1107 );1108 }11091110 #[test]1111 fn test_rename_mod_invalid() {1112 check(1113 "'foo",1114 r#"mod foo$0 {}"#,1115 "error: Invalid name `'foo`: cannot rename module to 'foo",1116 );1117 }11181119 #[test]1120 fn test_rename_mod_invalid_raw_ident() {1121 check(1122 "r#self",1123 r#"mod foo$0 {}"#,1124 "error: Invalid name `self`: cannot rename module to self",1125 );1126 }11271128 #[test]1129 fn test_rename_for_local() {1130 check(1131 "k",1132 r#"1133fn main() {1134 let mut i = 1;1135 let j = 1;1136 i = i$0 + j;11371138 { i = 0; }11391140 i = 5;1141}1142"#,1143 r#"1144fn main() {1145 let mut k = 1;1146 let j = 1;1147 k = k + j;11481149 { k = 0; }11501151 k = 5;1152}1153"#,1154 );1155 }11561157 #[test]1158 fn test_rename_unresolved_reference() {1159 check(1160 "new_name",1161 r#"fn main() { let _ = unresolved_ref$0; }"#,1162 "error: No references found at position",1163 );1164 }11651166 #[test]1167 fn test_rename_macro_multiple_occurrences() {1168 check(1169 "Baaah",1170 r#"macro_rules! foo {1171 ($ident:ident) => {1172 const $ident: () = ();1173 struct $ident {}1174 };1175}11761177foo!($0Foo);1178const _: () = Foo;1179const _: Foo = Foo {};1180 "#,1181 r#"1182macro_rules! foo {1183 ($ident:ident) => {1184 const $ident: () = ();1185 struct $ident {}1186 };1187}11881189foo!(Baaah);1190const _: () = Baaah;1191const _: Baaah = Baaah {};1192 "#,1193 )1194 }11951196 #[test]1197 fn test_rename_for_macro_args() {1198 check(1199 "b",1200 r#"1201macro_rules! foo {($i:ident) => {$i} }1202fn main() {1203 let a$0 = "test";1204 foo!(a);1205}1206"#,1207 r#"1208macro_rules! foo {($i:ident) => {$i} }1209fn main() {1210 let b = "test";1211 foo!(b);1212}1213"#,1214 );1215 }12161217 #[test]1218 fn test_rename_for_macro_args_rev() {1219 check(1220 "b",1221 r#"1222macro_rules! foo {($i:ident) => {$i} }1223fn main() {1224 let a = "test";1225 foo!(a$0);1226}1227"#,1228 r#"1229macro_rules! foo {($i:ident) => {$i} }1230fn main() {1231 let b = "test";1232 foo!(b);1233}1234"#,1235 );1236 }12371238 #[test]1239 fn test_rename_for_macro_define_fn() {1240 check(1241 "bar",1242 r#"1243macro_rules! define_fn {($id:ident) => { fn $id{} }}1244define_fn!(foo);1245fn main() {1246 fo$0o();1247}1248"#,1249 r#"1250macro_rules! define_fn {($id:ident) => { fn $id{} }}1251define_fn!(bar);1252fn main() {1253 bar();1254}1255"#,1256 );1257 }12581259 #[test]1260 fn test_rename_for_macro_define_fn_rev() {1261 check(1262 "bar",1263 r#"1264macro_rules! define_fn {($id:ident) => { fn $id{} }}1265define_fn!(fo$0o);1266fn main() {1267 foo();1268}1269"#,1270 r#"1271macro_rules! define_fn {($id:ident) => { fn $id{} }}1272define_fn!(bar);1273fn main() {1274 bar();1275}1276"#,1277 );1278 }12791280 #[test]1281 fn test_rename_for_param_inside() {1282 check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);1283 }12841285 #[test]1286 fn test_rename_refs_for_fn_param() {1287 check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);1288 }12891290 #[test]1291 fn test_rename_for_mut_param() {1292 check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);1293 }12941295 #[test]1296 fn test_rename_struct_field() {1297 check(1298 "foo",1299 r#"1300struct Foo { field$0: i32 }13011302impl Foo {1303 fn new(i: i32) -> Self {1304 Self { field: i }1305 }1306}1307"#,1308 r#"1309struct Foo { foo: i32 }13101311impl Foo {1312 fn new(i: i32) -> Self {1313 Self { foo: i }1314 }1315}1316"#,1317 );1318 }13191320 #[test]1321 fn test_rename_field_in_field_shorthand() {1322 cov_mark::check!(test_rename_field_in_field_shorthand);1323 check(1324 "field",1325 r#"1326struct Foo { foo$0: i32 }13271328impl Foo {1329 fn new(foo: i32) -> Self {1330 Self { foo }1331 }1332}1333"#,1334 r#"1335struct Foo { field: i32 }13361337impl Foo {1338 fn new(foo: i32) -> Self {1339 Self { field: foo }1340 }1341}1342"#,1343 );1344 }13451346 #[test]1347 fn test_rename_local_in_field_shorthand() {1348 cov_mark::check!(test_rename_local_in_field_shorthand);1349 check(1350 "j",1351 r#"1352struct Foo { i: i32 }13531354impl Foo {1355 fn new(i$0: i32) -> Self {1356 Self { i }1357 }1358}1359"#,1360 r#"1361struct Foo { i: i32 }13621363impl Foo {1364 fn new(j: i32) -> Self {1365 Self { i: j }1366 }1367}1368"#,1369 );1370 }13711372 #[test]1373 fn test_field_shorthand_correct_struct() {1374 check(1375 "j",1376 r#"1377struct Foo { i$0: i32 }1378struct Bar { i: i32 }13791380impl Bar {1381 fn new(i: i32) -> Self {1382 Self { i }1383 }1384}1385"#,1386 r#"1387struct Foo { j: i32 }1388struct Bar { i: i32 }13891390impl Bar {1391 fn new(i: i32) -> Self {1392 Self { i }1393 }1394}1395"#,1396 );1397 }13981399 #[test]1400 fn test_shadow_local_for_struct_shorthand() {1401 check(1402 "j",1403 r#"1404struct Foo { i: i32 }14051406fn baz(i$0: i32) -> Self {1407 let x = Foo { i };1408 {1409 let i = 0;1410 Foo { i }1411 }1412}1413"#,1414 r#"1415struct Foo { i: i32 }14161417fn baz(j: i32) -> Self {1418 let x = Foo { i: j };1419 {1420 let i = 0;1421 Foo { i }1422 }1423}1424"#,1425 );1426 }14271428 #[test]1429 fn test_rename_mod() {1430 check_expect(1431 "foo2",1432 r#"1433//- /lib.rs1434mod bar;14351436//- /bar.rs1437mod foo$0;14381439//- /bar/foo.rs1440// empty1441"#,1442 expect![[r#"1443 source_file_edits: [1444 (1445 FileId(1446 1,1447 ),1448 [1449 Indel {1450 insert: "foo2",1451 delete: 4..7,1452 },1453 ],1454 ),1455 ]1456 file_system_edits: [1457 MoveFile {1458 src: FileId(1459 2,1460 ),1461 dst: AnchoredPathBuf {1462 anchor: FileId(1463 2,1464 ),1465 path: "foo2.rs",1466 },1467 },1468 ]1469 "#]],1470 );1471 }14721473 #[test]1474 fn test_rename_mod_in_use_tree() {1475 check_expect(1476 "quux",1477 r#"1478//- /main.rs1479pub mod foo;1480pub mod bar;1481fn main() {}14821483//- /foo.rs1484pub struct FooContent;14851486//- /bar.rs1487use crate::foo$0::FooContent;1488"#,1489 expect![[r#"1490 source_file_edits: [1491 (1492 FileId(1493 0,1494 ),1495 [1496 Indel {1497 insert: "quux",1498 delete: 8..11,1499 },1500 ],1501 ),1502 (1503 FileId(1504 2,1505 ),1506 [1507 Indel {1508 insert: "quux",1509 delete: 11..14,1510 },1511 ],1512 ),1513 ]1514 file_system_edits: [1515 MoveFile {1516 src: FileId(1517 1,1518 ),1519 dst: AnchoredPathBuf {1520 anchor: FileId(1521 1,1522 ),1523 path: "quux.rs",1524 },1525 },1526 ]1527 "#]],1528 );1529 }15301531 #[test]1532 fn test_rename_mod_in_dir() {1533 check_expect(1534 "foo2",1535 r#"1536//- /lib.rs1537mod fo$0o;1538//- /foo/mod.rs1539// empty1540"#,1541 expect![[r#"1542 source_file_edits: [1543 (1544 FileId(1545 0,1546 ),1547 [1548 Indel {1549 insert: "foo2",1550 delete: 4..7,1551 },1552 ],1553 ),1554 ]1555 file_system_edits: [1556 MoveDir {1557 src: AnchoredPathBuf {1558 anchor: FileId(1559 1,1560 ),1561 path: "../foo",1562 },1563 src_id: FileId(1564 1,1565 ),1566 dst: AnchoredPathBuf {1567 anchor: FileId(1568 1,1569 ),1570 path: "../foo2",1571 },1572 },1573 ]1574 "#]],1575 );1576 }15771578 #[test]1579 fn test_rename_unusually_nested_mod() {1580 check_expect(1581 "bar",1582 r#"1583//- /lib.rs1584mod outer { mod fo$0o; }15851586//- /outer/foo.rs1587// empty1588"#,1589 expect![[r#"1590 source_file_edits: [1591 (1592 FileId(1593 0,1594 ),1595 [1596 Indel {1597 insert: "bar",1598 delete: 16..19,1599 },1600 ],1601 ),1602 ]1603 file_system_edits: [1604 MoveFile {1605 src: FileId(1606 1,1607 ),1608 dst: AnchoredPathBuf {1609 anchor: FileId(1610 1,1611 ),1612 path: "bar.rs",1613 },1614 },1615 ]1616 "#]],1617 );1618 }16191620 #[test]1621 fn test_module_rename_in_path() {1622 check(1623 "baz",1624 r#"1625mod $0foo {1626 pub use self::bar as qux;1627 pub fn bar() {}1628}16291630fn main() { foo::bar(); }1631"#,1632 r#"1633mod baz {1634 pub use self::bar as qux;1635 pub fn bar() {}1636}16371638fn main() { baz::bar(); }1639"#,1640 );1641 }16421643 #[test]1644 fn test_rename_mod_filename_and_path() {1645 check_expect(1646 "foo2",1647 r#"1648//- /lib.rs1649mod bar;1650fn f() {1651 bar::foo::fun()1652}16531654//- /bar.rs1655pub mod foo$0;16561657//- /bar/foo.rs1658// pub fn fun() {}1659"#,1660 expect![[r#"1661 source_file_edits: [1662 (1663 FileId(1664 0,1665 ),1666 [1667 Indel {1668 insert: "foo2",1669 delete: 27..30,1670 },1671 ],1672 ),1673 (1674 FileId(1675 1,1676 ),1677 [1678 Indel {1679 insert: "foo2",1680 delete: 8..11,1681 },1682 ],1683 ),1684 ]1685 file_system_edits: [1686 MoveFile {1687 src: FileId(1688 2,1689 ),1690 dst: AnchoredPathBuf {1691 anchor: FileId(1692 2,1693 ),1694 path: "foo2.rs",1695 },1696 },1697 ]1698 "#]],1699 );1700 }17011702 #[test]1703 fn test_rename_mod_recursive() {1704 check_expect(1705 "foo2",1706 r#"1707//- /lib.rs1708mod foo$0;17091710//- /foo.rs1711mod bar;1712mod corge;17131714//- /foo/bar.rs1715mod qux;17161717//- /foo/bar/qux.rs1718mod quux;17191720//- /foo/bar/qux/quux/mod.rs1721// empty17221723//- /foo/corge.rs1724// empty1725"#,1726 expect![[r#"1727 source_file_edits: [1728 (1729 FileId(1730 0,1731 ),1732 [1733 Indel {1734 insert: "foo2",1735 delete: 4..7,1736 },1737 ],1738 ),1739 ]1740 file_system_edits: [1741 MoveFile {1742 src: FileId(1743 1,1744 ),1745 dst: AnchoredPathBuf {1746 anchor: FileId(1747 1,1748 ),1749 path: "foo2.rs",1750 },1751 },1752 MoveDir {1753 src: AnchoredPathBuf {1754 anchor: FileId(1755 1,1756 ),1757 path: "foo",1758 },1759 src_id: FileId(1760 1,1761 ),1762 dst: AnchoredPathBuf {1763 anchor: FileId(1764 1,1765 ),1766 path: "foo2",1767 },1768 },1769 ]1770 "#]],1771 )1772 }1773 #[test]1774 fn test_rename_mod_ref_by_super() {1775 check(1776 "baz",1777 r#"1778 mod $0foo {1779 struct X;17801781 mod bar {1782 use super::X;1783 }1784 }1785 "#,1786 r#"1787 mod baz {1788 struct X;17891790 mod bar {1791 use super::X;1792 }1793 }1794 "#,1795 )1796 }17971798 #[test]1799 fn test_rename_mod_in_macro() {1800 check(1801 "bar",1802 r#"1803//- /foo.rs18041805//- /lib.rs1806macro_rules! submodule {1807 ($name:ident) => {1808 mod $name;1809 };1810}18111812submodule!($0foo);1813"#,1814 r#"1815macro_rules! submodule {1816 ($name:ident) => {1817 mod $name;1818 };1819}18201821submodule!(bar);1822"#,1823 )1824 }18251826 #[test]1827 fn test_rename_mod_for_crate_root() {1828 check_expect_will_rename_file(1829 "main",1830 r#"1831//- /lib.rs1832use crate::foo as bar;1833fn foo() {}1834mod bar$0;1835"#,1836 expect![[r#"1837 source_file_edits: []1838 file_system_edits: []1839 "#]],1840 )1841 }18421843 #[test]1844 fn test_rename_mod_to_raw_ident() {1845 check_expect(1846 "r#fn",1847 r#"1848//- /lib.rs1849mod foo$0;18501851fn main() { foo::bar::baz(); }18521853//- /foo.rs1854pub mod bar;18551856//- /foo/bar.rs1857pub fn baz() {}1858"#,1859 expect![[r#"1860 source_file_edits: [1861 (1862 FileId(1863 0,1864 ),1865 [1866 Indel {1867 insert: "r#fn",1868 delete: 4..7,1869 },1870 Indel {1871 insert: "r#fn",1872 delete: 22..25,1873 },1874 ],1875 ),1876 ]1877 file_system_edits: [1878 MoveFile {1879 src: FileId(1880 1,1881 ),1882 dst: AnchoredPathBuf {1883 anchor: FileId(1884 1,1885 ),1886 path: "fn.rs",1887 },1888 },1889 MoveDir {1890 src: AnchoredPathBuf {1891 anchor: FileId(1892 1,1893 ),1894 path: "foo",1895 },1896 src_id: FileId(1897 1,1898 ),1899 dst: AnchoredPathBuf {1900 anchor: FileId(1901 1,1902 ),1903 path: "fn",1904 },1905 },1906 ]1907 "#]],1908 );1909 }19101911 #[test]1912 fn test_rename_mod_from_raw_ident() {1913 check_expect(1914 "foo",1915 r#"1916//- /lib.rs1917mod r#fn$0;19181919fn main() { r#fn::bar::baz(); }19201921//- /fn.rs1922pub mod bar;19231924//- /fn/bar.rs1925pub fn baz() {}1926"#,1927 expect![[r#"1928 source_file_edits: [1929 (1930 FileId(1931 0,1932 ),1933 [1934 Indel {1935 insert: "foo",1936 delete: 4..8,1937 },1938 Indel {1939 insert: "foo",1940 delete: 23..27,1941 },1942 ],1943 ),1944 ]1945 file_system_edits: [1946 MoveFile {1947 src: FileId(1948 1,1949 ),1950 dst: AnchoredPathBuf {1951 anchor: FileId(1952 1,1953 ),1954 path: "foo.rs",1955 },1956 },1957 MoveDir {1958 src: AnchoredPathBuf {1959 anchor: FileId(1960 1,1961 ),1962 path: "fn",1963 },1964 src_id: FileId(1965 1,1966 ),1967 dst: AnchoredPathBuf {1968 anchor: FileId(1969 1,1970 ),1971 path: "foo",1972 },1973 },1974 ]1975 "#]],1976 );1977 }19781979 #[test]1980 fn test_rename_each_usage_gets_appropriate_rawness() {1981 check_expect(1982 "dyn",1983 r#"1984//- /a.rs crate:a edition:20151985pub fn foo() {}19861987//- /b.rs crate:b edition:2018 deps:a new_source_root:local1988fn bar() {1989 a::foo$0();1990}1991 "#,1992 expect![[r#"1993 source_file_edits: [1994 (1995 FileId(1996 0,1997 ),1998 [1999 Indel {2000 insert: "dyn",