File is large — showing lines 1–2,000 of 4,163.
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();82 if let Some(lifetime_token) = syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")83 {84 return Ok(RangeInfo::new(lifetime_token.text_range(), ()));85 }86 let res = find_definitions(&sema, syntax, position, &Name::new_symbol_root(sym::underscore))?87 .filter(|(_, _, def, _, _)| def.range_for_rename(&sema).is_some())88 .map(|(frange, kind, _, _, _)| {89 always!(90 frange.range.contains_inclusive(position.offset)91 && frange.file_id == position.file_id92 );9394 Ok(match kind {95 SyntaxKind::LIFETIME => {96 TextRange::new(frange.range.start() + TextSize::from(1), frange.range.end())97 }98 _ => frange.range,99 })100 })101 .reduce(|acc, cur| match (acc, cur) {102 // ensure all ranges are the same103 (Ok(acc_inner), Ok(cur_inner)) if acc_inner == cur_inner => Ok(acc_inner),104 (e @ Err(_), _) | (_, e @ Err(_)) => e,105 _ => bail!("inconsistent text range"),106 });107108 match res {109 // ensure at least one definition was found110 Some(res) => res.map(|range| RangeInfo::new(range, ())),111 None => bail!("No references found at position"),112 }113}114115// Feature: Rename116//117// Renames the item below the cursor and all of its references118//119// | Editor | Shortcut |120// |---------|----------|121// | VS Code | <kbd>F2</kbd> |122//123// 124pub(crate) fn rename(125 db: &RootDatabase,126 position: FilePosition,127 new_name: &str,128 config: &RenameConfig,129) -> RenameResult<SourceChange> {130 let sema = Semantics::new(db);131 let file_id = sema132 .attach_first_edition_opt(position.file_id)133 .ok_or_else(|| format_err!("No references found at position"))?;134 let source_file = sema.parse(file_id);135 let syntax = source_file.syntax();136137 let edition = file_id.edition(db);138 let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;139 if kind == IdentifierKind::Lifetime140 && let Some(lifetime_token) =141 syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")142 {143 let new_name_str = new_name.display(db, edition).to_string();144 return rename_elided_lifetime(position, lifetime_token, &new_name_str);145 }146147 let defs = find_definitions(&sema, syntax, position, &new_name)?;148 let alias_fallback =149 alias_fallback(syntax, position, &new_name.display(db, edition).to_string());150151 let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {152 Some(_) => ok_if_any(153 defs154 // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can155 // properly find "direct" usages/references.156 .map(|(.., def, new_name, _)| {157 match kind {158 IdentifierKind::Ident => (),159 IdentifierKind::Lifetime => {160 bail!("Cannot alias reference to a lifetime identifier")161 }162 IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),163 IdentifierKind::LowercaseSelf => {164 bail!("Cannot rename alias reference to `self`")165 }166 };167 let mut usages = def.usages(&sema).all();168169 // FIXME: hack - removes the usage that triggered this rename operation.170 match usages.references.get_mut(&file_id).and_then(|refs| {171 refs.iter()172 .position(|ref_| ref_.range.contains_inclusive(position.offset))173 .map(|idx| refs.remove(idx))174 }) {175 Some(_) => (),176 None => never!(),177 };178179 let mut source_change = SourceChange::default();180 source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| {181 (182 position.file_id,183 source_edit_from_references(db, refs, def, &new_name, edition),184 )185 }));186187 Ok(source_change)188 }),189 ),190 None => ok_if_any(defs.map(|(.., def, new_name, rename_def)| {191 if let Definition::Local(local) = def {192 if let Some(self_param) = local.as_self_param(sema.db) {193 cov_mark::hit!(rename_self_to_param);194 return rename_self_to_param(195 &sema,196 local,197 self_param,198 &new_name,199 kind,200 config.find_path_config(),201 );202 }203 if kind == IdentifierKind::LowercaseSelf {204 cov_mark::hit!(rename_to_self);205 return rename_to_self(&sema, local);206 }207 }208 def.rename(&sema, new_name.as_str(), rename_def, &config.ide_db_config())209 })),210 };211212 ops?.into_iter()213 .chain(alias_fallback)214 .reduce(|acc, elem| acc.merge(elem))215 .ok_or_else(|| format_err!("No references found at position"))216}217218/// Called by the client when it is about to rename a file.219pub(crate) fn will_rename_file(220 db: &RootDatabase,221 file_id: FileId,222 new_name_stem: &str,223 config: &RenameConfig,224) -> Option<SourceChange> {225 let sema = Semantics::new(db);226 let module = sema.file_to_module_def(file_id)?;227 let def = Definition::Module(module);228 let mut change =229 def.rename(&sema, new_name_stem, RenameDefinition::Yes, &config.ide_db_config()).ok()?;230 change.file_system_edits.clear();231 Some(change)232}233234// FIXME: Should support `extern crate`.235fn alias_fallback(236 syntax: &SyntaxNode,237 FilePosition { file_id, offset }: FilePosition,238 new_name: &str,239) -> Option<SourceChange> {240 let use_tree = syntax241 .token_at_offset(offset)242 .flat_map(|syntax| syntax.parent_ancestors())243 .find_map(ast::UseTree::cast)?;244245 let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;246 if !last_path_segment.syntax().text_range().contains_inclusive(offset) {247 return None;248 };249250 let mut builder = SourceChangeBuilder::new(file_id);251252 match use_tree.rename() {253 Some(rename) => {254 let offset = rename.syntax().text_range();255 builder.replace(offset, format!("as {new_name}"));256 }257 None => {258 let offset = use_tree.syntax().text_range().end();259 builder.insert(offset, format!(" as {new_name}"));260 }261 }262263 Some(builder.finish())264}265266fn find_definitions(267 sema: &Semantics<'_, RootDatabase>,268 syntax: &SyntaxNode,269 FilePosition { file_id, offset }: FilePosition,270 new_name: &Name,271) -> RenameResult<impl Iterator<Item = (FileRange, SyntaxKind, Definition, Name, RenameDefinition)>>272{273 let maybe_format_args =274 syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));275276 if let Some((range, _, _, Some(resolution))) =277 maybe_format_args.and_then(|token| sema.check_for_format_args_template(token, offset))278 {279 return Ok(vec![(280 FileRange { file_id, range },281 SyntaxKind::STRING,282 Definition::from(resolution),283 new_name.clone(),284 RenameDefinition::Yes,285 )]286 .into_iter());287 }288289 let original_ident = syntax290 .token_at_offset(offset)291 .max_by_key(|t| {292 t.kind().is_any_identifier() || matches!(t.kind(), SyntaxKind::LIFETIME_IDENT)293 })294 .map(|t| {295 if t.kind() == SyntaxKind::LIFETIME_IDENT {296 Name::new_lifetime(t.text())297 } else {298 Name::new_root(t.text())299 }300 })301 .ok_or_else(|| format_err!("No references found at position"))?;302 let symbols =303 sema.find_namelike_at_offset_with_descend(syntax, offset).map(|name_like| {304 let kind = name_like.syntax().kind();305 let range = sema306 .original_range_opt(name_like.syntax())307 .ok_or_else(|| format_err!("No references found at position"))?;308 let res = match &name_like {309 // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet310 ast::NameLike::Name(name)311 if name312 .syntax()313 .parent().is_some_and(|it| ast::Rename::can_cast(it.kind()))314 // FIXME: uncomment this once we resolve to usages to extern crate declarations315 // && name316 // .syntax()317 // .ancestors()318 // .nth(2)319 // .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind()))320 =>321 {322 bail!("Renaming aliases is currently unsupported")323 }324 ast::NameLike::Name(name) => NameClass::classify(sema, name)325 .map(|class| match class {326 NameClass::Definition(it) | NameClass::ConstReference(it) => it,327 NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {328 Definition::Local(local_def)329 }330 })331 .ok_or_else(|| format_err!("No references found at position")),332 ast::NameLike::NameRef(name_ref) => {333 NameRefClass::classify(sema, name_ref)334 .map(|class| match class {335 NameRefClass::Definition(def, _) => def,336 NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ } => {337 Definition::Local(local_ref)338 }339 NameRefClass::ExternCrateShorthand { decl, .. } => {340 Definition::ExternCrateDecl(decl)341 }342 })343 // FIXME: uncomment this once we resolve to usages to extern crate declarations344 .filter(|def| !matches!(def, Definition::ExternCrateDecl(..)))345 .ok_or_else(|| format_err!("No references found at position"))346 .and_then(|def| {347 // if the name differs from the definitions name it has to be an alias348 if def349 .name(sema.db).is_some_and(|it| it.as_str() != name_ref.text().trim_start_matches("r#"))350 {351 Err(format_err!("Renaming aliases is currently unsupported"))352 } else {353 Ok(def)354 }355 })356 }357 ast::NameLike::Lifetime(lifetime) => {358 NameRefClass::classify_lifetime(sema, lifetime)359 .and_then(|class| match class {360 NameRefClass::Definition(def, _) => Some(def),361 _ => None,362 })363 .or_else(|| {364 NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {365 NameClass::Definition(it) => Some(it),366 _ => None,367 })368 })369 .ok_or_else(|| format_err!("No references found at position"))370 }371 };372 res.map(|def| {373 let n = def.name(sema.db)?;374 if n == original_ident {375 Some((range, kind, def, new_name.clone(), RenameDefinition::Yes))376 } else if let Some(suffix) = n.as_str().strip_prefix(original_ident.as_str()) {377 Some((range, kind, def, Name::new_root(&format!("{}{suffix}", new_name.as_str())), RenameDefinition::No))378 } else {379 n.as_str().strip_suffix(original_ident.as_str().trim_start_matches('\''))380 .map(|prefix| (range, kind, def, Name::new_root(&format!("{prefix}{}", new_name.as_str())), RenameDefinition::No))381 }382 })383 });384385 let res: RenameResult<Vec<_>> = ok_if_any(symbols.filter_map(Result::transpose));386 match res {387 Ok(v) => {388 // remove duplicates, comparing `Definition`s389 Ok(v.into_iter()390 .unique_by(|&(.., def, _, _)| def)391 .map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e))392 .collect::<Vec<_>>()393 .into_iter())394 }395 Err(e) => Err(e),396 }397}398399fn transform_assoc_fn_into_method_call(400 sema: &Semantics<'_, RootDatabase>,401 source_change: &mut SourceChange,402 f: hir::Function,403) {404 let calls = Definition::Function(f).usages(sema).all();405 for (_file_id, calls) in calls {406 for call in calls {407 let Some(fn_name) = call.name.as_name_ref() else { continue };408 let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {409 continue;410 };411 let path = path.parent_path();412 // The `PathExpr` is the direct parent, above it is the `CallExpr`.413 let Some(call) =414 path.syntax().parent().and_then(|it| ast::CallExpr::cast(it.parent()?))415 else {416 continue;417 };418419 let Some(arg_list) = call.arg_list() else { continue };420 let mut args = arg_list.args();421 let Some(mut self_arg) = args.next() else { continue };422 let second_arg = args.next();423424 // Strip (de)references, as they will be taken automatically by auto(de)ref.425 loop {426 let self_ = match &self_arg {427 ast::Expr::RefExpr(self_) => self_.expr(),428 ast::Expr::ParenExpr(self_) => self_.expr(),429 ast::Expr::PrefixExpr(self_)430 if self_.op_kind() == Some(ast::UnaryOp::Deref) =>431 {432 self_.expr()433 }434 _ => break,435 };436 self_arg = match self_ {437 Some(it) => it,438 None => break,439 };440 }441442 let self_needs_parens =443 self_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix);444445 let replace_start = path.syntax().text_range().start();446 let replace_end = match second_arg {447 Some(second_arg) => second_arg.syntax().text_range().start(),448 None => arg_list449 .r_paren_token()450 .map(|it| it.text_range().start())451 .unwrap_or_else(|| arg_list.syntax().text_range().end()),452 };453 let replace_range = TextRange::new(replace_start, replace_end);454 let macro_file = sema.hir_file_for(fn_name.syntax());455 let Some((replace_range, _)) =456 InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)457 else {458 continue;459 };460461 let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {462 continue;463 };464 let mut replacement = String::new();465 if self_needs_parens {466 replacement.push('(');467 }468 replacement.push_str(macro_mapped_self.text(sema.db));469 if self_needs_parens {470 replacement.push(')');471 }472 replacement.push('.');473 format_to!(replacement, "{fn_name}");474 replacement.push('(');475476 source_change.insert_source_edit(477 replace_range.file_id.file_id(sema.db),478 TextEdit::replace(replace_range.range, replacement),479 );480 }481 }482}483484fn rename_to_self(485 sema: &Semantics<'_, RootDatabase>,486 local: hir::Local,487) -> RenameResult<SourceChange> {488 if never!(local.is_self(sema.db)) {489 bail!("rename_to_self invoked on self");490 }491492 let fn_def = match local.parent(sema.db) {493 hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,494 _ => bail!("Cannot rename local to self outside of function"),495 };496497 if fn_def.self_param(sema.db).is_some() {498 bail!("Method already has a self parameter");499 }500501 let params = fn_def.assoc_fn_params(sema.db);502 let first_param = params503 .first()504 .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?;505 match first_param.as_local(sema.db) {506 Some(plocal) => {507 if plocal != local {508 bail!("Only the first parameter may be renamed to self");509 }510 }511 None => bail!("rename_to_self invoked on destructuring parameter"),512 }513514 let assoc_item = fn_def515 .as_assoc_item(sema.db)516 .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?;517 let impl_ = match assoc_item.container(sema.db) {518 hir::AssocItemContainer::Trait(_) => {519 bail!("Cannot rename parameter to self for trait functions");520 }521 hir::AssocItemContainer::Impl(impl_) => impl_,522 };523 let first_param_ty = first_param.ty();524 let impl_ty = impl_.self_ty(sema.db);525 let (ty, self_param) = if impl_ty.is_reference() {526 // if the impl is a ref to the type we can just match the `&T` with self directly527 (first_param_ty.clone(), "self")528 } else {529 first_param_ty.as_reference_inner().map_or((first_param_ty.clone(), "self"), |ty| {530 (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })531 })532 };533534 if ty != impl_ty {535 bail!("Parameter type differs from impl block type");536 }537538 let InFile { file_id, value: param_source } = sema539 .source(first_param.clone())540 .ok_or_else(|| format_err!("No source for parameter found"))?;541542 let def = Definition::Local(local);543 let usages = def.usages(sema).all();544 let mut source_change = SourceChange::default();545 source_change.extend(usages.iter().map(|(file_id, references)| {546 (547 file_id.file_id(sema.db),548 source_edit_from_references(549 sema.db,550 references,551 def,552 &Name::new_symbol_root(sym::self_),553 file_id.edition(sema.db),554 ),555 )556 }));557 source_change.insert_source_edit(558 file_id.original_file(sema.db).file_id(sema.db),559 TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),560 );561 transform_assoc_fn_into_method_call(sema, &mut source_change, fn_def);562 Ok(source_change)563}564565#[derive(Debug, Clone, Copy, PartialEq, Eq)]566enum CallReceiverAdjust {567 Deref,568 Ref,569 RefMut,570 None,571}572573fn method_to_assoc_fn_call_self_adjust(574 sema: &Semantics<'_, RootDatabase>,575 self_arg: &ast::Expr,576) -> CallReceiverAdjust {577 let mut result = CallReceiverAdjust::None;578 let self_adjust = sema.expr_adjustments(self_arg);579 if let Some(self_adjust) = self_adjust {580 let mut i = 0;581 while i < self_adjust.len() {582 if matches!(self_adjust[i].kind, hir::Adjust::Deref(..))583 && matches!(584 self_adjust.get(i + 1),585 Some(hir::Adjustment { kind: hir::Adjust::Borrow(..), .. })586 )587 {588 // Deref then ref (reborrow), skip them.589 i += 2;590 continue;591 }592593 match self_adjust[i].kind {594 hir::Adjust::Deref(_) if result == CallReceiverAdjust::None => {595 // Autoref takes precedence over deref, because if given a `&Type` the compiler will deref596 // it automatically.597 result = CallReceiverAdjust::Deref;598 }599 hir::Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => {600 match (result, mutability) {601 (CallReceiverAdjust::RefMut, hir::Mutability::Shared) => {}602 (_, hir::Mutability::Mut) => result = CallReceiverAdjust::RefMut,603 (_, hir::Mutability::Shared) => result = CallReceiverAdjust::Ref,604 }605 }606 _ => {}607 }608609 i += 1;610 }611 }612 result613}614615fn transform_method_call_into_assoc_fn(616 sema: &Semantics<'_, RootDatabase>,617 source_change: &mut SourceChange,618 f: hir::Function,619 find_path_config: FindPathConfig,620) {621 let calls = Definition::Function(f).usages(sema).all();622 for (_file_id, calls) in calls {623 for call in calls {624 let Some(fn_name) = call.name.as_name_ref() else { continue };625 let Some(method_call) = fn_name.syntax().parent().and_then(ast::MethodCallExpr::cast)626 else {627 continue;628 };629 let Some(mut self_arg) = method_call.receiver() else {630 continue;631 };632633 let Some(scope) = sema.scope(fn_name.syntax()) else {634 continue;635 };636 let self_adjust = method_to_assoc_fn_call_self_adjust(sema, &self_arg);637638 // Strip parentheses, function arguments have higher precedence than any operator.639 while let ast::Expr::ParenExpr(it) = &self_arg {640 self_arg = match it.expr() {641 Some(it) => it,642 None => break,643 };644 }645646 let needs_comma = method_call.arg_list().is_some_and(|it| it.args().next().is_some());647648 let self_needs_parens = self_adjust != CallReceiverAdjust::None649 && self_arg.precedence().needs_parentheses_in(ExprPrecedence::Prefix);650651 let replace_start = method_call.syntax().text_range().start();652 let replace_end = method_call653 .arg_list()654 .and_then(|it| it.l_paren_token())655 .map(|it| it.text_range().end())656 .unwrap_or_else(|| method_call.syntax().text_range().end());657 let replace_range = TextRange::new(replace_start, replace_end);658 let macro_file = sema.hir_file_for(fn_name.syntax());659 let Some((replace_range, _)) =660 InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)661 else {662 continue;663 };664665 let fn_container_path = match f.container(sema.db) {666 hir::ItemContainer::Trait(trait_) => {667 // FIXME: We always put it as `Trait::function`. Is it better to use `Type::function` (but668 // that could conflict with an inherent method)? Or maybe `<Type as Trait>::function`?669 // Or let the user decide?670 let Some(path) = scope.module().find_path(671 sema.db,672 hir::ItemInNs::Types(trait_.into()),673 find_path_config,674 ) else {675 continue;676 };677 path.display(sema.db, replace_range.file_id.edition(sema.db)).to_string()678 }679 hir::ItemContainer::Impl(impl_) => {680 let ty = impl_.self_ty(sema.db);681 match ty.as_adt() {682 Some(adt) => {683 let Some(path) = scope.module().find_path(684 sema.db,685 hir::ItemInNs::Types(adt.into()),686 find_path_config,687 ) else {688 continue;689 };690 path.display(sema.db, replace_range.file_id.edition(sema.db))691 .to_string()692 }693 None => {694 let Ok(mut ty) =695 ty.display_source_code(sema.db, scope.module().into(), false)696 else {697 continue;698 };699 ty.insert(0, '<');700 ty.push('>');701 ty702 }703 }704 }705 _ => continue,706 };707708 let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {709 continue;710 };711 let mut replacement = String::new();712 replacement.push_str(&fn_container_path);713 replacement.push_str("::");714 format_to!(replacement, "{fn_name}");715 replacement.push('(');716 replacement.push_str(match self_adjust {717 CallReceiverAdjust::Deref => "*",718 CallReceiverAdjust::Ref => "&",719 CallReceiverAdjust::RefMut => "&mut ",720 CallReceiverAdjust::None => "",721 });722 if self_needs_parens {723 replacement.push('(');724 }725 replacement.push_str(macro_mapped_self.text(sema.db));726 if self_needs_parens {727 replacement.push(')');728 }729 if needs_comma {730 replacement.push_str(", ");731 }732733 source_change.insert_source_edit(734 replace_range.file_id.file_id(sema.db),735 TextEdit::replace(replace_range.range, replacement),736 );737 }738 }739}740741fn rename_self_to_param(742 sema: &Semantics<'_, RootDatabase>,743 local: hir::Local,744 self_param: hir::SelfParam,745 new_name: &Name,746 identifier_kind: IdentifierKind,747 find_path_config: FindPathConfig,748) -> RenameResult<SourceChange> {749 if identifier_kind == IdentifierKind::LowercaseSelf {750 // Let's do nothing rather than complain.751 cov_mark::hit!(rename_self_to_self);752 return Ok(SourceChange::default());753 }754755 let fn_def = match local.parent(sema.db) {756 hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,757 _ => bail!("Cannot rename local to self outside of function"),758 };759760 let InFile { file_id, value: self_param } =761 sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?;762763 let def = Definition::Local(local);764 let usages = def.usages(sema).all();765 let edit = text_edit_from_self_param(766 &self_param,767 new_name.display(sema.db, file_id.edition(sema.db)).to_string(),768 )769 .ok_or_else(|| format_err!("No target type found"))?;770 if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {771 bail!("Cannot rename reference to `_` as it is being referenced multiple times");772 }773 let mut source_change = SourceChange::default();774 source_change.insert_source_edit(file_id.original_file(sema.db).file_id(sema.db), edit);775 source_change.extend(usages.iter().map(|(file_id, references)| {776 (777 file_id.file_id(sema.db),778 source_edit_from_references(779 sema.db,780 references,781 def,782 new_name,783 file_id.edition(sema.db),784 ),785 )786 }));787 transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config);788 Ok(source_change)789}790791fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {792 let mut replacement_text = new_name;793 replacement_text.push_str(": ");794795 if self_param.amp_token().is_some() {796 replacement_text.push('&');797 }798 if let Some(lifetime) = self_param.lifetime() {799 write!(replacement_text, "{lifetime} ").unwrap();800 }801 if self_param.amp_token().and(self_param.mut_token()).is_some() {802 replacement_text.push_str("mut ");803 }804805 replacement_text.push_str("Self");806807 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))808}809810fn rename_elided_lifetime(811 position: FilePosition,812 lifetime_token: syntax::SyntaxToken,813 new_name: &str,814) -> RenameResult<SourceChange> {815 let parent = lifetime_token.parent().unwrap();816 let root = parent.ancestors().last().unwrap();817818 let mut builder = SourceChangeBuilder::new(position.file_id);819820 let editor = builder.make_editor(&root);821 let make = editor.make();822823 editor.replace(lifetime_token, make.lifetime(new_name).syntax().clone());824825 if let Some(has_generic_params) = parent.ancestors().find_map(ast::AnyHasGenericParams::cast) {826 let lifetime_param = make.lifetime_param(make.lifetime(new_name));827 editor.add_generic_param(&has_generic_params, lifetime_param.into());828 }829830 builder.add_file_edits(position.file_id, editor);831832 Ok(builder.finish())833}834835#[cfg(test)]836mod tests {837 use expect_test::{Expect, expect};838 use ide_db::source_change::SourceChange;839 use ide_db::text_edit::TextEdit;840 use itertools::Itertools;841 use stdx::trim_indent;842 use test_utils::assert_eq_text;843844 use crate::fixture;845846 use super::{RangeInfo, RenameConfig, RenameError};847848 const TEST_CONFIG: RenameConfig = RenameConfig {849 prefer_no_std: false,850 prefer_prelude: true,851 prefer_absolute: false,852 show_conflicts: true,853 };854855 #[track_caller]856 fn check(857 new_name: &str,858 #[rust_analyzer::rust_fixture] ra_fixture_before: &str,859 #[rust_analyzer::rust_fixture] ra_fixture_after: &str,860 ) {861 let ra_fixture_after = &trim_indent(ra_fixture_after);862 let (analysis, position) = fixture::position(ra_fixture_before);863 if !ra_fixture_after.starts_with("error: ")864 && let Err(err) = analysis.prepare_rename(position).unwrap()865 {866 panic!("Prepare rename to '{new_name}' was failed: {err}")867 }868 let rename_result = analysis869 .rename(position, new_name, &TEST_CONFIG)870 .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));871 match rename_result {872 Ok(source_change) => {873 let mut text_edit_builder = TextEdit::builder();874 let (&file_id, edit) = match source_change.source_file_edits.len() {875 0 => return,876 1 => source_change.source_file_edits.iter().next().unwrap(),877 _ => panic!(),878 };879 for indel in edit.0.iter() {880 text_edit_builder.replace(indel.delete, indel.insert.clone());881 }882 let mut result = analysis.file_text(file_id).unwrap().to_string();883 text_edit_builder.finish().apply(&mut result);884 assert_eq_text!(ra_fixture_after, &*result);885 }886 Err(err) => {887 if ra_fixture_after.starts_with("error:") {888 let error_message =889 ra_fixture_after.chars().skip("error:".len()).collect::<String>();890 assert_eq!(error_message.trim(), err.to_string());891 } else {892 panic!("Rename to '{new_name}' failed unexpectedly: {err}")893 }894 }895 };896 }897898 #[track_caller]899 fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) {900 let (analysis, position, conflicts) = fixture::annotations(ra_fixture);901 let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap();902 let expected_conflicts = conflicts903 .into_iter()904 .map(|(file_range, _)| (file_range.file_id, file_range.range))905 .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))906 .collect_vec();907 let found_conflicts = source_change908 .source_file_edits909 .iter()910 .filter(|(_, (edit, _))| edit.change_annotation().is_some())911 .flat_map(|(file_id, (edit, _))| {912 edit.into_iter().map(move |edit| (*file_id, edit.delete))913 })914 .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))915 .collect_vec();916 assert_eq!(917 expected_conflicts, found_conflicts,918 "rename conflicts mismatch: {source_change:#?}"919 );920 }921922 fn check_expect(923 new_name: &str,924 #[rust_analyzer::rust_fixture] ra_fixture: &str,925 expect: Expect,926 ) {927 let (analysis, position) = fixture::position(ra_fixture);928 let source_change = analysis929 .rename(position, new_name, &TEST_CONFIG)930 .unwrap()931 .expect("Expect returned a RenameError");932 expect.assert_eq(&filter_expect(source_change))933 }934935 fn check_expect_will_rename_file(936 new_name: &str,937 #[rust_analyzer::rust_fixture] ra_fixture: &str,938 expect: Expect,939 ) {940 let (analysis, position) = fixture::position(ra_fixture);941 let source_change = analysis942 .will_rename_file(position.file_id, new_name, &TEST_CONFIG)943 .unwrap()944 .expect("Expect returned a RenameError");945 expect.assert_eq(&filter_expect(source_change))946 }947948 fn check_prepare(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {949 let (analysis, position) = fixture::position(ra_fixture);950 let result = analysis951 .prepare_rename(position)952 .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {err}"));953 match result {954 Ok(RangeInfo { range, info: () }) => {955 let source = analysis.file_text(position.file_id).unwrap();956 expect.assert_eq(&format!("{range:?}: {}", &source[range]))957 }958 Err(RenameError(err)) => expect.assert_eq(&err),959 };960 }961962 fn filter_expect(source_change: SourceChange) -> String {963 let source_file_edits = source_change964 .source_file_edits965 .into_iter()966 .map(|(id, (text_edit, _))| (id, text_edit.into_iter().collect::<Vec<_>>()))967 .collect::<Vec<_>>();968969 format!(970 "source_file_edits: {:#?}\nfile_system_edits: {:#?}\n",971 source_file_edits, source_change.file_system_edits972 )973 }974975 #[test]976 fn rename_will_shadow() {977 check_conflicts(978 "new_name",979 r#"980fn foo() {981 let mut new_name = 123;982 let old_name$0 = 456;983 // ^^^^^^^^984 new_name = 789 + new_name;985}986 "#,987 );988 }989990 #[test]991 fn rename_will_be_shadowed() {992 check_conflicts(993 "new_name",994 r#"995fn foo() {996 let mut old_name$0 = 456;997 // ^^^^^^^^998 let new_name = 123;999 old_name = 789 + old_name;1000 // ^^^^^^^^ ^^^^^^^^1001}1002 "#,1003 );1004 }10051006 #[test]1007 fn test_prepare_rename_namelikes() {1008 check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);1009 check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"9..17: lifetime"#]]);1010 check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);1011 }10121013 #[test]1014 fn test_prepare_rename_in_macro() {1015 check_prepare(1016 r"macro_rules! foo {1017 ($ident:ident) => {1018 pub struct $ident;1019 }1020}1021foo!(Foo$0);",1022 expect![[r#"83..86: Foo"#]],1023 );1024 }10251026 #[test]1027 fn test_prepare_rename_keyword() {1028 check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);1029 }10301031 #[test]1032 fn test_prepare_rename_tuple_field() {1033 check_prepare(1034 r#"1035struct Foo(i32);10361037fn baz() {1038 let mut x = Foo(4);1039 x.0$0 = 5;1040}1041"#,1042 expect![[r#"No references found at position"#]],1043 );1044 }10451046 #[test]1047 fn test_prepare_rename_builtin() {1048 check_prepare(1049 r#"1050fn foo() {1051 let x: i32$0 = 0;1052}1053"#,1054 expect![[r#"No references found at position"#]],1055 );1056 }10571058 #[test]1059 fn test_prepare_rename_self() {1060 check_prepare(1061 r#"1062struct Foo {}10631064impl Foo {1065 fn foo(self) -> Self$0 {1066 self1067 }1068}1069"#,1070 expect![[r#"No references found at position"#]],1071 );1072 }10731074 #[test]1075 fn test_rename_to_underscore() {1076 check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);1077 }10781079 #[test]1080 fn test_rename_to_raw_identifier() {1081 check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);1082 }10831084 #[test]1085 fn test_rename_to_invalid_identifier1() {1086 check(1087 "invalid!",1088 r#"fn main() { let i$0 = 1; }"#,1089 "error: Invalid name `invalid!`: not an identifier",1090 );1091 }10921093 #[test]1094 fn test_rename_to_invalid_identifier2() {1095 check(1096 "multiple tokens",1097 r#"fn main() { let i$0 = 1; }"#,1098 "error: Invalid name `multiple tokens`: not an identifier",1099 );1100 }11011102 #[test]1103 fn test_rename_to_invalid_identifier3() {1104 check(1105 "super",1106 r#"fn main() { let i$0 = 1; }"#,1107 "error: Invalid name `super`: cannot rename to a keyword",1108 );1109 }11101111 #[test]1112 fn test_rename_to_invalid_identifier_lifetime() {1113 cov_mark::check!(rename_not_an_ident_ref);1114 check(1115 "'foo",1116 r#"fn main() { let i$0 = 1; }"#,1117 "error: Invalid name `'foo`: not an identifier",1118 );1119 }11201121 #[test]1122 fn test_rename_to_invalid_identifier_lifetime2() {1123 check(1124 "_",1125 r#"fn main<'a>(_: &'a$0 ()) {}"#,1126 r#"error: Invalid name `_`: not a lifetime identifier"#,1127 );1128 }11291130 #[test]1131 fn test_rename_accepts_lifetime_without_apostrophe() {1132 check("foo", r#"fn main<'a>(_: &'a$0 ()) {}"#, r#"fn main<'foo>(_: &'foo ()) {}"#);1133 }11341135 #[test]1136 fn test_rename_to_underscore_invalid() {1137 cov_mark::check!(rename_underscore_multiple);1138 check(1139 "_",1140 r#"fn main(foo$0: ()) {foo;}"#,1141 "error: Cannot rename reference to `_` as it is being referenced multiple times",1142 );1143 }11441145 #[test]1146 fn test_rename_mod_invalid() {1147 check(1148 "'foo",1149 r#"mod foo$0 {}"#,1150 "error: Invalid name `'foo`: cannot rename module to 'foo",1151 );1152 }11531154 #[test]1155 fn test_rename_mod_invalid_raw_ident() {1156 check(1157 "r#self",1158 r#"mod foo$0 {}"#,1159 "error: Invalid name `self`: cannot rename module to self",1160 );1161 }11621163 #[test]1164 fn test_rename_for_local() {1165 check(1166 "k",1167 r#"1168fn main() {1169 let mut i = 1;1170 let j = 1;1171 i = i$0 + j;11721173 { i = 0; }11741175 i = 5;1176}1177"#,1178 r#"1179fn main() {1180 let mut k = 1;1181 let j = 1;1182 k = k + j;11831184 { k = 0; }11851186 k = 5;1187}1188"#,1189 );1190 }11911192 #[test]1193 fn test_rename_unresolved_reference() {1194 check(1195 "new_name",1196 r#"fn main() { let _ = unresolved_ref$0; }"#,1197 "error: No references found at position",1198 );1199 }12001201 #[test]1202 fn test_rename_macro_multiple_occurrences() {1203 check(1204 "Baaah",1205 r#"macro_rules! foo {1206 ($ident:ident) => {1207 const $ident: () = ();1208 struct $ident {}1209 };1210}12111212foo!($0Foo);1213const _: () = Foo;1214const _: Foo = Foo {};1215 "#,1216 r#"1217macro_rules! foo {1218 ($ident:ident) => {1219 const $ident: () = ();1220 struct $ident {}1221 };1222}12231224foo!(Baaah);1225const _: () = Baaah;1226const _: Baaah = Baaah {};1227 "#,1228 )1229 }12301231 #[test]1232 fn test_rename_for_macro_args() {1233 check(1234 "b",1235 r#"1236macro_rules! foo {($i:ident) => {$i} }1237fn main() {1238 let a$0 = "test";1239 foo!(a);1240}1241"#,1242 r#"1243macro_rules! foo {($i:ident) => {$i} }1244fn main() {1245 let b = "test";1246 foo!(b);1247}1248"#,1249 );1250 }12511252 #[test]1253 fn test_rename_for_macro_args_rev() {1254 check(1255 "b",1256 r#"1257macro_rules! foo {($i:ident) => {$i} }1258fn main() {1259 let a = "test";1260 foo!(a$0);1261}1262"#,1263 r#"1264macro_rules! foo {($i:ident) => {$i} }1265fn main() {1266 let b = "test";1267 foo!(b);1268}1269"#,1270 );1271 }12721273 #[test]1274 fn test_rename_for_macro_define_fn() {1275 check(1276 "bar",1277 r#"1278macro_rules! define_fn {($id:ident) => { fn $id{} }}1279define_fn!(foo);1280fn main() {1281 fo$0o();1282}1283"#,1284 r#"1285macro_rules! define_fn {($id:ident) => { fn $id{} }}1286define_fn!(bar);1287fn main() {1288 bar();1289}1290"#,1291 );1292 }12931294 #[test]1295 fn test_rename_for_macro_define_fn_rev() {1296 check(1297 "bar",1298 r#"1299macro_rules! define_fn {($id:ident) => { fn $id{} }}1300define_fn!(fo$0o);1301fn main() {1302 foo();1303}1304"#,1305 r#"1306macro_rules! define_fn {($id:ident) => { fn $id{} }}1307define_fn!(bar);1308fn main() {1309 bar();1310}1311"#,1312 );1313 }13141315 #[test]1316 fn test_rename_for_param_inside() {1317 check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);1318 }13191320 #[test]1321 fn test_rename_refs_for_fn_param() {1322 check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);1323 }13241325 #[test]1326 fn test_rename_for_mut_param() {1327 check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);1328 }13291330 #[test]1331 fn test_rename_struct_field() {1332 check(1333 "foo",1334 r#"1335struct Foo { field$0: i32 }13361337impl Foo {1338 fn new(i: i32) -> Self {1339 Self { field: i }1340 }1341}1342"#,1343 r#"1344struct Foo { foo: i32 }13451346impl Foo {1347 fn new(i: i32) -> Self {1348 Self { foo: i }1349 }1350}1351"#,1352 );1353 }13541355 #[test]1356 fn test_rename_field_in_field_shorthand() {1357 cov_mark::check!(test_rename_field_in_field_shorthand);1358 check(1359 "field",1360 r#"1361struct Foo { foo$0: i32 }13621363impl Foo {1364 fn foo(foo: i32) {1365 Self { foo };1366 }1367}1368"#,1369 r#"1370struct Foo { field: i32 }13711372impl Foo {1373 fn foo(foo: i32) {1374 Self { field: foo };1375 }1376}1377"#,1378 );1379 }13801381 #[test]1382 fn test_rename_local_in_field_shorthand() {1383 cov_mark::check!(test_rename_local_in_field_shorthand);1384 check(1385 "j",1386 r#"1387struct Foo { i: i32 }13881389impl Foo {1390 fn new(i$0: i32) -> Self {1391 Self { i }1392 }1393}1394"#,1395 r#"1396struct Foo { i: i32 }13971398impl Foo {1399 fn new(j: i32) -> Self {1400 Self { i: j }1401 }1402}1403"#,1404 );1405 }14061407 #[test]1408 fn test_field_shorthand_correct_struct() {1409 check(1410 "j",1411 r#"1412struct Foo { i$0: i32 }1413struct Bar { i: i32 }14141415impl Bar {1416 fn new(i: i32) -> Self {1417 Self { i }1418 }1419}1420"#,1421 r#"1422struct Foo { j: i32 }1423struct Bar { i: i32 }14241425impl Bar {1426 fn new(i: i32) -> Self {1427 Self { i }1428 }1429}1430"#,1431 );1432 }14331434 #[test]1435 fn test_shadow_local_for_struct_shorthand() {1436 check(1437 "j",1438 r#"1439struct Foo { i: i32 }14401441fn baz(i$0: i32) -> Self {1442 let x = Foo { i };1443 {1444 let i = 0;1445 Foo { i }1446 }1447}1448"#,1449 r#"1450struct Foo { i: i32 }14511452fn baz(j: i32) -> Self {1453 let x = Foo { i: j };1454 {1455 let i = 0;1456 Foo { i }1457 }1458}1459"#,1460 );1461 }14621463 #[test]1464 fn test_rename_mod() {1465 check_expect(1466 "foo2",1467 r#"1468//- /lib.rs1469mod bar;14701471//- /bar.rs1472mod foo$0;14731474//- /bar/foo.rs1475// empty1476"#,1477 expect![[r#"1478 source_file_edits: [1479 (1480 FileId(1481 1,1482 ),1483 [1484 Indel {1485 insert: "foo2",1486 delete: 4..7,1487 },1488 ],1489 ),1490 ]1491 file_system_edits: [1492 MoveFile {1493 src: FileId(1494 2,1495 ),1496 dst: AnchoredPathBuf {1497 anchor: FileId(1498 2,1499 ),1500 path: "foo2.rs",1501 },1502 },1503 ]1504 "#]],1505 );1506 }15071508 #[test]1509 fn test_rename_mod_in_use_tree() {1510 check_expect(1511 "quux",1512 r#"1513//- /main.rs1514pub mod foo;1515pub mod bar;1516fn main() {}15171518//- /foo.rs1519pub struct FooContent;15201521//- /bar.rs1522use crate::foo$0::FooContent;1523"#,1524 expect![[r#"1525 source_file_edits: [1526 (1527 FileId(1528 0,1529 ),1530 [1531 Indel {1532 insert: "quux",1533 delete: 8..11,1534 },1535 ],1536 ),1537 (1538 FileId(1539 2,1540 ),1541 [1542 Indel {1543 insert: "quux",1544 delete: 11..14,1545 },1546 ],1547 ),1548 ]1549 file_system_edits: [1550 MoveFile {1551 src: FileId(1552 1,1553 ),1554 dst: AnchoredPathBuf {1555 anchor: FileId(1556 1,1557 ),1558 path: "quux.rs",1559 },1560 },1561 ]1562 "#]],1563 );1564 }15651566 #[test]1567 fn test_rename_mod_in_dir() {1568 check_expect(1569 "foo2",1570 r#"1571//- /lib.rs1572mod fo$0o;1573//- /foo/mod.rs1574// empty1575"#,1576 expect![[r#"1577 source_file_edits: [1578 (1579 FileId(1580 0,1581 ),1582 [1583 Indel {1584 insert: "foo2",1585 delete: 4..7,1586 },1587 ],1588 ),1589 ]1590 file_system_edits: [1591 MoveDir {1592 src: AnchoredPathBuf {1593 anchor: FileId(1594 1,1595 ),1596 path: "../foo",1597 },1598 src_id: FileId(1599 1,1600 ),1601 dst: AnchoredPathBuf {1602 anchor: FileId(1603 1,1604 ),1605 path: "../foo2",1606 },1607 },1608 ]1609 "#]],1610 );1611 }16121613 #[test]1614 fn test_rename_unusually_nested_mod() {1615 check_expect(1616 "bar",1617 r#"1618//- /lib.rs1619mod outer { mod fo$0o; }16201621//- /outer/foo.rs1622// empty1623"#,1624 expect![[r#"1625 source_file_edits: [1626 (1627 FileId(1628 0,1629 ),1630 [1631 Indel {1632 insert: "bar",1633 delete: 16..19,1634 },1635 ],1636 ),1637 ]1638 file_system_edits: [1639 MoveFile {1640 src: FileId(1641 1,1642 ),1643 dst: AnchoredPathBuf {1644 anchor: FileId(1645 1,1646 ),1647 path: "bar.rs",1648 },1649 },1650 ]1651 "#]],1652 );1653 }16541655 #[test]1656 fn test_module_rename_in_path() {1657 check(1658 "baz",1659 r#"1660mod $0foo {1661 pub use self::bar as qux;1662 pub fn bar() {}1663}16641665fn main() { foo::bar(); }1666"#,1667 r#"1668mod baz {1669 pub use self::bar as qux;1670 pub fn bar() {}1671}16721673fn main() { baz::bar(); }1674"#,1675 );1676 }16771678 #[test]1679 fn test_rename_mod_filename_and_path() {1680 check_expect(1681 "foo2",1682 r#"1683//- /lib.rs1684mod bar;1685fn f() {1686 bar::foo::fun()1687}16881689//- /bar.rs1690pub mod foo$0;16911692//- /bar/foo.rs1693// pub fn fun() {}1694"#,1695 expect![[r#"1696 source_file_edits: [1697 (1698 FileId(1699 0,1700 ),1701 [1702 Indel {1703 insert: "foo2",1704 delete: 27..30,1705 },1706 ],1707 ),1708 (1709 FileId(1710 1,1711 ),1712 [1713 Indel {1714 insert: "foo2",1715 delete: 8..11,1716 },1717 ],1718 ),1719 ]1720 file_system_edits: [1721 MoveFile {1722 src: FileId(1723 2,1724 ),1725 dst: AnchoredPathBuf {1726 anchor: FileId(1727 2,1728 ),1729 path: "foo2.rs",1730 },1731 },1732 ]1733 "#]],1734 );1735 }17361737 #[test]1738 fn test_rename_mod_recursive() {1739 check_expect(1740 "foo2",1741 r#"1742//- /lib.rs1743mod foo$0;17441745//- /foo.rs1746mod bar;1747mod corge;17481749//- /foo/bar.rs1750mod qux;17511752//- /foo/bar/qux.rs1753mod quux;17541755//- /foo/bar/qux/quux/mod.rs1756// empty17571758//- /foo/corge.rs1759// empty1760"#,1761 expect![[r#"1762 source_file_edits: [1763 (1764 FileId(1765 0,1766 ),1767 [1768 Indel {1769 insert: "foo2",1770 delete: 4..7,1771 },1772 ],1773 ),1774 ]1775 file_system_edits: [1776 MoveFile {1777 src: FileId(1778 1,1779 ),1780 dst: AnchoredPathBuf {1781 anchor: FileId(1782 1,1783 ),1784 path: "foo2.rs",1785 },1786 },1787 MoveDir {1788 src: AnchoredPathBuf {1789 anchor: FileId(1790 1,1791 ),1792 path: "foo",1793 },1794 src_id: FileId(1795 1,1796 ),1797 dst: AnchoredPathBuf {1798 anchor: FileId(1799 1,1800 ),1801 path: "foo2",1802 },1803 },1804 ]1805 "#]],1806 )1807 }1808 #[test]1809 fn test_rename_mod_ref_by_super() {1810 check(1811 "baz",1812 r#"1813 mod $0foo {1814 struct X;18151816 mod bar {1817 use super::X;1818 }1819 }1820 "#,1821 r#"1822 mod baz {1823 struct X;18241825 mod bar {1826 use super::X;1827 }1828 }1829 "#,1830 )1831 }18321833 #[test]1834 fn test_rename_mod_in_macro() {1835 check(1836 "bar",1837 r#"1838//- /foo.rs18391840//- /lib.rs1841macro_rules! submodule {1842 ($name:ident) => {1843 mod $name;1844 };1845}18461847submodule!($0foo);1848"#,1849 r#"1850macro_rules! submodule {1851 ($name:ident) => {1852 mod $name;1853 };1854}18551856submodule!(bar);1857"#,1858 )1859 }18601861 #[test]1862 fn test_rename_mod_for_crate_root() {1863 check_expect_will_rename_file(1864 "main",1865 r#"1866//- /lib.rs1867use crate::foo as bar;1868fn foo() {}1869mod bar$0;1870"#,1871 expect![[r#"1872 source_file_edits: []1873 file_system_edits: []1874 "#]],1875 )1876 }18771878 #[test]1879 fn test_rename_mod_to_raw_ident() {1880 check_expect(1881 "r#fn",1882 r#"1883//- /lib.rs1884mod foo$0;18851886fn main() { foo::bar::baz(); }18871888//- /foo.rs1889pub mod bar;18901891//- /foo/bar.rs1892pub fn baz() {}1893"#,1894 expect![[r#"1895 source_file_edits: [1896 (1897 FileId(1898 0,1899 ),1900 [1901 Indel {1902 insert: "r#fn",1903 delete: 4..7,1904 },1905 Indel {1906 insert: "r#fn",1907 delete: 22..25,1908 },1909 ],1910 ),1911 ]1912 file_system_edits: [1913 MoveFile {1914 src: FileId(1915 1,1916 ),1917 dst: AnchoredPathBuf {1918 anchor: FileId(1919 1,1920 ),1921 path: "fn.rs",1922 },1923 },1924 MoveDir {1925 src: AnchoredPathBuf {1926 anchor: FileId(1927 1,1928 ),1929 path: "foo",1930 },1931 src_id: FileId(1932 1,1933 ),1934 dst: AnchoredPathBuf {1935 anchor: FileId(1936 1,1937 ),1938 path: "fn",1939 },1940 },1941 ]1942 "#]],1943 );1944 }19451946 #[test]1947 fn test_rename_mod_from_raw_ident() {1948 check_expect(1949 "foo",1950 r#"1951//- /lib.rs1952mod r#fn$0;19531954fn main() { r#fn::bar::baz(); }19551956//- /fn.rs1957pub mod bar;19581959//- /fn/bar.rs1960pub fn baz() {}1961"#,1962 expect![[r#"1963 source_file_edits: [1964 (1965 FileId(1966 0,1967 ),1968 [1969 Indel {1970 insert: "foo",1971 delete: 4..8,1972 },1973 Indel {1974 insert: "foo",1975 delete: 23..27,1976 },1977 ],1978 ),1979 ]1980 file_system_edits: [1981 MoveFile {1982 src: FileId(1983 1,1984 ),1985 dst: AnchoredPathBuf {1986 anchor: FileId(1987 1,1988 ),1989 path: "foo.rs",1990 },1991 },1992 MoveDir {1993 src: AnchoredPathBuf {1994 anchor: FileId(1995 1,1996 ),1997 path: "fn",1998 },1999 src_id: FileId(2000 1,