Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: create alias when renaming an import. #16489

Merged
merged 2 commits into from
Feb 19, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 108 additions & 15 deletions crates/ide/src/rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -90,24 +91,60 @@ pub(crate) fn rename(
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)
})
.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"))
}
Expand All @@ -130,6 +167,38 @@ pub(crate) fn will_rename_file(
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,
Expand Down Expand Up @@ -2626,7 +2695,8 @@ 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",
);
Expand Down Expand Up @@ -2686,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; }
"#,
)
}
}
Loading