Skip to content

Commit d368172

Browse files
committed
fix: migrate unmerge_use to syntax editor
Also ensures that attributes on the use item are applied to the new use item when unmerging. Signed-off-by: Prajwal S N <[email protected]>
1 parent 5bbf2ce commit d368172

File tree

4 files changed

+108
-16
lines changed

4 files changed

+108
-16
lines changed

crates/ide-assists/src/handlers/unmerge_use.rs

+44-15
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use syntax::{
22
AstNode, SyntaxKind,
3-
ast::{self, HasVisibility, edit_in_place::Removable, make},
4-
ted::{self, Position},
3+
ast::{
4+
self, HasAttrs, HasVisibility, edit::IndentLevel, edit_in_place::AttrsOwnerEdit, make,
5+
syntax_factory::SyntaxFactory,
6+
},
7+
syntax_editor::{Element, Position, Removable},
58
};
69

710
use crate::{
@@ -22,20 +25,17 @@ use crate::{
2225
// use std::fmt::Display;
2326
// ```
2427
pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
25-
let tree: ast::UseTree = ctx.find_node_at_offset::<ast::UseTree>()?.clone_for_update();
28+
let tree = ctx.find_node_at_offset::<ast::UseTree>()?;
2629

2730
let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
2831
if tree_list.use_trees().count() < 2 {
2932
cov_mark::hit!(skip_single_use_item);
3033
return None;
3134
}
3235

33-
let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
36+
let use_ = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
3437
let path = resolve_full_path(&tree)?;
3538

36-
let old_parent_range = use_.syntax().parent()?.text_range();
37-
let new_parent = use_.syntax().parent()?;
38-
3939
// If possible, explain what is going to be done.
4040
let label = match tree.path().and_then(|path| path.first_segment()) {
4141
Some(name) => format!("Unmerge use of `{name}`"),
@@ -44,16 +44,30 @@ pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
4444

4545
let target = tree.syntax().text_range();
4646
acc.add(AssistId::refactor_rewrite("unmerge_use"), label, target, |builder| {
47-
let new_use = make::use_(
47+
let make = SyntaxFactory::new();
48+
let new_use = make.use_(
4849
use_.visibility(),
49-
make::use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()),
50-
)
51-
.clone_for_update();
52-
53-
tree.remove();
54-
ted::insert(Position::after(use_.syntax()), new_use.syntax());
50+
make.use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()),
51+
);
52+
// Add any attributes that are present on the use tree
53+
use_.attrs().for_each(|attr| {
54+
new_use.add_attr(attr.clone_for_update());
55+
});
5556

56-
builder.replace(old_parent_range, new_parent.to_string());
57+
let mut editor = builder.make_editor(use_.syntax());
58+
// Remove the use tree from the current use item
59+
tree.remove(&mut editor);
60+
// Insert a newline and indentation, followed by the new use item
61+
editor.insert_all(
62+
Position::after(use_.syntax()),
63+
vec![
64+
make.whitespace(&format!("\n{}", IndentLevel::from_node(use_.syntax())))
65+
.syntax_element(),
66+
new_use.syntax().syntax_element(),
67+
],
68+
);
69+
editor.add_mappings(make.finish_with_mappings());
70+
builder.add_file_edits(ctx.file_id(), editor);
5771
})
5872
}
5973

@@ -230,4 +244,19 @@ pub use std::fmt::Display;
230244
use std::process;",
231245
);
232246
}
247+
248+
#[test]
249+
fn unmerge_use_item_with_attributes() {
250+
check_assist(
251+
unmerge_use,
252+
r"
253+
#[allow(deprecated)]
254+
use foo::{bar, baz$0};",
255+
r"
256+
#[allow(deprecated)]
257+
use foo::{bar};
258+
#[allow(deprecated)]
259+
use foo::baz;",
260+
);
261+
}
233262
}

crates/syntax/src/ast/syntax_factory/constructors.rs

+14
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,20 @@ impl SyntaxFactory {
107107
ast
108108
}
109109

110+
pub fn use_(&self, visibility: Option<ast::Visibility>, use_tree: ast::UseTree) -> ast::Use {
111+
make::use_(visibility, use_tree).clone_for_update()
112+
}
113+
114+
pub fn use_tree(
115+
&self,
116+
path: ast::Path,
117+
use_tree_list: Option<ast::UseTreeList>,
118+
alias: Option<ast::Rename>,
119+
add_star: bool,
120+
) -> ast::UseTree {
121+
make::use_tree(path, use_tree_list, alias, add_star).clone_for_update()
122+
}
123+
110124
pub fn path_unqualified(&self, segment: ast::PathSegment) -> ast::Path {
111125
let ast = make::path_unqualified(segment.clone()).clone_for_update();
112126

crates/syntax/src/syntax_editor.rs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ mod edit_algo;
2020
mod edits;
2121
mod mapping;
2222

23+
pub use edits::Removable;
2324
pub use mapping::{SyntaxMapping, SyntaxMappingBuilder};
2425

2526
#[derive(Debug)]

crates/syntax/src/syntax_editor/edits.rs

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
//! Structural editing for ast using `SyntaxEditor`
22
33
use crate::{
4-
Direction, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T,
4+
AstToken, Direction, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, T,
5+
algo::neighbor,
56
ast::{
67
self, AstNode, Fn, GenericParam, HasGenericParams, HasName, edit::IndentLevel, make,
78
syntax_factory::SyntaxFactory,
@@ -143,6 +144,53 @@ fn normalize_ws_between_braces(editor: &mut SyntaxEditor, node: &SyntaxNode) ->
143144
Some(())
144145
}
145146

147+
pub trait Removable: AstNode {
148+
fn remove(&self, editor: &mut SyntaxEditor);
149+
}
150+
151+
impl Removable for ast::Use {
152+
fn remove(&self, editor: &mut SyntaxEditor) {
153+
let make = SyntaxFactory::new();
154+
155+
let next_ws = self
156+
.syntax()
157+
.next_sibling_or_token()
158+
.and_then(|it| it.into_token())
159+
.and_then(ast::Whitespace::cast);
160+
if let Some(next_ws) = next_ws {
161+
let ws_text = next_ws.syntax().text();
162+
if let Some(rest) = ws_text.strip_prefix('\n') {
163+
if rest.is_empty() {
164+
editor.delete(next_ws.syntax());
165+
} else {
166+
editor.replace(next_ws.syntax(), make.whitespace(rest));
167+
}
168+
}
169+
}
170+
171+
editor.delete(self.syntax());
172+
}
173+
}
174+
175+
impl Removable for ast::UseTree {
176+
fn remove(&self, editor: &mut SyntaxEditor) {
177+
for dir in [Direction::Next, Direction::Prev] {
178+
if let Some(next_use_tree) = neighbor(self, dir) {
179+
let separators = self
180+
.syntax()
181+
.siblings_with_tokens(dir)
182+
.skip(1)
183+
.take_while(|it| it.as_node() != Some(next_use_tree.syntax()));
184+
for sep in separators {
185+
editor.delete(sep);
186+
}
187+
break;
188+
}
189+
}
190+
editor.delete(self.syntax());
191+
}
192+
}
193+
146194
#[cfg(test)]
147195
mod tests {
148196
use parser::Edition;

0 commit comments

Comments
 (0)