Skip to content

Commit 911f76e

Browse files
feat: append as <name> when renaming inside an "UseTree".
test: include `rename_path_inside_use_tree`. Keeps tracks the progress of the changes. 3 other tests broke with the changes of this. feat: rename all other usages within the current file. feat: fix most of the implementation problems. test: `rename_path_inside_use_tree` tests a more complicated scenario.
1 parent 8f6a728 commit 911f76e

File tree

1 file changed

+106
-14
lines changed

1 file changed

+106
-14
lines changed

crates/ide/src/rename.rs

Lines changed: 106 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use ide_db::{
99
base_db::{FileId, FileRange},
1010
defs::{Definition, NameClass, NameRefClass},
1111
rename::{bail, format_err, source_edit_from_references, IdentifierKind},
12+
source_change::SourceChangeBuilder,
1213
RootDatabase,
1314
};
1415
use itertools::Itertools;
@@ -91,24 +92,52 @@ pub(crate) fn rename(
9192
let syntax = source_file.syntax();
9293

9394
let defs = find_definitions(&sema, syntax, position)?;
95+
let alias_fallback = alias_fallback(&sema, syntax, position, new_name)?;
96+
97+
let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {
98+
Some(_) => defs
99+
.map(|(.., def)| {
100+
let mut usages = def.usages(&sema).all();
101+
102+
// Remove the usage that triggered this rename operation.
103+
match usages.references.get_mut(&position.file_id) {
104+
Some(refs) => {
105+
let position = refs
106+
.iter()
107+
.position(|ref_| ref_.range.contains_inclusive(position.offset));
108+
never!(position.is_none());
109+
refs.remove(position.unwrap());
110+
}
111+
None => never!(),
112+
};
94113

95-
let ops: RenameResult<Vec<SourceChange>> = defs
96-
.map(|(.., def)| {
97-
if let Definition::Local(local) = def {
98-
if let Some(self_param) = local.as_self_param(sema.db) {
99-
cov_mark::hit!(rename_self_to_param);
100-
return rename_self_to_param(&sema, local, self_param, new_name);
101-
}
102-
if new_name == "self" {
103-
cov_mark::hit!(rename_to_self);
104-
return rename_to_self(&sema, local);
114+
let mut source_change = SourceChange::default();
115+
source_change.extend(usages.iter().map(|(&file_id, refs)| {
116+
(file_id, source_edit_from_references(refs, def, new_name))
117+
}));
118+
119+
Ok(source_change)
120+
})
121+
.collect(),
122+
None => defs
123+
.map(|(.., def)| {
124+
if let Definition::Local(local) = def {
125+
if let Some(self_param) = local.as_self_param(sema.db) {
126+
cov_mark::hit!(rename_self_to_param);
127+
return rename_self_to_param(&sema, local, self_param, new_name);
128+
}
129+
if new_name == "self" {
130+
cov_mark::hit!(rename_to_self);
131+
return rename_to_self(&sema, local);
132+
}
105133
}
106-
}
107-
def.rename(&sema, new_name, rename_external)
108-
})
109-
.collect();
134+
def.rename(&sema, new_name, rename_external)
135+
})
136+
.collect(),
137+
};
110138

111139
ops?.into_iter()
140+
.chain(alias_fallback)
112141
.reduce(|acc, elem| acc.merge(elem))
113142
.ok_or_else(|| format_err!("No references found at position"))
114143
}
@@ -131,6 +160,46 @@ pub(crate) fn will_rename_file(
131160
Some(change)
132161
}
133162

163+
// FIXME: Allow usage with `extern crate`?
164+
fn alias_fallback(
165+
sema: &Semantics<'_, RootDatabase>,
166+
syntax: &SyntaxNode,
167+
FilePosition { file_id, offset }: FilePosition,
168+
new_name: &str,
169+
) -> RenameResult<Option<SourceChange>> {
170+
match sema.find_node_at_offset_with_descend::<ast::UseTree>(syntax, offset) {
171+
Some(use_tree) => {
172+
if let Some(_rename) = use_tree.rename() {
173+
return Ok(None); // FIXME: path already has an alias
174+
};
175+
176+
if let Some(last_segment) = use_tree.path().and_then(|path| path.segments().last()) {
177+
if !last_segment
178+
.name_ref()
179+
.is_some_and(|name| name.syntax().text_range().contains_inclusive(offset))
180+
{
181+
return Ok(None); // FIXME: path isn't last path segment
182+
}
183+
};
184+
185+
match IdentifierKind::classify(new_name)? {
186+
IdentifierKind::Ident => (),
187+
IdentifierKind::Lifetime => {
188+
bail!("Cannot alias reference to a lifetime identifier")
189+
}
190+
IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),
191+
};
192+
193+
let mut builder = SourceChangeBuilder::new(file_id);
194+
let offset = use_tree.syntax().text_range().end();
195+
builder.insert(offset, format!(" as {new_name}"));
196+
197+
Ok(Some(builder.finish()))
198+
}
199+
None => Ok(None),
200+
}
201+
}
202+
134203
fn find_definitions(
135204
sema: &Semantics<'_, RootDatabase>,
136205
syntax: &SyntaxNode,
@@ -2707,4 +2776,27 @@ fn test() {
27072776
"#,
27082777
);
27092778
}
2779+
2780+
#[test]
2781+
fn rename_path_inside_use_tree() {
2782+
check(
2783+
"Baz",
2784+
r#"
2785+
mod foo { pub struct Foo; }
2786+
mod bar { use super::Foo; }
2787+
2788+
use foo::Foo$0;
2789+
2790+
fn main() { let _: Foo; }
2791+
"#,
2792+
r#"
2793+
mod foo { pub struct Foo; }
2794+
mod bar { use super::Baz; }
2795+
2796+
use foo::Foo as Baz;
2797+
2798+
fn main() { let _: Baz; }
2799+
"#,
2800+
)
2801+
}
27102802
}

0 commit comments

Comments
 (0)