File is large — showing lines 1–2,000 of 4,175.
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// 124//125// #### Magic Renames126//127// rust-analyzer supports some special renames that do additional magic:128//129// - **Anonymous lifetime renames**. You can rename `'_` to any lifetime name (the new name must start with `'`),130// and rust-analyzer will automatically add the new lifetime to the list of generic parameters.131// - **`self` renames**. You can rename parameters to/from `self`. Renaming `self` into another name will update132// all callers using method syntax to call the function like an associated function. Renaming to `self` is only133// supported for the first parameter inside an `impl` and when the `Self` type matches the type of the parameter,134// and will update callers to use method call syntax.135pub(crate) fn rename(136 db: &RootDatabase,137 position: FilePosition,138 new_name: &str,139 config: &RenameConfig,140) -> RenameResult<SourceChange> {141 let sema = Semantics::new(db);142 let file_id = sema143 .attach_first_edition_opt(position.file_id)144 .ok_or_else(|| format_err!("No references found at position"))?;145 let source_file = sema.parse(file_id);146 let syntax = source_file.syntax();147148 let edition = file_id.edition(db);149 let (new_name, kind) = IdentifierKind::classify(edition, new_name)?;150 if kind == IdentifierKind::Lifetime151 && let Some(lifetime_token) =152 syntax.token_at_offset(position.offset).find(|t| t.text() == "'_")153 {154 let new_name_str = new_name.display(db, edition).to_string();155 return rename_elided_lifetime(position, lifetime_token, &new_name_str);156 }157158 let defs = find_definitions(&sema, syntax, position, &new_name)?;159 let alias_fallback =160 alias_fallback(syntax, position, &new_name.display(db, edition).to_string());161162 let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {163 Some(_) => ok_if_any(164 defs165 // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can166 // properly find "direct" usages/references.167 .map(|(.., def, new_name, _)| {168 match kind {169 IdentifierKind::Ident => (),170 IdentifierKind::Lifetime => {171 bail!("Cannot alias reference to a lifetime identifier")172 }173 IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),174 IdentifierKind::LowercaseSelf => {175 bail!("Cannot rename alias reference to `self`")176 }177 };178 let mut usages = def.usages(&sema).all();179180 // FIXME: hack - removes the usage that triggered this rename operation.181 match usages.references.get_mut(&file_id).and_then(|refs| {182 refs.iter()183 .position(|ref_| ref_.range.contains_inclusive(position.offset))184 .map(|idx| refs.remove(idx))185 }) {186 Some(_) => (),187 None => never!(),188 };189190 let mut source_change = SourceChange::default();191 source_change.extend(usages.references.get_mut(&file_id).iter().map(|refs| {192 (193 position.file_id,194 source_edit_from_references(db, refs, def, &new_name, edition),195 )196 }));197198 Ok(source_change)199 }),200 ),201 None => ok_if_any(defs.map(|(.., def, new_name, rename_def)| {202 if let Definition::Local(local) = def {203 if let Some(self_param) = local.as_self_param(sema.db) {204 cov_mark::hit!(rename_self_to_param);205 return rename_self_to_param(206 &sema,207 local,208 self_param,209 &new_name,210 kind,211 config.find_path_config(),212 );213 }214 if kind == IdentifierKind::LowercaseSelf {215 cov_mark::hit!(rename_to_self);216 return rename_to_self(&sema, local);217 }218 }219 def.rename(&sema, new_name.as_str(), rename_def, &config.ide_db_config())220 })),221 };222223 ops?.into_iter()224 .chain(alias_fallback)225 .reduce(|acc, elem| acc.merge(elem))226 .ok_or_else(|| format_err!("No references found at position"))227}228229/// Called by the client when it is about to rename a file.230pub(crate) fn will_rename_file(231 db: &RootDatabase,232 file_id: FileId,233 new_name_stem: &str,234 config: &RenameConfig,235) -> Option<SourceChange> {236 let sema = Semantics::new(db);237 let module = sema.file_to_module_def(file_id)?;238 let def = Definition::Module(module);239 let mut change =240 def.rename(&sema, new_name_stem, RenameDefinition::Yes, &config.ide_db_config()).ok()?;241 change.file_system_edits.clear();242 Some(change)243}244245// FIXME: Should support `extern crate`.246fn alias_fallback(247 syntax: &SyntaxNode,248 FilePosition { file_id, offset }: FilePosition,249 new_name: &str,250) -> Option<SourceChange> {251 let use_tree = syntax252 .token_at_offset(offset)253 .flat_map(|syntax| syntax.parent_ancestors())254 .find_map(ast::UseTree::cast)?;255256 let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;257 if !last_path_segment.syntax().text_range().contains_inclusive(offset) {258 return None;259 };260261 let mut builder = SourceChangeBuilder::new(file_id);262263 match use_tree.rename() {264 Some(rename) => {265 let offset = rename.syntax().text_range();266 builder.replace(offset, format!("as {new_name}"));267 }268 None => {269 let offset = use_tree.syntax().text_range().end();270 builder.insert(offset, format!(" as {new_name}"));271 }272 }273274 Some(builder.finish())275}276277fn find_definitions<'db>(278 sema: &Semantics<'db, RootDatabase>,279 syntax: &SyntaxNode,280 FilePosition { file_id, offset }: FilePosition,281 new_name: &Name,282) -> RenameResult<283 impl Iterator<Item = (FileRange, SyntaxKind, Definition<'db>, Name, RenameDefinition)>,284> {285 let maybe_format_args =286 syntax.token_at_offset(offset).find(|t| matches!(t.kind(), SyntaxKind::STRING));287288 if let Some((range, _, _, Some(resolution))) =289 maybe_format_args.and_then(|token| sema.check_for_format_args_template(token, offset))290 {291 return Ok(vec![(292 FileRange { file_id, range },293 SyntaxKind::STRING,294 Definition::from(resolution),295 new_name.clone(),296 RenameDefinition::Yes,297 )]298 .into_iter());299 }300301 let original_ident = syntax302 .token_at_offset(offset)303 .max_by_key(|t| {304 t.kind().is_any_identifier() || matches!(t.kind(), SyntaxKind::LIFETIME_IDENT)305 })306 .map(|t| {307 if t.kind() == SyntaxKind::LIFETIME_IDENT {308 Name::new_lifetime(t.text())309 } else {310 Name::new_root(t.text())311 }312 })313 .ok_or_else(|| format_err!("No references found at position"))?;314 let symbols =315 sema.find_namelike_at_offset_with_descend(syntax, offset).map(|name_like| {316 let kind = name_like.syntax().kind();317 let range = sema318 .original_range_opt(name_like.syntax())319 .ok_or_else(|| format_err!("No references found at position"))?;320 let res = match &name_like {321 // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet322 ast::NameLike::Name(name)323 if name324 .syntax()325 .parent().is_some_and(|it| ast::Rename::can_cast(it.kind()))326 // FIXME: uncomment this once we resolve to usages to extern crate declarations327 // && name328 // .syntax()329 // .ancestors()330 // .nth(2)331 // .map_or(true, |it| !ast::ExternCrate::can_cast(it.kind()))332 =>333 {334 bail!("Renaming aliases is currently unsupported")335 }336 ast::NameLike::Name(name) => NameClass::classify(sema, name)337 .map(|class| match class {338 NameClass::Definition(it) | NameClass::ConstReference(it) => it,339 NameClass::PatFieldShorthand { local_def, field_ref: _, adt_subst: _ } => {340 Definition::Local(local_def)341 }342 })343 .ok_or_else(|| format_err!("No references found at position")),344 ast::NameLike::NameRef(name_ref) => {345 NameRefClass::classify(sema, name_ref)346 .map(|class| match class {347 NameRefClass::Definition(def, _) => def,348 NameRefClass::FieldShorthand { local_ref, field_ref: _, adt_subst: _ } => {349 Definition::Local(local_ref)350 }351 NameRefClass::ExternCrateShorthand { decl, .. } => {352 Definition::ExternCrateDecl(decl)353 }354 })355 // FIXME: uncomment this once we resolve to usages to extern crate declarations356 .filter(|def| !matches!(def, Definition::ExternCrateDecl(..)))357 .ok_or_else(|| format_err!("No references found at position"))358 .and_then(|def| {359 // if the name differs from the definitions name it has to be an alias360 if def361 .name(sema.db).is_some_and(|it| it.as_str() != name_ref.text().trim_start_matches("r#"))362 {363 Err(format_err!("Renaming aliases is currently unsupported"))364 } else {365 Ok(def)366 }367 })368 }369 ast::NameLike::Lifetime(lifetime) => {370 NameRefClass::classify_lifetime(sema, lifetime)371 .and_then(|class| match class {372 NameRefClass::Definition(def, _) => Some(def),373 _ => None,374 })375 .or_else(|| {376 NameClass::classify_lifetime(sema, lifetime).and_then(|it| match it {377 NameClass::Definition(it) => Some(it),378 _ => None,379 })380 })381 .ok_or_else(|| format_err!("No references found at position"))382 }383 };384 res.map(|def| {385 let n = def.name(sema.db)?;386 if n == original_ident {387 Some((range, kind, def, new_name.clone(), RenameDefinition::Yes))388 } else if let Some(suffix) = n.as_str().strip_prefix(original_ident.as_str()) {389 Some((range, kind, def, Name::new_root(&format!("{}{suffix}", new_name.as_str())), RenameDefinition::No))390 } else {391 n.as_str().strip_suffix(original_ident.as_str().trim_start_matches('\''))392 .map(|prefix| (range, kind, def, Name::new_root(&format!("{prefix}{}", new_name.as_str())), RenameDefinition::No))393 }394 })395 });396397 let res: RenameResult<Vec<_>> = ok_if_any(symbols.filter_map(Result::transpose));398 match res {399 Ok(v) => {400 // remove duplicates, comparing `Definition`s401 Ok(v.into_iter()402 .unique_by(|&(.., def, _, _)| def)403 .map(|(a, b, c, d, e)| (a.into_file_id(sema.db), b, c, d, e))404 .collect::<Vec<_>>()405 .into_iter())406 }407 Err(e) => Err(e),408 }409}410411fn transform_assoc_fn_into_method_call(412 sema: &Semantics<'_, RootDatabase>,413 source_change: &mut SourceChange,414 f: hir::Function,415) {416 let calls = Definition::Function(f).usages(sema).all();417 for (_file_id, calls) in calls {418 for call in calls {419 let Some(fn_name) = call.name.as_name_ref() else { continue };420 let Some(path) = fn_name.syntax().parent().and_then(ast::PathSegment::cast) else {421 continue;422 };423 let path = path.parent_path();424 // The `PathExpr` is the direct parent, above it is the `CallExpr`.425 let Some(call) =426 path.syntax().parent().and_then(|it| ast::CallExpr::cast(it.parent()?))427 else {428 continue;429 };430431 let Some(arg_list) = call.arg_list() else { continue };432 let mut args = arg_list.args();433 let Some(mut self_arg) = args.next() else { continue };434 let second_arg = args.next();435436 // Strip (de)references, as they will be taken automatically by auto(de)ref.437 loop {438 let self_ = match &self_arg {439 ast::Expr::RefExpr(self_) => self_.expr(),440 ast::Expr::ParenExpr(self_) => self_.expr(),441 ast::Expr::PrefixExpr(self_)442 if self_.op_kind() == Some(ast::UnaryOp::Deref) =>443 {444 self_.expr()445 }446 _ => break,447 };448 self_arg = match self_ {449 Some(it) => it,450 None => break,451 };452 }453454 let self_needs_parens =455 self_arg.precedence().needs_parentheses_in(ExprPrecedence::Postfix);456457 let replace_start = path.syntax().text_range().start();458 let replace_end = match second_arg {459 Some(second_arg) => second_arg.syntax().text_range().start(),460 None => arg_list461 .r_paren_token()462 .map(|it| it.text_range().start())463 .unwrap_or_else(|| arg_list.syntax().text_range().end()),464 };465 let replace_range = TextRange::new(replace_start, replace_end);466 let macro_file = sema.hir_file_for(fn_name.syntax());467 let Some((replace_range, _)) =468 InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)469 else {470 continue;471 };472473 let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {474 continue;475 };476 let mut replacement = String::new();477 if self_needs_parens {478 replacement.push('(');479 }480 replacement.push_str(macro_mapped_self.text(sema.db));481 if self_needs_parens {482 replacement.push(')');483 }484 replacement.push('.');485 format_to!(replacement, "{fn_name}");486 replacement.push('(');487488 source_change.insert_source_edit(489 replace_range.file_id.file_id(sema.db),490 TextEdit::replace(replace_range.range, replacement),491 );492 }493 }494}495496fn rename_to_self<'db>(497 sema: &Semantics<'db, RootDatabase>,498 local: hir::Local<'db>,499) -> RenameResult<SourceChange> {500 if never!(local.is_self(sema.db)) {501 bail!("rename_to_self invoked on self");502 }503504 let fn_def = match local.parent(sema.db) {505 hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,506 _ => bail!("Cannot rename local to self outside of function"),507 };508509 if fn_def.self_param(sema.db).is_some() {510 bail!("Method already has a self parameter");511 }512513 let params = fn_def.assoc_fn_params(sema.db);514 let first_param = params515 .first()516 .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?;517 match first_param.as_local(sema.db) {518 Some(plocal) => {519 if plocal != local {520 bail!("Only the first parameter may be renamed to self");521 }522 }523 None => bail!("rename_to_self invoked on destructuring parameter"),524 }525526 let assoc_item = fn_def527 .as_assoc_item(sema.db)528 .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?;529 let impl_ = match assoc_item.container(sema.db) {530 hir::AssocItemContainer::Trait(_) => {531 bail!("Cannot rename parameter to self for trait functions");532 }533 hir::AssocItemContainer::Impl(impl_) => impl_,534 };535 let first_param_ty = first_param.ty();536 let impl_ty = impl_.self_ty(sema.db);537 let (ty, self_param) = if impl_ty.is_reference() {538 // if the impl is a ref to the type we can just match the `&T` with self directly539 (first_param_ty.clone(), "self")540 } else {541 first_param_ty.as_reference_inner().map_or((first_param_ty.clone(), "self"), |ty| {542 (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" })543 })544 };545546 if ty != impl_ty {547 bail!("Parameter type differs from impl block type");548 }549550 let InFile { file_id, value: param_source } = sema551 .source(first_param.clone())552 .ok_or_else(|| format_err!("No source for parameter found"))?;553554 let def = Definition::Local(local);555 let usages = def.usages(sema).all();556 let mut source_change = SourceChange::default();557 source_change.extend(usages.iter().map(|(file_id, references)| {558 (559 file_id.file_id(sema.db),560 source_edit_from_references(561 sema.db,562 references,563 def,564 &Name::new_symbol_root(sym::self_),565 file_id.edition(sema.db),566 ),567 )568 }));569 source_change.insert_source_edit(570 file_id.original_file(sema.db).file_id(sema.db),571 TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)),572 );573 transform_assoc_fn_into_method_call(sema, &mut source_change, fn_def);574 Ok(source_change)575}576577#[derive(Debug, Clone, Copy, PartialEq, Eq)]578enum CallReceiverAdjust {579 Deref,580 Ref,581 RefMut,582 None,583}584585fn method_to_assoc_fn_call_self_adjust(586 sema: &Semantics<'_, RootDatabase>,587 self_arg: &ast::Expr,588) -> CallReceiverAdjust {589 let mut result = CallReceiverAdjust::None;590 let self_adjust = sema.expr_adjustments(self_arg);591 if let Some(self_adjust) = self_adjust {592 let mut i = 0;593 while i < self_adjust.len() {594 if matches!(self_adjust[i].kind, hir::Adjust::Deref(..))595 && matches!(596 self_adjust.get(i + 1),597 Some(hir::Adjustment { kind: hir::Adjust::Borrow(..), .. })598 )599 {600 // Deref then ref (reborrow), skip them.601 i += 2;602 continue;603 }604605 match self_adjust[i].kind {606 hir::Adjust::Deref(_) if result == CallReceiverAdjust::None => {607 // Autoref takes precedence over deref, because if given a `&Type` the compiler will deref608 // it automatically.609 result = CallReceiverAdjust::Deref;610 }611 hir::Adjust::Borrow(hir::AutoBorrow::Ref(mutability)) => {612 match (result, mutability) {613 (CallReceiverAdjust::RefMut, hir::Mutability::Shared) => {}614 (_, hir::Mutability::Mut) => result = CallReceiverAdjust::RefMut,615 (_, hir::Mutability::Shared) => result = CallReceiverAdjust::Ref,616 }617 }618 _ => {}619 }620621 i += 1;622 }623 }624 result625}626627fn transform_method_call_into_assoc_fn(628 sema: &Semantics<'_, RootDatabase>,629 source_change: &mut SourceChange,630 f: hir::Function,631 find_path_config: FindPathConfig,632) {633 let calls = Definition::Function(f).usages(sema).all();634 for (_file_id, calls) in calls {635 for call in calls {636 let Some(fn_name) = call.name.as_name_ref() else { continue };637 let Some(method_call) = fn_name.syntax().parent().and_then(ast::MethodCallExpr::cast)638 else {639 continue;640 };641 let Some(mut self_arg) = method_call.receiver() else {642 continue;643 };644645 let Some(scope) = sema.scope(fn_name.syntax()) else {646 continue;647 };648 let self_adjust = method_to_assoc_fn_call_self_adjust(sema, &self_arg);649650 // Strip parentheses, function arguments have higher precedence than any operator.651 while let ast::Expr::ParenExpr(it) = &self_arg {652 self_arg = match it.expr() {653 Some(it) => it,654 None => break,655 };656 }657658 let needs_comma = method_call.arg_list().is_some_and(|it| it.args().next().is_some());659660 let self_needs_parens = self_adjust != CallReceiverAdjust::None661 && self_arg.precedence().needs_parentheses_in(ExprPrecedence::Prefix);662663 let replace_start = method_call.syntax().text_range().start();664 let replace_end = method_call665 .arg_list()666 .and_then(|it| it.l_paren_token())667 .map(|it| it.text_range().end())668 .unwrap_or_else(|| method_call.syntax().text_range().end());669 let replace_range = TextRange::new(replace_start, replace_end);670 let macro_file = sema.hir_file_for(fn_name.syntax());671 let Some((replace_range, _)) =672 InFile::new(macro_file, replace_range).original_node_file_range_opt(sema.db)673 else {674 continue;675 };676677 let fn_container_path = match f.container(sema.db) {678 hir::ItemContainer::Trait(trait_) => {679 // FIXME: We always put it as `Trait::function`. Is it better to use `Type::function` (but680 // that could conflict with an inherent method)? Or maybe `<Type as Trait>::function`?681 // Or let the user decide?682 let Some(path) = scope.module().find_path(683 sema.db,684 hir::ItemInNs::Types(trait_.into()),685 find_path_config,686 ) else {687 continue;688 };689 path.display(sema.db, replace_range.file_id.edition(sema.db)).to_string()690 }691 hir::ItemContainer::Impl(impl_) => {692 let ty = impl_.self_ty(sema.db);693 match ty.as_adt() {694 Some(adt) => {695 let Some(path) = scope.module().find_path(696 sema.db,697 hir::ItemInNs::Types(adt.into()),698 find_path_config,699 ) else {700 continue;701 };702 path.display(sema.db, replace_range.file_id.edition(sema.db))703 .to_string()704 }705 None => {706 let Ok(mut ty) =707 ty.display_source_code(sema.db, scope.module().into(), false)708 else {709 continue;710 };711 ty.insert(0, '<');712 ty.push('>');713 ty714 }715 }716 }717 _ => continue,718 };719720 let Some(macro_mapped_self) = sema.original_range_opt(self_arg.syntax()) else {721 continue;722 };723 let mut replacement = String::new();724 replacement.push_str(&fn_container_path);725 replacement.push_str("::");726 format_to!(replacement, "{fn_name}");727 replacement.push('(');728 replacement.push_str(match self_adjust {729 CallReceiverAdjust::Deref => "*",730 CallReceiverAdjust::Ref => "&",731 CallReceiverAdjust::RefMut => "&mut ",732 CallReceiverAdjust::None => "",733 });734 if self_needs_parens {735 replacement.push('(');736 }737 replacement.push_str(macro_mapped_self.text(sema.db));738 if self_needs_parens {739 replacement.push(')');740 }741 if needs_comma {742 replacement.push_str(", ");743 }744745 source_change.insert_source_edit(746 replace_range.file_id.file_id(sema.db),747 TextEdit::replace(replace_range.range, replacement),748 );749 }750 }751}752753fn rename_self_to_param<'db>(754 sema: &Semantics<'db, RootDatabase>,755 local: hir::Local<'db>,756 self_param: hir::SelfParam,757 new_name: &Name,758 identifier_kind: IdentifierKind,759 find_path_config: FindPathConfig,760) -> RenameResult<SourceChange> {761 if identifier_kind == IdentifierKind::LowercaseSelf {762 // Let's do nothing rather than complain.763 cov_mark::hit!(rename_self_to_self);764 return Ok(SourceChange::default());765 }766767 let fn_def = match local.parent(sema.db) {768 hir::ExpressionStoreOwner::Body(hir::DefWithBody::Function(func)) => func,769 _ => bail!("Cannot rename local to self outside of function"),770 };771772 let InFile { file_id, value: self_param } =773 sema.source(self_param).ok_or_else(|| format_err!("cannot find function source"))?;774775 let def = Definition::Local(local);776 let usages = def.usages(sema).all();777 let edit = text_edit_from_self_param(778 &self_param,779 new_name.display(sema.db, file_id.edition(sema.db)).to_string(),780 )781 .ok_or_else(|| format_err!("No target type found"))?;782 if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore {783 bail!("Cannot rename reference to `_` as it is being referenced multiple times");784 }785 let mut source_change = SourceChange::default();786 source_change.insert_source_edit(file_id.original_file(sema.db).file_id(sema.db), edit);787 source_change.extend(usages.iter().map(|(file_id, references)| {788 (789 file_id.file_id(sema.db),790 source_edit_from_references(791 sema.db,792 references,793 def,794 new_name,795 file_id.edition(sema.db),796 ),797 )798 }));799 transform_method_call_into_assoc_fn(sema, &mut source_change, fn_def, find_path_config);800 Ok(source_change)801}802803fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: String) -> Option<TextEdit> {804 let mut replacement_text = new_name;805 replacement_text.push_str(": ");806807 if self_param.amp_token().is_some() {808 replacement_text.push('&');809 }810 if let Some(lifetime) = self_param.lifetime() {811 write!(replacement_text, "{lifetime} ").unwrap();812 }813 if self_param.amp_token().and(self_param.mut_token()).is_some() {814 replacement_text.push_str("mut ");815 }816817 replacement_text.push_str("Self");818819 Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text))820}821822fn rename_elided_lifetime(823 position: FilePosition,824 lifetime_token: syntax::SyntaxToken,825 new_name: &str,826) -> RenameResult<SourceChange> {827 let parent = lifetime_token.parent().unwrap();828 let root = parent.tree_top();829830 let mut builder = SourceChangeBuilder::new(position.file_id);831832 let editor = builder.make_editor(&root);833 let make = editor.make();834835 editor.replace(lifetime_token, make.lifetime(new_name).syntax().clone());836837 if let Some(has_generic_params) = parent.ancestors().find_map(ast::AnyHasGenericParams::cast) {838 let lifetime_param = make.lifetime_param(make.lifetime(new_name));839 editor.add_generic_param(&has_generic_params, lifetime_param.into());840 }841842 builder.add_file_edits(position.file_id, editor);843844 Ok(builder.finish())845}846847#[cfg(test)]848mod tests {849 use expect_test::{Expect, expect};850 use ide_db::source_change::SourceChange;851 use ide_db::text_edit::TextEdit;852 use itertools::Itertools;853 use stdx::trim_indent;854 use test_utils::assert_eq_text;855856 use crate::fixture;857858 use super::{RangeInfo, RenameConfig, RenameError};859860 const TEST_CONFIG: RenameConfig = RenameConfig {861 prefer_no_std: false,862 prefer_prelude: true,863 prefer_absolute: false,864 show_conflicts: true,865 };866867 #[track_caller]868 fn check(869 new_name: &str,870 #[rust_analyzer::rust_fixture] ra_fixture_before: &str,871 #[rust_analyzer::rust_fixture] ra_fixture_after: &str,872 ) {873 let ra_fixture_after = &trim_indent(ra_fixture_after);874 let (analysis, position) = fixture::position(ra_fixture_before);875 if !ra_fixture_after.starts_with("error: ")876 && let Err(err) = analysis.prepare_rename(position).unwrap()877 {878 panic!("Prepare rename to '{new_name}' was failed: {err}")879 }880 let rename_result = analysis881 .rename(position, new_name, &TEST_CONFIG)882 .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));883 match rename_result {884 Ok(source_change) => {885 let mut text_edit_builder = TextEdit::builder();886 let (&file_id, edit) = match source_change.source_file_edits.len() {887 0 => return,888 1 => source_change.source_file_edits.iter().next().unwrap(),889 _ => panic!(),890 };891 for indel in edit.0.iter() {892 text_edit_builder.replace(indel.delete, indel.insert.clone());893 }894 let mut result = analysis.file_text(file_id).unwrap().to_string();895 text_edit_builder.finish().apply(&mut result);896 assert_eq_text!(ra_fixture_after, &*result);897 }898 Err(err) => {899 if ra_fixture_after.starts_with("error:") {900 let error_message =901 ra_fixture_after.chars().skip("error:".len()).collect::<String>();902 assert_eq!(error_message.trim(), err.to_string());903 } else {904 panic!("Rename to '{new_name}' failed unexpectedly: {err}")905 }906 }907 };908 }909910 #[track_caller]911 fn check_conflicts(new_name: &str, #[rust_analyzer::rust_fixture] ra_fixture: &str) {912 let (analysis, position, conflicts) = fixture::annotations(ra_fixture);913 let source_change = analysis.rename(position, new_name, &TEST_CONFIG).unwrap().unwrap();914 let expected_conflicts = conflicts915 .into_iter()916 .map(|(file_range, _)| (file_range.file_id, file_range.range))917 .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))918 .collect_vec();919 let found_conflicts = source_change920 .source_file_edits921 .iter()922 .filter(|(_, (edit, _))| edit.change_annotation().is_some())923 .flat_map(|(file_id, (edit, _))| {924 edit.into_iter().map(move |edit| (*file_id, edit.delete))925 })926 .sorted_unstable_by_key(|(file_id, range)| (*file_id, range.start()))927 .collect_vec();928 assert_eq!(929 expected_conflicts, found_conflicts,930 "rename conflicts mismatch: {source_change:#?}"931 );932 }933934 fn check_expect(935 new_name: &str,936 #[rust_analyzer::rust_fixture] ra_fixture: &str,937 expect: Expect,938 ) {939 let (analysis, position) = fixture::position(ra_fixture);940 let source_change = analysis941 .rename(position, new_name, &TEST_CONFIG)942 .unwrap()943 .expect("Expect returned a RenameError");944 expect.assert_eq(&filter_expect(source_change))945 }946947 fn check_expect_will_rename_file(948 new_name: &str,949 #[rust_analyzer::rust_fixture] ra_fixture: &str,950 expect: Expect,951 ) {952 let (analysis, position) = fixture::position(ra_fixture);953 let source_change = analysis954 .will_rename_file(position.file_id, new_name, &TEST_CONFIG)955 .unwrap()956 .expect("Expect returned a RenameError");957 expect.assert_eq(&filter_expect(source_change))958 }959960 fn check_prepare(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {961 let (analysis, position) = fixture::position(ra_fixture);962 let result = analysis963 .prepare_rename(position)964 .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {err}"));965 match result {966 Ok(RangeInfo { range, info: () }) => {967 let source = analysis.file_text(position.file_id).unwrap();968 expect.assert_eq(&format!("{range:?}: {}", &source[range]))969 }970 Err(RenameError(err)) => expect.assert_eq(&err),971 };972 }973974 fn filter_expect(source_change: SourceChange) -> String {975 let source_file_edits = source_change976 .source_file_edits977 .into_iter()978 .map(|(id, (text_edit, _))| (id, text_edit.into_iter().collect::<Vec<_>>()))979 .collect::<Vec<_>>();980981 format!(982 "source_file_edits: {:#?}\nfile_system_edits: {:#?}\n",983 source_file_edits, source_change.file_system_edits984 )985 }986987 #[test]988 fn rename_will_shadow() {989 check_conflicts(990 "new_name",991 r#"992fn foo() {993 let mut new_name = 123;994 let old_name$0 = 456;995 // ^^^^^^^^996 new_name = 789 + new_name;997}998 "#,999 );1000 }10011002 #[test]1003 fn rename_will_be_shadowed() {1004 check_conflicts(1005 "new_name",1006 r#"1007fn foo() {1008 let mut old_name$0 = 456;1009 // ^^^^^^^^1010 let new_name = 123;1011 old_name = 789 + old_name;1012 // ^^^^^^^^ ^^^^^^^^1013}1014 "#,1015 );1016 }10171018 #[test]1019 fn test_prepare_rename_namelikes() {1020 check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]);1021 check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"9..17: lifetime"#]]);1022 check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"23..27: name"#]]);1023 }10241025 #[test]1026 fn test_prepare_rename_in_macro() {1027 check_prepare(1028 r"macro_rules! foo {1029 ($ident:ident) => {1030 pub struct $ident;1031 }1032}1033foo!(Foo$0);",1034 expect![[r#"83..86: Foo"#]],1035 );1036 }10371038 #[test]1039 fn test_prepare_rename_keyword() {1040 check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]);1041 }10421043 #[test]1044 fn test_prepare_rename_tuple_field() {1045 check_prepare(1046 r#"1047struct Foo(i32);10481049fn baz() {1050 let mut x = Foo(4);1051 x.0$0 = 5;1052}1053"#,1054 expect![[r#"No references found at position"#]],1055 );1056 }10571058 #[test]1059 fn test_prepare_rename_builtin() {1060 check_prepare(1061 r#"1062fn foo() {1063 let x: i32$0 = 0;1064}1065"#,1066 expect![[r#"No references found at position"#]],1067 );1068 }10691070 #[test]1071 fn test_prepare_rename_self() {1072 check_prepare(1073 r#"1074struct Foo {}10751076impl Foo {1077 fn foo(self) -> Self$0 {1078 self1079 }1080}1081"#,1082 expect![[r#"No references found at position"#]],1083 );1084 }10851086 #[test]1087 fn test_rename_to_underscore() {1088 check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#);1089 }10901091 #[test]1092 fn test_rename_to_raw_identifier() {1093 check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#);1094 }10951096 #[test]1097 fn test_rename_to_invalid_identifier1() {1098 check(1099 "invalid!",1100 r#"fn main() { let i$0 = 1; }"#,1101 "error: Invalid name `invalid!`: not an identifier",1102 );1103 }11041105 #[test]1106 fn test_rename_to_invalid_identifier2() {1107 check(1108 "multiple tokens",1109 r#"fn main() { let i$0 = 1; }"#,1110 "error: Invalid name `multiple tokens`: not an identifier",1111 );1112 }11131114 #[test]1115 fn test_rename_to_invalid_identifier3() {1116 check(1117 "super",1118 r#"fn main() { let i$0 = 1; }"#,1119 "error: Invalid name `super`: cannot rename to a keyword",1120 );1121 }11221123 #[test]1124 fn test_rename_to_invalid_identifier_lifetime() {1125 cov_mark::check!(rename_not_an_ident_ref);1126 check(1127 "'foo",1128 r#"fn main() { let i$0 = 1; }"#,1129 "error: Invalid name `'foo`: not an identifier",1130 );1131 }11321133 #[test]1134 fn test_rename_to_invalid_identifier_lifetime2() {1135 check(1136 "_",1137 r#"fn main<'a>(_: &'a$0 ()) {}"#,1138 r#"error: Invalid name `_`: not a lifetime identifier"#,1139 );1140 }11411142 #[test]1143 fn test_rename_accepts_lifetime_without_apostrophe() {1144 check("foo", r#"fn main<'a>(_: &'a$0 ()) {}"#, r#"fn main<'foo>(_: &'foo ()) {}"#);1145 }11461147 #[test]1148 fn test_rename_to_underscore_invalid() {1149 cov_mark::check!(rename_underscore_multiple);1150 check(1151 "_",1152 r#"fn main(foo$0: ()) {foo;}"#,1153 "error: Cannot rename reference to `_` as it is being referenced multiple times",1154 );1155 }11561157 #[test]1158 fn test_rename_mod_invalid() {1159 check(1160 "'foo",1161 r#"mod foo$0 {}"#,1162 "error: Invalid name `'foo`: cannot rename module to 'foo",1163 );1164 }11651166 #[test]1167 fn test_rename_mod_invalid_raw_ident() {1168 check(1169 "r#self",1170 r#"mod foo$0 {}"#,1171 "error: Invalid name `self`: cannot rename module to self",1172 );1173 }11741175 #[test]1176 fn test_rename_for_local() {1177 check(1178 "k",1179 r#"1180fn main() {1181 let mut i = 1;1182 let j = 1;1183 i = i$0 + j;11841185 { i = 0; }11861187 i = 5;1188}1189"#,1190 r#"1191fn main() {1192 let mut k = 1;1193 let j = 1;1194 k = k + j;11951196 { k = 0; }11971198 k = 5;1199}1200"#,1201 );1202 }12031204 #[test]1205 fn test_rename_unresolved_reference() {1206 check(1207 "new_name",1208 r#"fn main() { let _ = unresolved_ref$0; }"#,1209 "error: No references found at position",1210 );1211 }12121213 #[test]1214 fn test_rename_macro_multiple_occurrences() {1215 check(1216 "Baaah",1217 r#"macro_rules! foo {1218 ($ident:ident) => {1219 const $ident: () = ();1220 struct $ident {}1221 };1222}12231224foo!($0Foo);1225const _: () = Foo;1226const _: Foo = Foo {};1227 "#,1228 r#"1229macro_rules! foo {1230 ($ident:ident) => {1231 const $ident: () = ();1232 struct $ident {}1233 };1234}12351236foo!(Baaah);1237const _: () = Baaah;1238const _: Baaah = Baaah {};1239 "#,1240 )1241 }12421243 #[test]1244 fn test_rename_for_macro_args() {1245 check(1246 "b",1247 r#"1248macro_rules! foo {($i:ident) => {$i} }1249fn main() {1250 let a$0 = "test";1251 foo!(a);1252}1253"#,1254 r#"1255macro_rules! foo {($i:ident) => {$i} }1256fn main() {1257 let b = "test";1258 foo!(b);1259}1260"#,1261 );1262 }12631264 #[test]1265 fn test_rename_for_macro_args_rev() {1266 check(1267 "b",1268 r#"1269macro_rules! foo {($i:ident) => {$i} }1270fn main() {1271 let a = "test";1272 foo!(a$0);1273}1274"#,1275 r#"1276macro_rules! foo {($i:ident) => {$i} }1277fn main() {1278 let b = "test";1279 foo!(b);1280}1281"#,1282 );1283 }12841285 #[test]1286 fn test_rename_for_macro_define_fn() {1287 check(1288 "bar",1289 r#"1290macro_rules! define_fn {($id:ident) => { fn $id{} }}1291define_fn!(foo);1292fn main() {1293 fo$0o();1294}1295"#,1296 r#"1297macro_rules! define_fn {($id:ident) => { fn $id{} }}1298define_fn!(bar);1299fn main() {1300 bar();1301}1302"#,1303 );1304 }13051306 #[test]1307 fn test_rename_for_macro_define_fn_rev() {1308 check(1309 "bar",1310 r#"1311macro_rules! define_fn {($id:ident) => { fn $id{} }}1312define_fn!(fo$0o);1313fn main() {1314 foo();1315}1316"#,1317 r#"1318macro_rules! define_fn {($id:ident) => { fn $id{} }}1319define_fn!(bar);1320fn main() {1321 bar();1322}1323"#,1324 );1325 }13261327 #[test]1328 fn test_rename_for_param_inside() {1329 check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#);1330 }13311332 #[test]1333 fn test_rename_refs_for_fn_param() {1334 check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#);1335 }13361337 #[test]1338 fn test_rename_for_mut_param() {1339 check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#);1340 }13411342 #[test]1343 fn test_rename_struct_field() {1344 check(1345 "foo",1346 r#"1347struct Foo { field$0: i32 }13481349impl Foo {1350 fn new(i: i32) -> Self {1351 Self { field: i }1352 }1353}1354"#,1355 r#"1356struct Foo { foo: i32 }13571358impl Foo {1359 fn new(i: i32) -> Self {1360 Self { foo: i }1361 }1362}1363"#,1364 );1365 }13661367 #[test]1368 fn test_rename_field_in_field_shorthand() {1369 cov_mark::check!(test_rename_field_in_field_shorthand);1370 check(1371 "field",1372 r#"1373struct Foo { foo$0: i32 }13741375impl Foo {1376 fn foo(foo: i32) {1377 Self { foo };1378 }1379}1380"#,1381 r#"1382struct Foo { field: i32 }13831384impl Foo {1385 fn foo(foo: i32) {1386 Self { field: foo };1387 }1388}1389"#,1390 );1391 }13921393 #[test]1394 fn test_rename_local_in_field_shorthand() {1395 cov_mark::check!(test_rename_local_in_field_shorthand);1396 check(1397 "j",1398 r#"1399struct Foo { i: i32 }14001401impl Foo {1402 fn new(i$0: i32) -> Self {1403 Self { i }1404 }1405}1406"#,1407 r#"1408struct Foo { i: i32 }14091410impl Foo {1411 fn new(j: i32) -> Self {1412 Self { i: j }1413 }1414}1415"#,1416 );1417 }14181419 #[test]1420 fn test_field_shorthand_correct_struct() {1421 check(1422 "j",1423 r#"1424struct Foo { i$0: i32 }1425struct Bar { i: i32 }14261427impl Bar {1428 fn new(i: i32) -> Self {1429 Self { i }1430 }1431}1432"#,1433 r#"1434struct Foo { j: i32 }1435struct Bar { i: i32 }14361437impl Bar {1438 fn new(i: i32) -> Self {1439 Self { i }1440 }1441}1442"#,1443 );1444 }14451446 #[test]1447 fn test_shadow_local_for_struct_shorthand() {1448 check(1449 "j",1450 r#"1451struct Foo { i: i32 }14521453fn baz(i$0: i32) -> Self {1454 let x = Foo { i };1455 {1456 let i = 0;1457 Foo { i }1458 }1459}1460"#,1461 r#"1462struct Foo { i: i32 }14631464fn baz(j: i32) -> Self {1465 let x = Foo { i: j };1466 {1467 let i = 0;1468 Foo { i }1469 }1470}1471"#,1472 );1473 }14741475 #[test]1476 fn test_rename_mod() {1477 check_expect(1478 "foo2",1479 r#"1480//- /lib.rs1481mod bar;14821483//- /bar.rs1484mod foo$0;14851486//- /bar/foo.rs1487// empty1488"#,1489 expect![[r#"1490 source_file_edits: [1491 (1492 FileId(1493 1,1494 ),1495 [1496 Indel {1497 insert: "foo2",1498 delete: 4..7,1499 },1500 ],1501 ),1502 ]1503 file_system_edits: [1504 MoveFile {1505 src: FileId(1506 2,1507 ),1508 dst: AnchoredPathBuf {1509 anchor: FileId(1510 2,1511 ),1512 path: "foo2.rs",1513 },1514 },1515 ]1516 "#]],1517 );1518 }15191520 #[test]1521 fn test_rename_mod_in_use_tree() {1522 check_expect(1523 "quux",1524 r#"1525//- /main.rs1526pub mod foo;1527pub mod bar;1528fn main() {}15291530//- /foo.rs1531pub struct FooContent;15321533//- /bar.rs1534use crate::foo$0::FooContent;1535"#,1536 expect![[r#"1537 source_file_edits: [1538 (1539 FileId(1540 0,1541 ),1542 [1543 Indel {1544 insert: "quux",1545 delete: 8..11,1546 },1547 ],1548 ),1549 (1550 FileId(1551 2,1552 ),1553 [1554 Indel {1555 insert: "quux",1556 delete: 11..14,1557 },1558 ],1559 ),1560 ]1561 file_system_edits: [1562 MoveFile {1563 src: FileId(1564 1,1565 ),1566 dst: AnchoredPathBuf {1567 anchor: FileId(1568 1,1569 ),1570 path: "quux.rs",1571 },1572 },1573 ]1574 "#]],1575 );1576 }15771578 #[test]1579 fn test_rename_mod_in_dir() {1580 check_expect(1581 "foo2",1582 r#"1583//- /lib.rs1584mod fo$0o;1585//- /foo/mod.rs1586// empty1587"#,1588 expect![[r#"1589 source_file_edits: [1590 (1591 FileId(1592 0,1593 ),1594 [1595 Indel {1596 insert: "foo2",1597 delete: 4..7,1598 },1599 ],1600 ),1601 ]1602 file_system_edits: [1603 MoveDir {1604 src: AnchoredPathBuf {1605 anchor: FileId(1606 1,1607 ),1608 path: "../foo",1609 },1610 src_id: FileId(1611 1,1612 ),1613 dst: AnchoredPathBuf {1614 anchor: FileId(1615 1,1616 ),1617 path: "../foo2",1618 },1619 },1620 ]1621 "#]],1622 );1623 }16241625 #[test]1626 fn test_rename_unusually_nested_mod() {1627 check_expect(1628 "bar",1629 r#"1630//- /lib.rs1631mod outer { mod fo$0o; }16321633//- /outer/foo.rs1634// empty1635"#,1636 expect![[r#"1637 source_file_edits: [1638 (1639 FileId(1640 0,1641 ),1642 [1643 Indel {1644 insert: "bar",1645 delete: 16..19,1646 },1647 ],1648 ),1649 ]1650 file_system_edits: [1651 MoveFile {1652 src: FileId(1653 1,1654 ),1655 dst: AnchoredPathBuf {1656 anchor: FileId(1657 1,1658 ),1659 path: "bar.rs",1660 },1661 },1662 ]1663 "#]],1664 );1665 }16661667 #[test]1668 fn test_module_rename_in_path() {1669 check(1670 "baz",1671 r#"1672mod $0foo {1673 pub use self::bar as qux;1674 pub fn bar() {}1675}16761677fn main() { foo::bar(); }1678"#,1679 r#"1680mod baz {1681 pub use self::bar as qux;1682 pub fn bar() {}1683}16841685fn main() { baz::bar(); }1686"#,1687 );1688 }16891690 #[test]1691 fn test_rename_mod_filename_and_path() {1692 check_expect(1693 "foo2",1694 r#"1695//- /lib.rs1696mod bar;1697fn f() {1698 bar::foo::fun()1699}17001701//- /bar.rs1702pub mod foo$0;17031704//- /bar/foo.rs1705// pub fn fun() {}1706"#,1707 expect![[r#"1708 source_file_edits: [1709 (1710 FileId(1711 0,1712 ),1713 [1714 Indel {1715 insert: "foo2",1716 delete: 27..30,1717 },1718 ],1719 ),1720 (1721 FileId(1722 1,1723 ),1724 [1725 Indel {1726 insert: "foo2",1727 delete: 8..11,1728 },1729 ],1730 ),1731 ]1732 file_system_edits: [1733 MoveFile {1734 src: FileId(1735 2,1736 ),1737 dst: AnchoredPathBuf {1738 anchor: FileId(1739 2,1740 ),1741 path: "foo2.rs",1742 },1743 },1744 ]1745 "#]],1746 );1747 }17481749 #[test]1750 fn test_rename_mod_recursive() {1751 check_expect(1752 "foo2",1753 r#"1754//- /lib.rs1755mod foo$0;17561757//- /foo.rs1758mod bar;1759mod corge;17601761//- /foo/bar.rs1762mod qux;17631764//- /foo/bar/qux.rs1765mod quux;17661767//- /foo/bar/qux/quux/mod.rs1768// empty17691770//- /foo/corge.rs1771// empty1772"#,1773 expect![[r#"1774 source_file_edits: [1775 (1776 FileId(1777 0,1778 ),1779 [1780 Indel {1781 insert: "foo2",1782 delete: 4..7,1783 },1784 ],1785 ),1786 ]1787 file_system_edits: [1788 MoveFile {1789 src: FileId(1790 1,1791 ),1792 dst: AnchoredPathBuf {1793 anchor: FileId(1794 1,1795 ),1796 path: "foo2.rs",1797 },1798 },1799 MoveDir {1800 src: AnchoredPathBuf {1801 anchor: FileId(1802 1,1803 ),1804 path: "foo",1805 },1806 src_id: FileId(1807 1,1808 ),1809 dst: AnchoredPathBuf {1810 anchor: FileId(1811 1,1812 ),1813 path: "foo2",1814 },1815 },1816 ]1817 "#]],1818 )1819 }1820 #[test]1821 fn test_rename_mod_ref_by_super() {1822 check(1823 "baz",1824 r#"1825 mod $0foo {1826 struct X;18271828 mod bar {1829 use super::X;1830 }1831 }1832 "#,1833 r#"1834 mod baz {1835 struct X;18361837 mod bar {1838 use super::X;1839 }1840 }1841 "#,1842 )1843 }18441845 #[test]1846 fn test_rename_mod_in_macro() {1847 check(1848 "bar",1849 r#"1850//- /foo.rs18511852//- /lib.rs1853macro_rules! submodule {1854 ($name:ident) => {1855 mod $name;1856 };1857}18581859submodule!($0foo);1860"#,1861 r#"1862macro_rules! submodule {1863 ($name:ident) => {1864 mod $name;1865 };1866}18671868submodule!(bar);1869"#,1870 )1871 }18721873 #[test]1874 fn test_rename_mod_for_crate_root() {1875 check_expect_will_rename_file(1876 "main",1877 r#"1878//- /lib.rs1879use crate::foo as bar;1880fn foo() {}1881mod bar$0;1882"#,1883 expect![[r#"1884 source_file_edits: []1885 file_system_edits: []1886 "#]],1887 )1888 }18891890 #[test]1891 fn test_rename_mod_to_raw_ident() {1892 check_expect(1893 "r#fn",1894 r#"1895//- /lib.rs1896mod foo$0;18971898fn main() { foo::bar::baz(); }18991900//- /foo.rs1901pub mod bar;19021903//- /foo/bar.rs1904pub fn baz() {}1905"#,1906 expect![[r#"1907 source_file_edits: [1908 (1909 FileId(1910 0,1911 ),1912 [1913 Indel {1914 insert: "r#fn",1915 delete: 4..7,1916 },1917 Indel {1918 insert: "r#fn",1919 delete: 22..25,1920 },1921 ],1922 ),1923 ]1924 file_system_edits: [1925 MoveFile {1926 src: FileId(1927 1,1928 ),1929 dst: AnchoredPathBuf {1930 anchor: FileId(1931 1,1932 ),1933 path: "fn.rs",1934 },1935 },1936 MoveDir {1937 src: AnchoredPathBuf {1938 anchor: FileId(1939 1,1940 ),1941 path: "foo",1942 },1943 src_id: FileId(1944 1,1945 ),1946 dst: AnchoredPathBuf {1947 anchor: FileId(1948 1,1949 ),1950 path: "fn",1951 },1952 },1953 ]1954 "#]],1955 );1956 }19571958 #[test]1959 fn test_rename_mod_from_raw_ident() {1960 check_expect(1961 "foo",1962 r#"1963//- /lib.rs1964mod r#fn$0;19651966fn main() { r#fn::bar::baz(); }19671968//- /fn.rs1969pub mod bar;19701971//- /fn/bar.rs1972pub fn baz() {}1973"#,1974 expect![[r#"1975 source_file_edits: [1976 (1977 FileId(1978 0,1979 ),1980 [1981 Indel {1982 insert: "foo",1983 delete: 4..8,1984 },1985 Indel {1986 insert: "foo",1987 delete: 23..27,1988 },1989 ],1990 ),1991 ]1992 file_system_edits: [1993 MoveFile {1994 src: FileId(1995 1,1996 ),1997 dst: AnchoredPathBuf {1998 anchor: FileId(1999 1,2000 ),