From da534bdd074788e47b5dae76998b8f8535ea7257 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 14 Jun 2021 19:14:34 +0300 Subject: internal: flatten module hierarchy It seems that any crate can be made better by flattening the modules down to a single layer? --- crates/ide/src/lib.rs | 10 +- crates/ide/src/references.rs | 2 - crates/ide/src/references/rename.rs | 1788 ---------------------------------- crates/ide/src/rename.rs | 1789 +++++++++++++++++++++++++++++++++++ 4 files changed, 1795 insertions(+), 1794 deletions(-) delete mode 100644 crates/ide/src/references/rename.rs create mode 100644 crates/ide/src/rename.rs (limited to 'crates') diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index 98d01f0ce..9db387d26 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -39,6 +39,7 @@ mod matching_brace; mod move_item; mod parent_module; mod references; +mod rename; mod fn_references; mod runnables; mod ssr; @@ -79,7 +80,8 @@ pub use crate::{ markup::Markup, move_item::Direction, prime_caches::PrimeCachesProgress, - references::{rename::RenameError, ReferenceSearchResult}, + references::ReferenceSearchResult, + rename::RenameError, runnables::{Runnable, RunnableKind, TestId}, syntax_highlighting::{ tags::{Highlight, HlMod, HlMods, HlOperator, HlPunct, HlTag}, @@ -591,14 +593,14 @@ impl Analysis { position: FilePosition, new_name: &str, ) -> Cancellable> { - self.with_db(|db| references::rename::rename(db, position, new_name)) + self.with_db(|db| rename::rename(db, position, new_name)) } pub fn prepare_rename( &self, position: FilePosition, ) -> Cancellable, RenameError>> { - self.with_db(|db| references::rename::prepare_rename(db, position)) + self.with_db(|db| rename::prepare_rename(db, position)) } pub fn will_rename_file( @@ -606,7 +608,7 @@ impl Analysis { file_id: FileId, new_name_stem: &str, ) -> Cancellable> { - self.with_db(|db| references::rename::will_rename_file(db, file_id, new_name_stem)) + self.with_db(|db| rename::will_rename_file(db, file_id, new_name_stem)) } pub fn structural_search_replace( diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index a0fdead2c..945c9b9e1 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -9,8 +9,6 @@ //! at the index that the match starts at and its tree parent is //! resolved to the search element definition, we get a reference. -pub(crate) mod rename; - use hir::{PathResolution, Semantics}; use ide_db::{ base_db::FileId, diff --git a/crates/ide/src/references/rename.rs b/crates/ide/src/references/rename.rs deleted file mode 100644 index cec1d4552..000000000 --- a/crates/ide/src/references/rename.rs +++ /dev/null @@ -1,1788 +0,0 @@ -//! Renaming functionality -//! -//! All reference and file rename requests go through here where the corresponding [`SourceChange`]s -//! will be calculated. -use hir::{AsAssocItem, InFile, Semantics}; -use ide_db::{ - base_db::FileId, - defs::{Definition, NameClass, NameRefClass}, - rename::{bail, format_err, source_edit_from_references, IdentifierKind}, - RootDatabase, -}; -use stdx::never; -use syntax::{ast, AstNode, SyntaxNode}; - -use text_edit::TextEdit; - -use crate::{FilePosition, RangeInfo, SourceChange}; - -pub use ide_db::rename::RenameError; - -type RenameResult = Result; - -/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is -/// being targeted for a rename. -pub(crate) fn prepare_rename( - db: &RootDatabase, - position: FilePosition, -) -> RenameResult> { - let sema = Semantics::new(db); - let source_file = sema.parse(position.file_id); - let syntax = source_file.syntax(); - - let def = find_definition(&sema, syntax, position)?; - let frange = - def.rename_range(&sema).ok_or_else(|| format_err!("No references found at position"))?; - Ok(RangeInfo::new(frange.range, ())) -} - -// Feature: Rename -// -// Renames the item below the cursor and all of its references -// -// |=== -// | Editor | Shortcut -// -// | VS Code | kbd:[F2] -// |=== -// -// image::https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif[] -pub(crate) fn rename( - db: &RootDatabase, - position: FilePosition, - new_name: &str, -) -> RenameResult { - let sema = Semantics::new(db); - rename_with_semantics(&sema, position, new_name) -} - -pub(crate) fn rename_with_semantics( - sema: &Semantics, - position: FilePosition, - new_name: &str, -) -> RenameResult { - let source_file = sema.parse(position.file_id); - let syntax = source_file.syntax(); - - let def = find_definition(sema, syntax, position)?; - - if let Definition::Local(local) = def { - if let Some(self_param) = local.as_self_param(sema.db) { - cov_mark::hit!(rename_self_to_param); - return rename_self_to_param(sema, local, self_param, new_name); - } - if new_name == "self" { - cov_mark::hit!(rename_to_self); - return rename_to_self(sema, local); - } - } - - def.rename(sema, new_name) -} - -/// Called by the client when it is about to rename a file. -pub(crate) fn will_rename_file( - db: &RootDatabase, - file_id: FileId, - new_name_stem: &str, -) -> Option { - let sema = Semantics::new(db); - let module = sema.to_module_def(file_id)?; - let def = Definition::ModuleDef(module.into()); - let mut change = def.rename(&sema, new_name_stem).ok()?; - change.file_system_edits.clear(); - Some(change) -} - -fn find_definition( - sema: &Semantics, - syntax: &SyntaxNode, - position: FilePosition, -) -> RenameResult { - match sema - .find_node_at_offset_with_descend(syntax, position.offset) - .ok_or_else(|| format_err!("No references found at position"))? - { - // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet - ast::NameLike::Name(name) - if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) => - { - bail!("Renaming aliases is currently unsupported") - } - ast::NameLike::Name(name) => { - NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db)) - } - ast::NameLike::NameRef(name_ref) => { - if let Some(def) = - NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db)) - { - // if the name differs from the definitions name it has to be an alias - if def.name(sema.db).map_or(false, |it| it.to_string() != name_ref.text()) { - bail!("Renaming aliases is currently unsupported"); - } - Some(def) - } else { - None - } - } - ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) - .map(|class| NameRefClass::referenced(class, sema.db)) - .or_else(|| { - NameClass::classify_lifetime(sema, &lifetime) - .map(|it| it.referenced_or_defined(sema.db)) - }), - } - .ok_or_else(|| format_err!("No references found at position")) -} - -fn rename_to_self(sema: &Semantics, local: hir::Local) -> RenameResult { - if never!(local.is_self(sema.db)) { - bail!("rename_to_self invoked on self"); - } - - let fn_def = match local.parent(sema.db) { - hir::DefWithBody::Function(func) => func, - _ => bail!("Cannot rename local to self outside of function"), - }; - - if let Some(_) = fn_def.self_param(sema.db) { - bail!("Method already has a self parameter"); - } - - let params = fn_def.assoc_fn_params(sema.db); - let first_param = params - .first() - .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?; - if first_param.as_local(sema.db) != local { - bail!("Only the first parameter may be renamed to self"); - } - - let assoc_item = fn_def - .as_assoc_item(sema.db) - .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?; - let impl_ = match assoc_item.container(sema.db) { - hir::AssocItemContainer::Trait(_) => { - bail!("Cannot rename parameter to self for trait functions"); - } - hir::AssocItemContainer::Impl(impl_) => impl_, - }; - let first_param_ty = first_param.ty(); - let impl_ty = impl_.self_ty(sema.db); - let (ty, self_param) = if impl_ty.remove_ref().is_some() { - // if the impl is a ref to the type we can just match the `&T` with self directly - (first_param_ty.clone(), "self") - } else { - first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| { - (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" }) - }) - }; - - if ty != impl_ty { - bail!("Parameter type differs from impl block type"); - } - - let InFile { file_id, value: param_source } = - first_param.source(sema.db).ok_or_else(|| format_err!("No source for parameter found"))?; - - let def = Definition::Local(local); - let usages = def.usages(sema).all(); - let mut source_change = SourceChange::default(); - source_change.extend(usages.iter().map(|(&file_id, references)| { - (file_id, source_edit_from_references(references, def, "self")) - })); - source_change.insert_source_edit( - file_id.original_file(sema.db), - TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)), - ); - Ok(source_change) -} - -fn rename_self_to_param( - sema: &Semantics, - local: hir::Local, - self_param: hir::SelfParam, - new_name: &str, -) -> RenameResult { - if new_name == "self" { - // Let's do nothing rather than complain. - cov_mark::hit!(rename_self_to_self); - return Ok(SourceChange::default()); - } - - let identifier_kind = IdentifierKind::classify(new_name)?; - - let InFile { file_id, value: self_param } = - self_param.source(sema.db).ok_or_else(|| format_err!("cannot find function source"))?; - - let def = Definition::Local(local); - let usages = def.usages(sema).all(); - let edit = text_edit_from_self_param(&self_param, new_name) - .ok_or_else(|| format_err!("No target type found"))?; - if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { - bail!("Cannot rename reference to `_` as it is being referenced multiple times"); - } - let mut source_change = SourceChange::default(); - source_change.insert_source_edit(file_id.original_file(sema.db), edit); - source_change.extend(usages.iter().map(|(&file_id, references)| { - (file_id, source_edit_from_references(references, def, new_name)) - })); - Ok(source_change) -} - -fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option { - fn target_type_name(impl_def: &ast::Impl) -> Option { - if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { - return Some(p.path()?.segment()?.name_ref()?.text().to_string()); - } - None - } - - let impl_def = self_param.syntax().ancestors().find_map(ast::Impl::cast)?; - let type_name = target_type_name(&impl_def)?; - - let mut replacement_text = String::from(new_name); - replacement_text.push_str(": "); - match (self_param.amp_token(), self_param.mut_token()) { - (Some(_), None) => replacement_text.push('&'), - (Some(_), Some(_)) => replacement_text.push_str("&mut "), - (_, _) => (), - }; - replacement_text.push_str(type_name.as_str()); - - Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) -} - -#[cfg(test)] -mod tests { - use expect_test::{expect, Expect}; - use stdx::trim_indent; - use test_utils::assert_eq_text; - use text_edit::TextEdit; - - use crate::{fixture, FileId}; - - use super::{RangeInfo, RenameError}; - - fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { - let ra_fixture_after = &trim_indent(ra_fixture_after); - let (analysis, position) = fixture::position(ra_fixture_before); - let rename_result = analysis - .rename(position, new_name) - .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err)); - match rename_result { - Ok(source_change) => { - let mut text_edit_builder = TextEdit::builder(); - let mut file_id: Option = None; - for edit in source_change.source_file_edits { - file_id = Some(edit.0); - for indel in edit.1.into_iter() { - text_edit_builder.replace(indel.delete, indel.insert); - } - } - if let Some(file_id) = file_id { - let mut result = analysis.file_text(file_id).unwrap().to_string(); - text_edit_builder.finish().apply(&mut result); - assert_eq_text!(ra_fixture_after, &*result); - } - } - Err(err) => { - if ra_fixture_after.starts_with("error:") { - let error_message = ra_fixture_after - .chars() - .into_iter() - .skip("error:".len()) - .collect::(); - assert_eq!(error_message.trim(), err.to_string()); - return; - } else { - panic!("Rename to '{}' failed unexpectedly: {}", new_name, err) - } - } - }; - } - - fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { - let (analysis, position) = fixture::position(ra_fixture); - let source_change = - analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); - expect.assert_debug_eq(&source_change) - } - - fn check_prepare(ra_fixture: &str, expect: Expect) { - let (analysis, position) = fixture::position(ra_fixture); - let result = analysis - .prepare_rename(position) - .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {}", err)); - match result { - Ok(RangeInfo { range, info: () }) => { - let source = analysis.file_text(position.file_id).unwrap(); - expect.assert_eq(&format!("{:?}: {}", range, &source[range])) - } - Err(RenameError(err)) => expect.assert_eq(&err), - }; - } - - #[test] - fn test_prepare_rename_namelikes() { - check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); - check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); - check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"3..7: name"#]]); - } - - #[test] - fn test_prepare_rename_in_macro() { - check_prepare( - r"macro_rules! foo { - ($ident:ident) => { - pub struct $ident; - } -} -foo!(Foo$0);", - expect![[r#"83..86: Foo"#]], - ); - } - - #[test] - fn test_prepare_rename_keyword() { - check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]); - } - - #[test] - fn test_prepare_rename_tuple_field() { - check_prepare( - r#" -struct Foo(i32); - -fn baz() { - let mut x = Foo(4); - x.0$0 = 5; -} -"#, - expect![[r#"No references found at position"#]], - ); - } - - #[test] - fn test_prepare_rename_builtin() { - check_prepare( - r#" -fn foo() { - let x: i32$0 = 0; -} -"#, - expect![[r#"No references found at position"#]], - ); - } - - #[test] - fn test_prepare_rename_self() { - check_prepare( - r#" -struct Foo {} - -impl Foo { - fn foo(self) -> Self$0 { - self - } -} -"#, - expect![[r#"No references found at position"#]], - ); - } - - #[test] - fn test_rename_to_underscore() { - check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#); - } - - #[test] - fn test_rename_to_raw_identifier() { - check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#); - } - - #[test] - fn test_rename_to_invalid_identifier1() { - check( - "invalid!", - r#"fn main() { let i$0 = 1; }"#, - "error: Invalid name `invalid!`: not an identifier", - ); - } - - #[test] - fn test_rename_to_invalid_identifier2() { - check( - "multiple tokens", - r#"fn main() { let i$0 = 1; }"#, - "error: Invalid name `multiple tokens`: not an identifier", - ); - } - - #[test] - fn test_rename_to_invalid_identifier3() { - check( - "let", - r#"fn main() { let i$0 = 1; }"#, - "error: Invalid name `let`: not an identifier", - ); - } - - #[test] - fn test_rename_to_invalid_identifier_lifetime() { - cov_mark::check!(rename_not_an_ident_ref); - check( - "'foo", - r#"fn main() { let i$0 = 1; }"#, - "error: Invalid name `'foo`: not an identifier", - ); - } - - #[test] - fn test_rename_to_invalid_identifier_lifetime2() { - cov_mark::check!(rename_not_a_lifetime_ident_ref); - check( - "foo", - r#"fn main<'a>(_: &'a$0 ()) {}"#, - "error: Invalid name `foo`: not a lifetime identifier", - ); - } - - #[test] - fn test_rename_to_underscore_invalid() { - cov_mark::check!(rename_underscore_multiple); - check( - "_", - r#"fn main(foo$0: ()) {foo;}"#, - "error: Cannot rename reference to `_` as it is being referenced multiple times", - ); - } - - #[test] - fn test_rename_mod_invalid() { - check( - "'foo", - r#"mod foo$0 {}"#, - "error: Invalid name `'foo`: cannot rename module to 'foo", - ); - } - - #[test] - fn test_rename_for_local() { - check( - "k", - r#" -fn main() { - let mut i = 1; - let j = 1; - i = i$0 + j; - - { i = 0; } - - i = 5; -} -"#, - r#" -fn main() { - let mut k = 1; - let j = 1; - k = k + j; - - { k = 0; } - - k = 5; -} -"#, - ); - } - - #[test] - fn test_rename_unresolved_reference() { - check( - "new_name", - r#"fn main() { let _ = unresolved_ref$0; }"#, - "error: No references found at position", - ); - } - - #[test] - fn test_rename_for_macro_args() { - check( - "b", - r#" -macro_rules! foo {($i:ident) => {$i} } -fn main() { - let a$0 = "test"; - foo!(a); -} -"#, - r#" -macro_rules! foo {($i:ident) => {$i} } -fn main() { - let b = "test"; - foo!(b); -} -"#, - ); - } - - #[test] - fn test_rename_for_macro_args_rev() { - check( - "b", - r#" -macro_rules! foo {($i:ident) => {$i} } -fn main() { - let a = "test"; - foo!(a$0); -} -"#, - r#" -macro_rules! foo {($i:ident) => {$i} } -fn main() { - let b = "test"; - foo!(b); -} -"#, - ); - } - - #[test] - fn test_rename_for_macro_define_fn() { - check( - "bar", - r#" -macro_rules! define_fn {($id:ident) => { fn $id{} }} -define_fn!(foo); -fn main() { - fo$0o(); -} -"#, - r#" -macro_rules! define_fn {($id:ident) => { fn $id{} }} -define_fn!(bar); -fn main() { - bar(); -} -"#, - ); - } - - #[test] - fn test_rename_for_macro_define_fn_rev() { - check( - "bar", - r#" -macro_rules! define_fn {($id:ident) => { fn $id{} }} -define_fn!(fo$0o); -fn main() { - foo(); -} -"#, - r#" -macro_rules! define_fn {($id:ident) => { fn $id{} }} -define_fn!(bar); -fn main() { - bar(); -} -"#, - ); - } - - #[test] - fn test_rename_for_param_inside() { - check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#); - } - - #[test] - fn test_rename_refs_for_fn_param() { - check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#); - } - - #[test] - fn test_rename_for_mut_param() { - check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#); - } - - #[test] - fn test_rename_struct_field() { - check( - "j", - r#" -struct Foo { i$0: i32 } - -impl Foo { - fn new(i: i32) -> Self { - Self { i: i } - } -} -"#, - r#" -struct Foo { j: i32 } - -impl Foo { - fn new(i: i32) -> Self { - Self { j: i } - } -} -"#, - ); - } - - #[test] - fn test_rename_field_in_field_shorthand() { - cov_mark::check!(test_rename_field_in_field_shorthand); - check( - "j", - r#" -struct Foo { i$0: i32 } - -impl Foo { - fn new(i: i32) -> Self { - Self { i } - } -} -"#, - r#" -struct Foo { j: i32 } - -impl Foo { - fn new(i: i32) -> Self { - Self { j: i } - } -} -"#, - ); - } - - #[test] - fn test_rename_local_in_field_shorthand() { - cov_mark::check!(test_rename_local_in_field_shorthand); - check( - "j", - r#" -struct Foo { i: i32 } - -impl Foo { - fn new(i$0: i32) -> Self { - Self { i } - } -} -"#, - r#" -struct Foo { i: i32 } - -impl Foo { - fn new(j: i32) -> Self { - Self { i: j } - } -} -"#, - ); - } - - #[test] - fn test_field_shorthand_correct_struct() { - check( - "j", - r#" -struct Foo { i$0: i32 } -struct Bar { i: i32 } - -impl Bar { - fn new(i: i32) -> Self { - Self { i } - } -} -"#, - r#" -struct Foo { j: i32 } -struct Bar { i: i32 } - -impl Bar { - fn new(i: i32) -> Self { - Self { i } - } -} -"#, - ); - } - - #[test] - fn test_shadow_local_for_struct_shorthand() { - check( - "j", - r#" -struct Foo { i: i32 } - -fn baz(i$0: i32) -> Self { - let x = Foo { i }; - { - let i = 0; - Foo { i } - } -} -"#, - r#" -struct Foo { i: i32 } - -fn baz(j: i32) -> Self { - let x = Foo { i: j }; - { - let i = 0; - Foo { i } - } -} -"#, - ); - } - - #[test] - fn test_rename_mod() { - check_expect( - "foo2", - r#" -//- /lib.rs -mod bar; - -//- /bar.rs -mod foo$0; - -//- /bar/foo.rs -// empty -"#, - expect![[r#" - SourceChange { - source_file_edits: { - FileId( - 1, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, - }, - file_system_edits: [ - MoveFile { - src: FileId( - 2, - ), - dst: AnchoredPathBuf { - anchor: FileId( - 2, - ), - path: "foo2.rs", - }, - }, - ], - is_snippet: false, - } - "#]], - ); - } - - #[test] - fn test_rename_mod_in_use_tree() { - check_expect( - "quux", - r#" -//- /main.rs -pub mod foo; -pub mod bar; -fn main() {} - -//- /foo.rs -pub struct FooContent; - -//- /bar.rs -use crate::foo$0::FooContent; -"#, - expect![[r#" - SourceChange { - source_file_edits: { - FileId( - 0, - ): TextEdit { - indels: [ - Indel { - insert: "quux", - delete: 8..11, - }, - ], - }, - FileId( - 2, - ): TextEdit { - indels: [ - Indel { - insert: "quux", - delete: 11..14, - }, - ], - }, - }, - file_system_edits: [ - MoveFile { - src: FileId( - 1, - ), - dst: AnchoredPathBuf { - anchor: FileId( - 1, - ), - path: "quux.rs", - }, - }, - ], - is_snippet: false, - } - "#]], - ); - } - - #[test] - fn test_rename_mod_in_dir() { - check_expect( - "foo2", - r#" -//- /lib.rs -mod fo$0o; -//- /foo/mod.rs -// empty -"#, - expect![[r#" - SourceChange { - source_file_edits: { - FileId( - 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 4..7, - }, - ], - }, - }, - file_system_edits: [ - MoveFile { - src: FileId( - 1, - ), - dst: AnchoredPathBuf { - anchor: FileId( - 1, - ), - path: "../foo2/mod.rs", - }, - }, - ], - is_snippet: false, - } - "#]], - ); - } - - #[test] - fn test_rename_unusually_nested_mod() { - check_expect( - "bar", - r#" -//- /lib.rs -mod outer { mod fo$0o; } - -//- /outer/foo.rs -// empty -"#, - expect![[r#" - SourceChange { - source_file_edits: { - FileId( - 0, - ): TextEdit { - indels: [ - Indel { - insert: "bar", - delete: 16..19, - }, - ], - }, - }, - file_system_edits: [ - MoveFile { - src: FileId( - 1, - ), - dst: AnchoredPathBuf { - anchor: FileId( - 1, - ), - path: "bar.rs", - }, - }, - ], - is_snippet: false, - } - "#]], - ); - } - - #[test] - fn test_module_rename_in_path() { - check( - "baz", - r#" -mod $0foo { pub fn bar() {} } - -fn main() { foo::bar(); } -"#, - r#" -mod baz { pub fn bar() {} } - -fn main() { baz::bar(); } -"#, - ); - } - - #[test] - fn test_rename_mod_filename_and_path() { - check_expect( - "foo2", - r#" -//- /lib.rs -mod bar; -fn f() { - bar::foo::fun() -} - -//- /bar.rs -pub mod foo$0; - -//- /bar/foo.rs -// pub fn fun() {} -"#, - expect![[r#" - SourceChange { - source_file_edits: { - FileId( - 0, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 27..30, - }, - ], - }, - FileId( - 1, - ): TextEdit { - indels: [ - Indel { - insert: "foo2", - delete: 8..11, - }, - ], - }, - }, - file_system_edits: [ - MoveFile { - src: FileId( - 2, - ), - dst: AnchoredPathBuf { - anchor: FileId( - 2, - ), - path: "foo2.rs", - }, - }, - ], - is_snippet: false, - } - "#]], - ); - } - - #[test] - fn test_enum_variant_from_module_1() { - cov_mark::check!(rename_non_local); - check( - "Baz", - r#" -mod foo { - pub enum Foo { Bar$0 } -} - -fn func(f: foo::Foo) { - match f { - foo::Foo::Bar => {} - } -} -"#, - r#" -mod foo { - pub enum Foo { Baz } -} - -fn func(f: foo::Foo) { - match f { - foo::Foo::Baz => {} - } -} -"#, - ); - } - - #[test] - fn test_enum_variant_from_module_2() { - check( - "baz", - r#" -mod foo { - pub struct Foo { pub bar$0: uint } -} - -fn foo(f: foo::Foo) { - let _ = f.bar; -} -"#, - r#" -mod foo { - pub struct Foo { pub baz: uint } -} - -fn foo(f: foo::Foo) { - let _ = f.baz; -} -"#, - ); - } - - #[test] - fn test_parameter_to_self() { - cov_mark::check!(rename_to_self); - check( - "self", - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(foo$0: &mut Foo) -> i32 { - foo.i - } -} -"#, - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(&mut self) -> i32 { - self.i - } -} -"#, - ); - check( - "self", - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(foo$0: Foo) -> i32 { - foo.i - } -} -"#, - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(self) -> i32 { - self.i - } -} -"#, - ); - } - - #[test] - fn test_parameter_to_self_error_no_impl() { - check( - "self", - r#" -struct Foo { i: i32 } - -fn f(foo$0: &mut Foo) -> i32 { - foo.i -} -"#, - "error: Cannot rename parameter to self for free function", - ); - check( - "self", - r#" -struct Foo { i: i32 } -struct Bar; - -impl Bar { - fn f(foo$0: &mut Foo) -> i32 { - foo.i - } -} -"#, - "error: Parameter type differs from impl block type", - ); - } - - #[test] - fn test_parameter_to_self_error_not_first() { - check( - "self", - r#" -struct Foo { i: i32 } -impl Foo { - fn f(x: (), foo$0: &mut Foo) -> i32 { - foo.i - } -} -"#, - "error: Only the first parameter may be renamed to self", - ); - } - - #[test] - fn test_parameter_to_self_impl_ref() { - check( - "self", - r#" -struct Foo { i: i32 } -impl &Foo { - fn f(foo$0: &Foo) -> i32 { - foo.i - } -} -"#, - r#" -struct Foo { i: i32 } -impl &Foo { - fn f(self) -> i32 { - self.i - } -} -"#, - ); - } - - #[test] - fn test_self_to_parameter() { - check( - "foo", - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(&mut $0self) -> i32 { - self.i - } -} -"#, - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(foo: &mut Foo) -> i32 { - foo.i - } -} -"#, - ); - } - - #[test] - fn test_owned_self_to_parameter() { - cov_mark::check!(rename_self_to_param); - check( - "foo", - r#" -struct Foo { i: i32 } - -impl Foo { - fn f($0self) -> i32 { - self.i - } -} -"#, - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(foo: Foo) -> i32 { - foo.i - } -} -"#, - ); - } - - #[test] - fn test_self_in_path_to_parameter() { - check( - "foo", - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(&self) -> i32 { - let self_var = 1; - self$0.i - } -} -"#, - r#" -struct Foo { i: i32 } - -impl Foo { - fn f(foo: &Foo) -> i32 { - let self_var = 1; - foo.i - } -} -"#, - ); - } - - #[test] - fn test_rename_field_put_init_shorthand() { - cov_mark::check!(test_rename_field_put_init_shorthand); - check( - "bar", - r#" -struct Foo { i$0: i32 } - -fn foo(bar: i32) -> Foo { - Foo { i: bar } -} -"#, - r#" -struct Foo { bar: i32 } - -fn foo(bar: i32) -> Foo { - Foo { bar } -} -"#, - ); - } - - #[test] - fn test_rename_local_put_init_shorthand() { - cov_mark::check!(test_rename_local_put_init_shorthand); - check( - "i", - r#" -struct Foo { i: i32 } - -fn foo(bar$0: i32) -> Foo { - Foo { i: bar } -} -"#, - r#" -struct Foo { i: i32 } - -fn foo(i: i32) -> Foo { - Foo { i } -} -"#, - ); - } - - #[test] - fn test_struct_field_pat_into_shorthand() { - cov_mark::check!(test_rename_field_put_init_shorthand_pat); - check( - "baz", - r#" -struct Foo { i$0: i32 } - -fn foo(foo: Foo) { - let Foo { i: ref baz @ qux } = foo; - let _ = qux; -} -"#, - r#" -struct Foo { baz: i32 } - -fn foo(foo: Foo) { - let Foo { ref baz @ qux } = foo; - let _ = qux; -} -"#, - ); - } - - #[test] - fn test_rename_binding_in_destructure_pat() { - let expected_fixture = r#" -struct Foo { - i: i32, -} - -fn foo(foo: Foo) { - let Foo { i: bar } = foo; - let _ = bar; -} -"#; - check( - "bar", - r#" -struct Foo { - i: i32, -} - -fn foo(foo: Foo) { - let Foo { i: b } = foo; - let _ = b$0; -} -"#, - expected_fixture, - ); - check( - "bar", - r#" -struct Foo { - i: i32, -} - -fn foo(foo: Foo) { - let Foo { i } = foo; - let _ = i$0; -} -"#, - expected_fixture, - ); - } - - #[test] - fn test_rename_binding_in_destructure_param_pat() { - check( - "bar", - r#" -struct Foo { - i: i32 -} - -fn foo(Foo { i }: foo) -> i32 { - i$0 -} -"#, - r#" -struct Foo { - i: i32 -} - -fn foo(Foo { i: bar }: foo) -> i32 { - bar -} -"#, - ) - } - - #[test] - fn test_struct_field_complex_ident_pat() { - check( - "baz", - r#" -struct Foo { i$0: i32 } - -fn foo(foo: Foo) { - let Foo { ref i } = foo; -} -"#, - r#" -struct Foo { baz: i32 } - -fn foo(foo: Foo) { - let Foo { baz: ref i } = foo; -} -"#, - ); - } - - #[test] - fn test_rename_lifetimes() { - cov_mark::check!(rename_lifetime); - check( - "'yeeee", - r#" -trait Foo<'a> { - fn foo() -> &'a (); -} -impl<'a> Foo<'a> for &'a () { - fn foo() -> &'a$0 () { - unimplemented!() - } -} -"#, - r#" -trait Foo<'a> { - fn foo() -> &'a (); -} -impl<'yeeee> Foo<'yeeee> for &'yeeee () { - fn foo() -> &'yeeee () { - unimplemented!() - } -} -"#, - ) - } - - #[test] - fn test_rename_bind_pat() { - check( - "new_name", - r#" -fn main() { - enum CustomOption { - None, - Some(T), - } - - let test_variable = CustomOption::Some(22); - - match test_variable { - CustomOption::Some(foo$0) if foo == 11 => {} - _ => (), - } -}"#, - r#" -fn main() { - enum CustomOption { - None, - Some(T), - } - - let test_variable = CustomOption::Some(22); - - match test_variable { - CustomOption::Some(new_name) if new_name == 11 => {} - _ => (), - } -}"#, - ); - } - - #[test] - fn test_rename_label() { - check( - "'foo", - r#" -fn foo<'a>() -> &'a () { - 'a: { - 'b: loop { - break 'a$0; - } - } -} -"#, - r#" -fn foo<'a>() -> &'a () { - 'foo: { - 'b: loop { - break 'foo; - } - } -} -"#, - ) - } - - #[test] - fn test_self_to_self() { - cov_mark::check!(rename_self_to_self); - check( - "self", - r#" -struct Foo; -impl Foo { - fn foo(self$0) {} -} -"#, - r#" -struct Foo; -impl Foo { - fn foo(self) {} -} -"#, - ) - } - - #[test] - fn test_rename_field_in_pat_in_macro_doesnt_shorthand() { - // ideally we would be able to make this emit a short hand, but I doubt this is easily possible - check( - "baz", - r#" -macro_rules! foo { - ($pattern:pat) => { - let $pattern = loop {}; - }; -} -struct Foo { - bar$0: u32, -} -fn foo() { - foo!(Foo { bar: baz }); -} -"#, - r#" -macro_rules! foo { - ($pattern:pat) => { - let $pattern = loop {}; - }; -} -struct Foo { - baz: u32, -} -fn foo() { - foo!(Foo { baz: baz }); -} -"#, - ) - } - - #[test] - fn test_rename_tuple_field() { - check( - "foo", - r#" -struct Foo(i32); - -fn baz() { - let mut x = Foo(4); - x.0$0 = 5; -} -"#, - "error: No identifier available to rename", - ); - } - - #[test] - fn test_rename_builtin() { - check( - "foo", - r#" -fn foo() { - let x: i32$0 = 0; -} -"#, - "error: Cannot rename builtin type", - ); - } - - #[test] - fn test_rename_self() { - check( - "foo", - r#" -struct Foo {} - -impl Foo { - fn foo(self) -> Self$0 { - self - } -} -"#, - "error: Cannot rename `Self`", - ); - } - - #[test] - fn test_rename_ignores_self_ty() { - check( - "Fo0", - r#" -struct $0Foo; - -impl Foo where Self: {} -"#, - r#" -struct Fo0; - -impl Fo0 where Self: {} -"#, - ); - } - - #[test] - fn test_rename_fails_on_aliases() { - check( - "Baz", - r#" -struct Foo; -use Foo as Bar$0; -"#, - "error: Renaming aliases is currently unsupported", - ); - check( - "Baz", - r#" -struct Foo; -use Foo as Bar; -use Bar$0; -"#, - "error: Renaming aliases is currently unsupported", - ); - } - - #[test] - fn test_rename_trait_method() { - let res = r" -trait Foo { - fn foo(&self) { - self.foo(); - } -} - -impl Foo for () { - fn foo(&self) { - self.foo(); - } -}"; - check( - "foo", - r#" -trait Foo { - fn bar$0(&self) { - self.bar(); - } -} - -impl Foo for () { - fn bar(&self) { - self.bar(); - } -}"#, - res, - ); - check( - "foo", - r#" -trait Foo { - fn bar(&self) { - self.bar$0(); - } -} - -impl Foo for () { - fn bar(&self) { - self.bar(); - } -}"#, - res, - ); - check( - "foo", - r#" -trait Foo { - fn bar(&self) { - self.bar(); - } -} - -impl Foo for () { - fn bar$0(&self) { - self.bar(); - } -}"#, - res, - ); - check( - "foo", - r#" -trait Foo { - fn bar(&self) { - self.bar(); - } -} - -impl Foo for () { - fn bar(&self) { - self.bar$0(); - } -}"#, - res, - ); - } - - #[test] - fn test_rename_trait_const() { - let res = r" -trait Foo { - const FOO: (); -} - -impl Foo for () { - const FOO: (); -} -fn f() { <()>::FOO; }"; - check( - "FOO", - r#" -trait Foo { - const BAR$0: (); -} - -impl Foo for () { - const BAR: (); -} -fn f() { <()>::BAR; }"#, - res, - ); - check( - "FOO", - r#" -trait Foo { - const BAR: (); -} - -impl Foo for () { - const BAR$0: (); -} -fn f() { <()>::BAR; }"#, - res, - ); - check( - "FOO", - r#" -trait Foo { - const BAR: (); -} - -impl Foo for () { - const BAR: (); -} -fn f() { <()>::BAR$0; }"#, - res, - ); - } - - #[test] - fn macros_are_broken_lol() { - cov_mark::check!(macros_are_broken_lol); - check( - "lol", - r#" -macro_rules! m { () => { fn f() {} } } -m!(); -fn main() { f$0() } -"#, - r#" -macro_rules! m { () => { fn f() {} } } -lol -fn main() { lol() } -"#, - ) - } -} diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs new file mode 100644 index 000000000..41689a939 --- /dev/null +++ b/crates/ide/src/rename.rs @@ -0,0 +1,1789 @@ +//! Renaming functionality. +//! +//! This is mostly front-end for [`ide_db::rename`], but it also includes the +//! tests. This module also implements a couple of magic tricks, like renaming +//! `self` and to `self` (to switch between associated function and method). +use hir::{AsAssocItem, InFile, Semantics}; +use ide_db::{ + base_db::FileId, + defs::{Definition, NameClass, NameRefClass}, + rename::{bail, format_err, source_edit_from_references, IdentifierKind}, + RootDatabase, +}; +use stdx::never; +use syntax::{ast, AstNode, SyntaxNode}; + +use text_edit::TextEdit; + +use crate::{FilePosition, RangeInfo, SourceChange}; + +pub use ide_db::rename::RenameError; + +type RenameResult = Result; + +/// Prepares a rename. The sole job of this function is to return the TextRange of the thing that is +/// being targeted for a rename. +pub(crate) fn prepare_rename( + db: &RootDatabase, + position: FilePosition, +) -> RenameResult> { + let sema = Semantics::new(db); + let source_file = sema.parse(position.file_id); + let syntax = source_file.syntax(); + + let def = find_definition(&sema, syntax, position)?; + let frange = + def.rename_range(&sema).ok_or_else(|| format_err!("No references found at position"))?; + Ok(RangeInfo::new(frange.range, ())) +} + +// Feature: Rename +// +// Renames the item below the cursor and all of its references +// +// |=== +// | Editor | Shortcut +// +// | VS Code | kbd:[F2] +// |=== +// +// image::https://user-images.githubusercontent.com/48062697/113065582-055aae80-91b1-11eb-8ade-2b58e6d81883.gif[] +pub(crate) fn rename( + db: &RootDatabase, + position: FilePosition, + new_name: &str, +) -> RenameResult { + let sema = Semantics::new(db); + rename_with_semantics(&sema, position, new_name) +} + +pub(crate) fn rename_with_semantics( + sema: &Semantics, + position: FilePosition, + new_name: &str, +) -> RenameResult { + let source_file = sema.parse(position.file_id); + let syntax = source_file.syntax(); + + let def = find_definition(sema, syntax, position)?; + + if let Definition::Local(local) = def { + if let Some(self_param) = local.as_self_param(sema.db) { + cov_mark::hit!(rename_self_to_param); + return rename_self_to_param(sema, local, self_param, new_name); + } + if new_name == "self" { + cov_mark::hit!(rename_to_self); + return rename_to_self(sema, local); + } + } + + def.rename(sema, new_name) +} + +/// Called by the client when it is about to rename a file. +pub(crate) fn will_rename_file( + db: &RootDatabase, + file_id: FileId, + new_name_stem: &str, +) -> Option { + let sema = Semantics::new(db); + let module = sema.to_module_def(file_id)?; + let def = Definition::ModuleDef(module.into()); + let mut change = def.rename(&sema, new_name_stem).ok()?; + change.file_system_edits.clear(); + Some(change) +} + +fn find_definition( + sema: &Semantics, + syntax: &SyntaxNode, + position: FilePosition, +) -> RenameResult { + match sema + .find_node_at_offset_with_descend(syntax, position.offset) + .ok_or_else(|| format_err!("No references found at position"))? + { + // renaming aliases would rename the item being aliased as the HIR doesn't track aliases yet + ast::NameLike::Name(name) + if name.syntax().parent().map_or(false, |it| ast::Rename::can_cast(it.kind())) => + { + bail!("Renaming aliases is currently unsupported") + } + ast::NameLike::Name(name) => { + NameClass::classify(sema, &name).map(|class| class.referenced_or_defined(sema.db)) + } + ast::NameLike::NameRef(name_ref) => { + if let Some(def) = + NameRefClass::classify(sema, &name_ref).map(|class| class.referenced(sema.db)) + { + // if the name differs from the definitions name it has to be an alias + if def.name(sema.db).map_or(false, |it| it.to_string() != name_ref.text()) { + bail!("Renaming aliases is currently unsupported"); + } + Some(def) + } else { + None + } + } + ast::NameLike::Lifetime(lifetime) => NameRefClass::classify_lifetime(sema, &lifetime) + .map(|class| NameRefClass::referenced(class, sema.db)) + .or_else(|| { + NameClass::classify_lifetime(sema, &lifetime) + .map(|it| it.referenced_or_defined(sema.db)) + }), + } + .ok_or_else(|| format_err!("No references found at position")) +} + +fn rename_to_self(sema: &Semantics, local: hir::Local) -> RenameResult { + if never!(local.is_self(sema.db)) { + bail!("rename_to_self invoked on self"); + } + + let fn_def = match local.parent(sema.db) { + hir::DefWithBody::Function(func) => func, + _ => bail!("Cannot rename local to self outside of function"), + }; + + if let Some(_) = fn_def.self_param(sema.db) { + bail!("Method already has a self parameter"); + } + + let params = fn_def.assoc_fn_params(sema.db); + let first_param = params + .first() + .ok_or_else(|| format_err!("Cannot rename local to self unless it is a parameter"))?; + if first_param.as_local(sema.db) != local { + bail!("Only the first parameter may be renamed to self"); + } + + let assoc_item = fn_def + .as_assoc_item(sema.db) + .ok_or_else(|| format_err!("Cannot rename parameter to self for free function"))?; + let impl_ = match assoc_item.container(sema.db) { + hir::AssocItemContainer::Trait(_) => { + bail!("Cannot rename parameter to self for trait functions"); + } + hir::AssocItemContainer::Impl(impl_) => impl_, + }; + let first_param_ty = first_param.ty(); + let impl_ty = impl_.self_ty(sema.db); + let (ty, self_param) = if impl_ty.remove_ref().is_some() { + // if the impl is a ref to the type we can just match the `&T` with self directly + (first_param_ty.clone(), "self") + } else { + first_param_ty.remove_ref().map_or((first_param_ty.clone(), "self"), |ty| { + (ty, if first_param_ty.is_mutable_reference() { "&mut self" } else { "&self" }) + }) + }; + + if ty != impl_ty { + bail!("Parameter type differs from impl block type"); + } + + let InFile { file_id, value: param_source } = + first_param.source(sema.db).ok_or_else(|| format_err!("No source for parameter found"))?; + + let def = Definition::Local(local); + let usages = def.usages(sema).all(); + let mut source_change = SourceChange::default(); + source_change.extend(usages.iter().map(|(&file_id, references)| { + (file_id, source_edit_from_references(references, def, "self")) + })); + source_change.insert_source_edit( + file_id.original_file(sema.db), + TextEdit::replace(param_source.syntax().text_range(), String::from(self_param)), + ); + Ok(source_change) +} + +fn rename_self_to_param( + sema: &Semantics, + local: hir::Local, + self_param: hir::SelfParam, + new_name: &str, +) -> RenameResult { + if new_name == "self" { + // Let's do nothing rather than complain. + cov_mark::hit!(rename_self_to_self); + return Ok(SourceChange::default()); + } + + let identifier_kind = IdentifierKind::classify(new_name)?; + + let InFile { file_id, value: self_param } = + self_param.source(sema.db).ok_or_else(|| format_err!("cannot find function source"))?; + + let def = Definition::Local(local); + let usages = def.usages(sema).all(); + let edit = text_edit_from_self_param(&self_param, new_name) + .ok_or_else(|| format_err!("No target type found"))?; + if usages.len() > 1 && identifier_kind == IdentifierKind::Underscore { + bail!("Cannot rename reference to `_` as it is being referenced multiple times"); + } + let mut source_change = SourceChange::default(); + source_change.insert_source_edit(file_id.original_file(sema.db), edit); + source_change.extend(usages.iter().map(|(&file_id, references)| { + (file_id, source_edit_from_references(references, def, new_name)) + })); + Ok(source_change) +} + +fn text_edit_from_self_param(self_param: &ast::SelfParam, new_name: &str) -> Option { + fn target_type_name(impl_def: &ast::Impl) -> Option { + if let Some(ast::Type::PathType(p)) = impl_def.self_ty() { + return Some(p.path()?.segment()?.name_ref()?.text().to_string()); + } + None + } + + let impl_def = self_param.syntax().ancestors().find_map(ast::Impl::cast)?; + let type_name = target_type_name(&impl_def)?; + + let mut replacement_text = String::from(new_name); + replacement_text.push_str(": "); + match (self_param.amp_token(), self_param.mut_token()) { + (Some(_), None) => replacement_text.push('&'), + (Some(_), Some(_)) => replacement_text.push_str("&mut "), + (_, _) => (), + }; + replacement_text.push_str(type_name.as_str()); + + Some(TextEdit::replace(self_param.syntax().text_range(), replacement_text)) +} + +#[cfg(test)] +mod tests { + use expect_test::{expect, Expect}; + use stdx::trim_indent; + use test_utils::assert_eq_text; + use text_edit::TextEdit; + + use crate::{fixture, FileId}; + + use super::{RangeInfo, RenameError}; + + fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { + let ra_fixture_after = &trim_indent(ra_fixture_after); + let (analysis, position) = fixture::position(ra_fixture_before); + let rename_result = analysis + .rename(position, new_name) + .unwrap_or_else(|err| panic!("Rename to '{}' was cancelled: {}", new_name, err)); + match rename_result { + Ok(source_change) => { + let mut text_edit_builder = TextEdit::builder(); + let mut file_id: Option = None; + for edit in source_change.source_file_edits { + file_id = Some(edit.0); + for indel in edit.1.into_iter() { + text_edit_builder.replace(indel.delete, indel.insert); + } + } + if let Some(file_id) = file_id { + let mut result = analysis.file_text(file_id).unwrap().to_string(); + text_edit_builder.finish().apply(&mut result); + assert_eq_text!(ra_fixture_after, &*result); + } + } + Err(err) => { + if ra_fixture_after.starts_with("error:") { + let error_message = ra_fixture_after + .chars() + .into_iter() + .skip("error:".len()) + .collect::(); + assert_eq!(error_message.trim(), err.to_string()); + return; + } else { + panic!("Rename to '{}' failed unexpectedly: {}", new_name, err) + } + } + }; + } + + fn check_expect(new_name: &str, ra_fixture: &str, expect: Expect) { + let (analysis, position) = fixture::position(ra_fixture); + let source_change = + analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError"); + expect.assert_debug_eq(&source_change) + } + + fn check_prepare(ra_fixture: &str, expect: Expect) { + let (analysis, position) = fixture::position(ra_fixture); + let result = analysis + .prepare_rename(position) + .unwrap_or_else(|err| panic!("PrepareRename was cancelled: {}", err)); + match result { + Ok(RangeInfo { range, info: () }) => { + let source = analysis.file_text(position.file_id).unwrap(); + expect.assert_eq(&format!("{:?}: {}", range, &source[range])) + } + Err(RenameError(err)) => expect.assert_eq(&err), + }; + } + + #[test] + fn test_prepare_rename_namelikes() { + check_prepare(r"fn name$0<'lifetime>() {}", expect![[r#"3..7: name"#]]); + check_prepare(r"fn name<'lifetime$0>() {}", expect![[r#"8..17: 'lifetime"#]]); + check_prepare(r"fn name<'lifetime>() { name$0(); }", expect![[r#"3..7: name"#]]); + } + + #[test] + fn test_prepare_rename_in_macro() { + check_prepare( + r"macro_rules! foo { + ($ident:ident) => { + pub struct $ident; + } +} +foo!(Foo$0);", + expect![[r#"83..86: Foo"#]], + ); + } + + #[test] + fn test_prepare_rename_keyword() { + check_prepare(r"struct$0 Foo;", expect![[r#"No references found at position"#]]); + } + + #[test] + fn test_prepare_rename_tuple_field() { + check_prepare( + r#" +struct Foo(i32); + +fn baz() { + let mut x = Foo(4); + x.0$0 = 5; +} +"#, + expect![[r#"No references found at position"#]], + ); + } + + #[test] + fn test_prepare_rename_builtin() { + check_prepare( + r#" +fn foo() { + let x: i32$0 = 0; +} +"#, + expect![[r#"No references found at position"#]], + ); + } + + #[test] + fn test_prepare_rename_self() { + check_prepare( + r#" +struct Foo {} + +impl Foo { + fn foo(self) -> Self$0 { + self + } +} +"#, + expect![[r#"No references found at position"#]], + ); + } + + #[test] + fn test_rename_to_underscore() { + check("_", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let _ = 1; }"#); + } + + #[test] + fn test_rename_to_raw_identifier() { + check("r#fn", r#"fn main() { let i$0 = 1; }"#, r#"fn main() { let r#fn = 1; }"#); + } + + #[test] + fn test_rename_to_invalid_identifier1() { + check( + "invalid!", + r#"fn main() { let i$0 = 1; }"#, + "error: Invalid name `invalid!`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier2() { + check( + "multiple tokens", + r#"fn main() { let i$0 = 1; }"#, + "error: Invalid name `multiple tokens`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier3() { + check( + "let", + r#"fn main() { let i$0 = 1; }"#, + "error: Invalid name `let`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier_lifetime() { + cov_mark::check!(rename_not_an_ident_ref); + check( + "'foo", + r#"fn main() { let i$0 = 1; }"#, + "error: Invalid name `'foo`: not an identifier", + ); + } + + #[test] + fn test_rename_to_invalid_identifier_lifetime2() { + cov_mark::check!(rename_not_a_lifetime_ident_ref); + check( + "foo", + r#"fn main<'a>(_: &'a$0 ()) {}"#, + "error: Invalid name `foo`: not a lifetime identifier", + ); + } + + #[test] + fn test_rename_to_underscore_invalid() { + cov_mark::check!(rename_underscore_multiple); + check( + "_", + r#"fn main(foo$0: ()) {foo;}"#, + "error: Cannot rename reference to `_` as it is being referenced multiple times", + ); + } + + #[test] + fn test_rename_mod_invalid() { + check( + "'foo", + r#"mod foo$0 {}"#, + "error: Invalid name `'foo`: cannot rename module to 'foo", + ); + } + + #[test] + fn test_rename_for_local() { + check( + "k", + r#" +fn main() { + let mut i = 1; + let j = 1; + i = i$0 + j; + + { i = 0; } + + i = 5; +} +"#, + r#" +fn main() { + let mut k = 1; + let j = 1; + k = k + j; + + { k = 0; } + + k = 5; +} +"#, + ); + } + + #[test] + fn test_rename_unresolved_reference() { + check( + "new_name", + r#"fn main() { let _ = unresolved_ref$0; }"#, + "error: No references found at position", + ); + } + + #[test] + fn test_rename_for_macro_args() { + check( + "b", + r#" +macro_rules! foo {($i:ident) => {$i} } +fn main() { + let a$0 = "test"; + foo!(a); +} +"#, + r#" +macro_rules! foo {($i:ident) => {$i} } +fn main() { + let b = "test"; + foo!(b); +} +"#, + ); + } + + #[test] + fn test_rename_for_macro_args_rev() { + check( + "b", + r#" +macro_rules! foo {($i:ident) => {$i} } +fn main() { + let a = "test"; + foo!(a$0); +} +"#, + r#" +macro_rules! foo {($i:ident) => {$i} } +fn main() { + let b = "test"; + foo!(b); +} +"#, + ); + } + + #[test] + fn test_rename_for_macro_define_fn() { + check( + "bar", + r#" +macro_rules! define_fn {($id:ident) => { fn $id{} }} +define_fn!(foo); +fn main() { + fo$0o(); +} +"#, + r#" +macro_rules! define_fn {($id:ident) => { fn $id{} }} +define_fn!(bar); +fn main() { + bar(); +} +"#, + ); + } + + #[test] + fn test_rename_for_macro_define_fn_rev() { + check( + "bar", + r#" +macro_rules! define_fn {($id:ident) => { fn $id{} }} +define_fn!(fo$0o); +fn main() { + foo(); +} +"#, + r#" +macro_rules! define_fn {($id:ident) => { fn $id{} }} +define_fn!(bar); +fn main() { + bar(); +} +"#, + ); + } + + #[test] + fn test_rename_for_param_inside() { + check("j", r#"fn foo(i : u32) -> u32 { i$0 }"#, r#"fn foo(j : u32) -> u32 { j }"#); + } + + #[test] + fn test_rename_refs_for_fn_param() { + check("j", r#"fn foo(i$0 : u32) -> u32 { i }"#, r#"fn foo(j : u32) -> u32 { j }"#); + } + + #[test] + fn test_rename_for_mut_param() { + check("j", r#"fn foo(mut i$0 : u32) -> u32 { i }"#, r#"fn foo(mut j : u32) -> u32 { j }"#); + } + + #[test] + fn test_rename_struct_field() { + check( + "j", + r#" +struct Foo { i$0: i32 } + +impl Foo { + fn new(i: i32) -> Self { + Self { i: i } + } +} +"#, + r#" +struct Foo { j: i32 } + +impl Foo { + fn new(i: i32) -> Self { + Self { j: i } + } +} +"#, + ); + } + + #[test] + fn test_rename_field_in_field_shorthand() { + cov_mark::check!(test_rename_field_in_field_shorthand); + check( + "j", + r#" +struct Foo { i$0: i32 } + +impl Foo { + fn new(i: i32) -> Self { + Self { i } + } +} +"#, + r#" +struct Foo { j: i32 } + +impl Foo { + fn new(i: i32) -> Self { + Self { j: i } + } +} +"#, + ); + } + + #[test] + fn test_rename_local_in_field_shorthand() { + cov_mark::check!(test_rename_local_in_field_shorthand); + check( + "j", + r#" +struct Foo { i: i32 } + +impl Foo { + fn new(i$0: i32) -> Self { + Self { i } + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn new(j: i32) -> Self { + Self { i: j } + } +} +"#, + ); + } + + #[test] + fn test_field_shorthand_correct_struct() { + check( + "j", + r#" +struct Foo { i$0: i32 } +struct Bar { i: i32 } + +impl Bar { + fn new(i: i32) -> Self { + Self { i } + } +} +"#, + r#" +struct Foo { j: i32 } +struct Bar { i: i32 } + +impl Bar { + fn new(i: i32) -> Self { + Self { i } + } +} +"#, + ); + } + + #[test] + fn test_shadow_local_for_struct_shorthand() { + check( + "j", + r#" +struct Foo { i: i32 } + +fn baz(i$0: i32) -> Self { + let x = Foo { i }; + { + let i = 0; + Foo { i } + } +} +"#, + r#" +struct Foo { i: i32 } + +fn baz(j: i32) -> Self { + let x = Foo { i: j }; + { + let i = 0; + Foo { i } + } +} +"#, + ); + } + + #[test] + fn test_rename_mod() { + check_expect( + "foo2", + r#" +//- /lib.rs +mod bar; + +//- /bar.rs +mod foo$0; + +//- /bar/foo.rs +// empty +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 1, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 2, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 2, + ), + path: "foo2.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_rename_mod_in_use_tree() { + check_expect( + "quux", + r#" +//- /main.rs +pub mod foo; +pub mod bar; +fn main() {} + +//- /foo.rs +pub struct FooContent; + +//- /bar.rs +use crate::foo$0::FooContent; +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 8..11, + }, + ], + }, + FileId( + 2, + ): TextEdit { + indels: [ + Indel { + insert: "quux", + delete: 11..14, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "quux.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_rename_mod_in_dir() { + check_expect( + "foo2", + r#" +//- /lib.rs +mod fo$0o; +//- /foo/mod.rs +// empty +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 4..7, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "../foo2/mod.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_rename_unusually_nested_mod() { + check_expect( + "bar", + r#" +//- /lib.rs +mod outer { mod fo$0o; } + +//- /outer/foo.rs +// empty +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "bar", + delete: 16..19, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 1, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 1, + ), + path: "bar.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_module_rename_in_path() { + check( + "baz", + r#" +mod $0foo { pub fn bar() {} } + +fn main() { foo::bar(); } +"#, + r#" +mod baz { pub fn bar() {} } + +fn main() { baz::bar(); } +"#, + ); + } + + #[test] + fn test_rename_mod_filename_and_path() { + check_expect( + "foo2", + r#" +//- /lib.rs +mod bar; +fn f() { + bar::foo::fun() +} + +//- /bar.rs +pub mod foo$0; + +//- /bar/foo.rs +// pub fn fun() {} +"#, + expect![[r#" + SourceChange { + source_file_edits: { + FileId( + 0, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 27..30, + }, + ], + }, + FileId( + 1, + ): TextEdit { + indels: [ + Indel { + insert: "foo2", + delete: 8..11, + }, + ], + }, + }, + file_system_edits: [ + MoveFile { + src: FileId( + 2, + ), + dst: AnchoredPathBuf { + anchor: FileId( + 2, + ), + path: "foo2.rs", + }, + }, + ], + is_snippet: false, + } + "#]], + ); + } + + #[test] + fn test_enum_variant_from_module_1() { + cov_mark::check!(rename_non_local); + check( + "Baz", + r#" +mod foo { + pub enum Foo { Bar$0 } +} + +fn func(f: foo::Foo) { + match f { + foo::Foo::Bar => {} + } +} +"#, + r#" +mod foo { + pub enum Foo { Baz } +} + +fn func(f: foo::Foo) { + match f { + foo::Foo::Baz => {} + } +} +"#, + ); + } + + #[test] + fn test_enum_variant_from_module_2() { + check( + "baz", + r#" +mod foo { + pub struct Foo { pub bar$0: uint } +} + +fn foo(f: foo::Foo) { + let _ = f.bar; +} +"#, + r#" +mod foo { + pub struct Foo { pub baz: uint } +} + +fn foo(f: foo::Foo) { + let _ = f.baz; +} +"#, + ); + } + + #[test] + fn test_parameter_to_self() { + cov_mark::check!(rename_to_self); + check( + "self", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo$0: &mut Foo) -> i32 { + foo.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(&mut self) -> i32 { + self.i + } +} +"#, + ); + check( + "self", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo$0: Foo) -> i32 { + foo.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(self) -> i32 { + self.i + } +} +"#, + ); + } + + #[test] + fn test_parameter_to_self_error_no_impl() { + check( + "self", + r#" +struct Foo { i: i32 } + +fn f(foo$0: &mut Foo) -> i32 { + foo.i +} +"#, + "error: Cannot rename parameter to self for free function", + ); + check( + "self", + r#" +struct Foo { i: i32 } +struct Bar; + +impl Bar { + fn f(foo$0: &mut Foo) -> i32 { + foo.i + } +} +"#, + "error: Parameter type differs from impl block type", + ); + } + + #[test] + fn test_parameter_to_self_error_not_first() { + check( + "self", + r#" +struct Foo { i: i32 } +impl Foo { + fn f(x: (), foo$0: &mut Foo) -> i32 { + foo.i + } +} +"#, + "error: Only the first parameter may be renamed to self", + ); + } + + #[test] + fn test_parameter_to_self_impl_ref() { + check( + "self", + r#" +struct Foo { i: i32 } +impl &Foo { + fn f(foo$0: &Foo) -> i32 { + foo.i + } +} +"#, + r#" +struct Foo { i: i32 } +impl &Foo { + fn f(self) -> i32 { + self.i + } +} +"#, + ); + } + + #[test] + fn test_self_to_parameter() { + check( + "foo", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(&mut $0self) -> i32 { + self.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo: &mut Foo) -> i32 { + foo.i + } +} +"#, + ); + } + + #[test] + fn test_owned_self_to_parameter() { + cov_mark::check!(rename_self_to_param); + check( + "foo", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f($0self) -> i32 { + self.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo: Foo) -> i32 { + foo.i + } +} +"#, + ); + } + + #[test] + fn test_self_in_path_to_parameter() { + check( + "foo", + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(&self) -> i32 { + let self_var = 1; + self$0.i + } +} +"#, + r#" +struct Foo { i: i32 } + +impl Foo { + fn f(foo: &Foo) -> i32 { + let self_var = 1; + foo.i + } +} +"#, + ); + } + + #[test] + fn test_rename_field_put_init_shorthand() { + cov_mark::check!(test_rename_field_put_init_shorthand); + check( + "bar", + r#" +struct Foo { i$0: i32 } + +fn foo(bar: i32) -> Foo { + Foo { i: bar } +} +"#, + r#" +struct Foo { bar: i32 } + +fn foo(bar: i32) -> Foo { + Foo { bar } +} +"#, + ); + } + + #[test] + fn test_rename_local_put_init_shorthand() { + cov_mark::check!(test_rename_local_put_init_shorthand); + check( + "i", + r#" +struct Foo { i: i32 } + +fn foo(bar$0: i32) -> Foo { + Foo { i: bar } +} +"#, + r#" +struct Foo { i: i32 } + +fn foo(i: i32) -> Foo { + Foo { i } +} +"#, + ); + } + + #[test] + fn test_struct_field_pat_into_shorthand() { + cov_mark::check!(test_rename_field_put_init_shorthand_pat); + check( + "baz", + r#" +struct Foo { i$0: i32 } + +fn foo(foo: Foo) { + let Foo { i: ref baz @ qux } = foo; + let _ = qux; +} +"#, + r#" +struct Foo { baz: i32 } + +fn foo(foo: Foo) { + let Foo { ref baz @ qux } = foo; + let _ = qux; +} +"#, + ); + } + + #[test] + fn test_rename_binding_in_destructure_pat() { + let expected_fixture = r#" +struct Foo { + i: i32, +} + +fn foo(foo: Foo) { + let Foo { i: bar } = foo; + let _ = bar; +} +"#; + check( + "bar", + r#" +struct Foo { + i: i32, +} + +fn foo(foo: Foo) { + let Foo { i: b } = foo; + let _ = b$0; +} +"#, + expected_fixture, + ); + check( + "bar", + r#" +struct Foo { + i: i32, +} + +fn foo(foo: Foo) { + let Foo { i } = foo; + let _ = i$0; +} +"#, + expected_fixture, + ); + } + + #[test] + fn test_rename_binding_in_destructure_param_pat() { + check( + "bar", + r#" +struct Foo { + i: i32 +} + +fn foo(Foo { i }: foo) -> i32 { + i$0 +} +"#, + r#" +struct Foo { + i: i32 +} + +fn foo(Foo { i: bar }: foo) -> i32 { + bar +} +"#, + ) + } + + #[test] + fn test_struct_field_complex_ident_pat() { + check( + "baz", + r#" +struct Foo { i$0: i32 } + +fn foo(foo: Foo) { + let Foo { ref i } = foo; +} +"#, + r#" +struct Foo { baz: i32 } + +fn foo(foo: Foo) { + let Foo { baz: ref i } = foo; +} +"#, + ); + } + + #[test] + fn test_rename_lifetimes() { + cov_mark::check!(rename_lifetime); + check( + "'yeeee", + r#" +trait Foo<'a> { + fn foo() -> &'a (); +} +impl<'a> Foo<'a> for &'a () { + fn foo() -> &'a$0 () { + unimplemented!() + } +} +"#, + r#" +trait Foo<'a> { + fn foo() -> &'a (); +} +impl<'yeeee> Foo<'yeeee> for &'yeeee () { + fn foo() -> &'yeeee () { + unimplemented!() + } +} +"#, + ) + } + + #[test] + fn test_rename_bind_pat() { + check( + "new_name", + r#" +fn main() { + enum CustomOption { + None, + Some(T), + } + + let test_variable = CustomOption::Some(22); + + match test_variable { + CustomOption::Some(foo$0) if foo == 11 => {} + _ => (), + } +}"#, + r#" +fn main() { + enum CustomOption { + None, + Some(T), + } + + let test_variable = CustomOption::Some(22); + + match test_variable { + CustomOption::Some(new_name) if new_name == 11 => {} + _ => (), + } +}"#, + ); + } + + #[test] + fn test_rename_label() { + check( + "'foo", + r#" +fn foo<'a>() -> &'a () { + 'a: { + 'b: loop { + break 'a$0; + } + } +} +"#, + r#" +fn foo<'a>() -> &'a () { + 'foo: { + 'b: loop { + break 'foo; + } + } +} +"#, + ) + } + + #[test] + fn test_self_to_self() { + cov_mark::check!(rename_self_to_self); + check( + "self", + r#" +struct Foo; +impl Foo { + fn foo(self$0) {} +} +"#, + r#" +struct Foo; +impl Foo { + fn foo(self) {} +} +"#, + ) + } + + #[test] + fn test_rename_field_in_pat_in_macro_doesnt_shorthand() { + // ideally we would be able to make this emit a short hand, but I doubt this is easily possible + check( + "baz", + r#" +macro_rules! foo { + ($pattern:pat) => { + let $pattern = loop {}; + }; +} +struct Foo { + bar$0: u32, +} +fn foo() { + foo!(Foo { bar: baz }); +} +"#, + r#" +macro_rules! foo { + ($pattern:pat) => { + let $pattern = loop {}; + }; +} +struct Foo { + baz: u32, +} +fn foo() { + foo!(Foo { baz: baz }); +} +"#, + ) + } + + #[test] + fn test_rename_tuple_field() { + check( + "foo", + r#" +struct Foo(i32); + +fn baz() { + let mut x = Foo(4); + x.0$0 = 5; +} +"#, + "error: No identifier available to rename", + ); + } + + #[test] + fn test_rename_builtin() { + check( + "foo", + r#" +fn foo() { + let x: i32$0 = 0; +} +"#, + "error: Cannot rename builtin type", + ); + } + + #[test] + fn test_rename_self() { + check( + "foo", + r#" +struct Foo {} + +impl Foo { + fn foo(self) -> Self$0 { + self + } +} +"#, + "error: Cannot rename `Self`", + ); + } + + #[test] + fn test_rename_ignores_self_ty() { + check( + "Fo0", + r#" +struct $0Foo; + +impl Foo where Self: {} +"#, + r#" +struct Fo0; + +impl Fo0 where Self: {} +"#, + ); + } + + #[test] + fn test_rename_fails_on_aliases() { + check( + "Baz", + r#" +struct Foo; +use Foo as Bar$0; +"#, + "error: Renaming aliases is currently unsupported", + ); + check( + "Baz", + r#" +struct Foo; +use Foo as Bar; +use Bar$0; +"#, + "error: Renaming aliases is currently unsupported", + ); + } + + #[test] + fn test_rename_trait_method() { + let res = r" +trait Foo { + fn foo(&self) { + self.foo(); + } +} + +impl Foo for () { + fn foo(&self) { + self.foo(); + } +}"; + check( + "foo", + r#" +trait Foo { + fn bar$0(&self) { + self.bar(); + } +} + +impl Foo for () { + fn bar(&self) { + self.bar(); + } +}"#, + res, + ); + check( + "foo", + r#" +trait Foo { + fn bar(&self) { + self.bar$0(); + } +} + +impl Foo for () { + fn bar(&self) { + self.bar(); + } +}"#, + res, + ); + check( + "foo", + r#" +trait Foo { + fn bar(&self) { + self.bar(); + } +} + +impl Foo for () { + fn bar$0(&self) { + self.bar(); + } +}"#, + res, + ); + check( + "foo", + r#" +trait Foo { + fn bar(&self) { + self.bar(); + } +} + +impl Foo for () { + fn bar(&self) { + self.bar$0(); + } +}"#, + res, + ); + } + + #[test] + fn test_rename_trait_const() { + let res = r" +trait Foo { + const FOO: (); +} + +impl Foo for () { + const FOO: (); +} +fn f() { <()>::FOO; }"; + check( + "FOO", + r#" +trait Foo { + const BAR$0: (); +} + +impl Foo for () { + const BAR: (); +} +fn f() { <()>::BAR; }"#, + res, + ); + check( + "FOO", + r#" +trait Foo { + const BAR: (); +} + +impl Foo for () { + const BAR$0: (); +} +fn f() { <()>::BAR; }"#, + res, + ); + check( + "FOO", + r#" +trait Foo { + const BAR: (); +} + +impl Foo for () { + const BAR: (); +} +fn f() { <()>::BAR$0; }"#, + res, + ); + } + + #[test] + fn macros_are_broken_lol() { + cov_mark::check!(macros_are_broken_lol); + check( + "lol", + r#" +macro_rules! m { () => { fn f() {} } } +m!(); +fn main() { f$0() } +"#, + r#" +macro_rules! m { () => { fn f() {} } } +lol +fn main() { lol() } +"#, + ) + } +} -- cgit v1.2.3