Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide/src/rename.rs')
-rw-r--r--crates/ide/src/rename.rs162
1 files changed, 117 insertions, 45 deletions
diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs
index 9fce4bb0f8..f78153df38 100644
--- a/crates/ide/src/rename.rs
+++ b/crates/ide/src/rename.rs
@@ -9,6 +9,7 @@ use ide_db::{
base_db::{FileId, FileRange},
defs::{Definition, NameClass, NameRefClass},
rename::{bail, format_err, source_edit_from_references, IdentifierKind},
+ source_change::SourceChangeBuilder,
RootDatabase,
};
use itertools::Itertools;
@@ -84,31 +85,66 @@ pub(crate) fn rename(
db: &RootDatabase,
position: FilePosition,
new_name: &str,
- rename_external: bool,
) -> RenameResult<SourceChange> {
let sema = Semantics::new(db);
let source_file = sema.parse(position.file_id);
let syntax = source_file.syntax();
let defs = find_definitions(&sema, syntax, position)?;
+ let alias_fallback = alias_fallback(syntax, position, new_name);
+
+ let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {
+ Some(_) => defs
+ // FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
+ // properly find "direct" usages/references.
+ .map(|(.., def)| {
+ match IdentifierKind::classify(new_name)? {
+ IdentifierKind::Ident => (),
+ IdentifierKind::Lifetime => {
+ bail!("Cannot alias reference to a lifetime identifier")
+ }
+ IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),
+ };
- let ops: RenameResult<Vec<SourceChange>> = defs
- .map(|(.., def)| {
- 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);
+ let mut usages = def.usages(&sema).all();
+
+ // FIXME: hack - removes the usage that triggered this rename operation.
+ match usages.references.get_mut(&position.file_id).and_then(|refs| {
+ refs.iter()
+ .position(|ref_| ref_.range.contains_inclusive(position.offset))
+ .map(|idx| refs.remove(idx))
+ }) {
+ Some(_) => (),
+ None => never!(),
+ };
+
+ let mut source_change = SourceChange::default();
+ source_change.extend(usages.iter().map(|(&file_id, refs)| {
+ (file_id, source_edit_from_references(refs, def, new_name))
+ }));
+
+ Ok(source_change)
+ })
+ .collect(),
+ None => defs
+ .map(|(.., def)| {
+ 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, rename_external)
- })
- .collect();
+ def.rename(&sema, new_name)
+ })
+ .collect(),
+ };
ops?.into_iter()
+ .chain(alias_fallback)
.reduce(|acc, elem| acc.merge(elem))
.ok_or_else(|| format_err!("No references found at position"))
}
@@ -123,14 +159,46 @@ pub(crate) fn will_rename_file(
let module = sema.to_module_def(file_id)?;
let def = Definition::Module(module);
let mut change = if is_raw_identifier(new_name_stem) {
- def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem]), true).ok()?
+ def.rename(&sema, &SmolStr::from_iter(["r#", new_name_stem])).ok()?
} else {
- def.rename(&sema, new_name_stem, true).ok()?
+ def.rename(&sema, new_name_stem).ok()?
};
change.file_system_edits.clear();
Some(change)
}
+// FIXME: Should support `extern crate`.
+fn alias_fallback(
+ syntax: &SyntaxNode,
+ FilePosition { file_id, offset }: FilePosition,
+ new_name: &str,
+) -> Option<SourceChange> {
+ let use_tree = syntax
+ .token_at_offset(offset)
+ .flat_map(|syntax| syntax.parent_ancestors())
+ .find_map(ast::UseTree::cast)?;
+
+ let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;
+ if !last_path_segment.syntax().text_range().contains_inclusive(offset) {
+ return None;
+ };
+
+ let mut builder = SourceChangeBuilder::new(file_id);
+
+ match use_tree.rename() {
+ Some(rename) => {
+ let offset = rename.syntax().text_range();
+ builder.replace(offset, format!("as {new_name}"));
+ }
+ None => {
+ let offset = use_tree.syntax().text_range().end();
+ builder.insert(offset, format!(" as {new_name}"));
+ }
+ }
+
+ Some(builder.finish())
+}
+
fn find_definitions(
sema: &Semantics<'_, RootDatabase>,
syntax: &SyntaxNode,
@@ -377,16 +445,11 @@ mod tests {
use super::{RangeInfo, RenameError};
fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
- check_with_rename_config(new_name, ra_fixture_before, ra_fixture_after, true);
+ check_with_rename_config(new_name, ra_fixture_before, ra_fixture_after);
}
#[track_caller]
- fn check_with_rename_config(
- new_name: &str,
- ra_fixture_before: &str,
- ra_fixture_after: &str,
- rename_external: bool,
- ) {
+ fn check_with_rename_config(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);
if !ra_fixture_after.starts_with("error: ") {
@@ -395,7 +458,7 @@ mod tests {
}
}
let rename_result = analysis
- .rename(position, new_name, rename_external)
+ .rename(position, new_name)
.unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));
match rename_result {
Ok(source_change) => {
@@ -426,10 +489,8 @@ mod tests {
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, true)
- .unwrap()
- .expect("Expect returned a RenameError");
+ let source_change =
+ analysis.rename(position, new_name).unwrap().expect("Expect returned a RenameError");
expect.assert_eq(&filter_expect(source_change))
}
@@ -2634,21 +2695,10 @@ use qux as frob;
//- /lib.rs crate:lib new_source_root:library
pub struct S;
//- /main.rs crate:main deps:lib new_source_root:local
-use lib::S$0;
+use lib::S;
+fn main() { let _: S$0; }
"#,
- "error: Cannot rename a non-local definition as the config for it is disabled",
- false,
- );
-
- check(
- "Baz",
- r#"
-//- /lib.rs crate:lib new_source_root:library
-pub struct S;
-//- /main.rs crate:main deps:lib new_source_root:local
-use lib::S$0;
-"#,
- "use lib::Baz;\n",
+ "error: Cannot rename a non-local definition",
);
}
@@ -2663,8 +2713,7 @@ use core::hash::Hash;
#[derive(H$0ash)]
struct A;
"#,
- "error: Cannot rename a non-local definition as the config for it is disabled",
- false,
+ "error: Cannot rename a non-local definition",
);
}
@@ -2707,4 +2756,27 @@ fn test() {
"#,
);
}
+
+ #[test]
+ fn rename_path_inside_use_tree() {
+ check(
+ "Baz",
+ r#"
+mod foo { pub struct Foo; }
+mod bar { use super::Foo; }
+
+use foo::Foo$0;
+
+fn main() { let _: Foo; }
+"#,
+ r#"
+mod foo { pub struct Foo; }
+mod bar { use super::Baz; }
+
+use foo::Foo as Baz;
+
+fn main() { let _: Baz; }
+"#,
+ )
+ }
}