Skip to content

Commit c3996d5

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 c3996d5

File tree

1 file changed

+106
-14
lines changed

1 file changed

+106
-14
lines changed

crates/ide/src/rename.rs

+106-14
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,60 @@ 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(syntax, position, new_name);
96+
97+
let ops: RenameResult<Vec<SourceChange>> = match alias_fallback {
98+
Some(_) => defs
99+
// FIXME: This can use the `ide_db::rename_reference` (or def.rename) method once we can
100+
// properly find "direct" usages/references.
101+
.map(|(.., def)| {
102+
match IdentifierKind::classify(new_name)? {
103+
IdentifierKind::Ident => (),
104+
IdentifierKind::Lifetime => {
105+
bail!("Cannot alias reference to a lifetime identifier")
106+
}
107+
IdentifierKind::Underscore => bail!("Cannot alias reference to `_`"),
108+
};
94109

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);
110+
let mut usages = def.usages(&sema).all();
111+
112+
// FIXME: hack - removes the usage that triggered this rename operation.
113+
match usages.references.get_mut(&position.file_id).and_then(|refs| {
114+
refs.iter()
115+
.position(|ref_| ref_.range.contains_inclusive(position.offset))
116+
.map(|idx| refs.remove(idx))
117+
}) {
118+
Some(_) => (),
119+
None => never!(),
120+
};
121+
122+
let mut source_change = SourceChange::default();
123+
source_change.extend(usages.iter().map(|(&file_id, refs)| {
124+
(file_id, source_edit_from_references(refs, def, new_name))
125+
}));
126+
127+
Ok(source_change)
128+
})
129+
.collect(),
130+
None => defs
131+
.map(|(.., def)| {
132+
if let Definition::Local(local) = def {
133+
if let Some(self_param) = local.as_self_param(sema.db) {
134+
cov_mark::hit!(rename_self_to_param);
135+
return rename_self_to_param(&sema, local, self_param, new_name);
136+
}
137+
if new_name == "self" {
138+
cov_mark::hit!(rename_to_self);
139+
return rename_to_self(&sema, local);
140+
}
105141
}
106-
}
107-
def.rename(&sema, new_name, rename_external)
108-
})
109-
.collect();
142+
def.rename(&sema, new_name, rename_external)
143+
})
144+
.collect(),
145+
};
110146

111147
ops?.into_iter()
148+
.chain(alias_fallback)
112149
.reduce(|acc, elem| acc.merge(elem))
113150
.ok_or_else(|| format_err!("No references found at position"))
114151
}
@@ -131,6 +168,38 @@ pub(crate) fn will_rename_file(
131168
Some(change)
132169
}
133170

171+
// FIXME: Should support `extern crate`.
172+
fn alias_fallback(
173+
syntax: &SyntaxNode,
174+
FilePosition { file_id, offset }: FilePosition,
175+
new_name: &str,
176+
) -> Option<SourceChange> {
177+
let use_tree = syntax
178+
.token_at_offset(offset)
179+
.flat_map(|syntax| syntax.parent_ancestors())
180+
.find_map(ast::UseTree::cast)?;
181+
182+
let last_path_segment = use_tree.path()?.segments().last()?.name_ref()?;
183+
if !last_path_segment.syntax().text_range().contains_inclusive(offset) {
184+
return None;
185+
};
186+
187+
let mut builder = SourceChangeBuilder::new(file_id);
188+
189+
match use_tree.rename() {
190+
Some(rename) => {
191+
let offset = rename.syntax().text_range();
192+
builder.replace(offset, format!("as {new_name}"));
193+
}
194+
None => {
195+
let offset = use_tree.syntax().text_range().end();
196+
builder.insert(offset, format!(" as {new_name}"));
197+
}
198+
}
199+
200+
Some(builder.finish())
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)