mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 12:29:21 +00:00
Auto merge of #16489 - UserIsntAvailable:feat/use-import-alias, r=Veykril
feat!: create alias when renaming an import.

Closes #15858
Implemented:
- [x] - Prevent using `reserved` keywords (e.g self) and `_`.
- [x] - Rename other modules that might be referencing the import.
- [x] - Fix "broken" tests.
- [ ] - Rename **only** "direct" references.
- [ ] - Test more cases.
Future possibilities:
1. Also support `extern crate <name>` syntax.
2. Allow aliasing `self` when it is inside an `UseTreeList`.
~3. If import path already has an alias, "rename" the alias.~
~[4. Create alias even if path is not the last path segment.](https://github.com/rust-lang/rust-analyzer/pull/16489#issuecomment-1930541697)~
This commit is contained in:
commit
ac1029fac3
1 changed files with 108 additions and 15 deletions
|
@ -9,6 +9,7 @@ use ide_db::{
|
||||||
base_db::{FileId, FileRange},
|
base_db::{FileId, FileRange},
|
||||||
defs::{Definition, NameClass, NameRefClass},
|
defs::{Definition, NameClass, NameRefClass},
|
||||||
rename::{bail, format_err, source_edit_from_references, IdentifierKind},
|
rename::{bail, format_err, source_edit_from_references, IdentifierKind},
|
||||||
|
source_change::SourceChangeBuilder,
|
||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
@ -90,24 +91,60 @@ pub(crate) fn rename(
|
||||||
let syntax = source_file.syntax();
|
let syntax = source_file.syntax();
|
||||||
|
|
||||||
let defs = find_definitions(&sema, syntax, position)?;
|
let defs = find_definitions(&sema, syntax, position)?;
|
||||||
|
let alias_fallback = alias_fallback(syntax, position, new_name);
|
||||||
|
|
||||||
let ops: RenameResult<Vec<SourceChange>> = defs
|
let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {
|
||||||
.map(|(.., def)| {
|
Some(_) => defs
|
||||||
if let Definition::Local(local) = def {
|
// FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
|
||||||
if let Some(self_param) = local.as_self_param(sema.db) {
|
// properly find "direct" usages/references.
|
||||||
cov_mark::hit!(rename_self_to_param);
|
.map(|(.., def)| {
|
||||||
return rename_self_to_param(&sema, local, self_param, new_name);
|
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 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if new_name == "self" {
|
def.rename(&sema, new_name)
|
||||||
cov_mark::hit!(rename_to_self);
|
})
|
||||||
return rename_to_self(&sema, local);
|
.collect(),
|
||||||
}
|
};
|
||||||
}
|
|
||||||
def.rename(&sema, new_name)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
ops?.into_iter()
|
ops?.into_iter()
|
||||||
|
.chain(alias_fallback)
|
||||||
.reduce(|acc, elem| acc.merge(elem))
|
.reduce(|acc, elem| acc.merge(elem))
|
||||||
.ok_or_else(|| format_err!("No references found at position"))
|
.ok_or_else(|| format_err!("No references found at position"))
|
||||||
}
|
}
|
||||||
|
@ -130,6 +167,38 @@ pub(crate) fn will_rename_file(
|
||||||
Some(change)
|
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(
|
fn find_definitions(
|
||||||
sema: &Semantics<'_, RootDatabase>,
|
sema: &Semantics<'_, RootDatabase>,
|
||||||
syntax: &SyntaxNode,
|
syntax: &SyntaxNode,
|
||||||
|
@ -2626,7 +2695,8 @@ use qux as frob;
|
||||||
//- /lib.rs crate:lib new_source_root:library
|
//- /lib.rs crate:lib new_source_root:library
|
||||||
pub struct S;
|
pub struct S;
|
||||||
//- /main.rs crate:main deps:lib new_source_root:local
|
//- /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",
|
"error: Cannot rename a non-local definition",
|
||||||
);
|
);
|
||||||
|
@ -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…
Add table
Add a link
Reference in a new issue