1use syntax::{
2 AstNode, SyntaxKind,
3 ast::{self, HasAttrs, HasVisibility, edit::IndentLevel, syntax_factory::SyntaxFactory},
4 syntax_editor::{Element, Position, Removable, SyntaxEditor},
5};
6
7use crate::{
8 AssistId,
9 assist_context::{AssistContext, Assists},
10};
11
12pub(crate) fn unmerge_imports(acc: &mut Assists, ctx: &AssistContext<'_, '_>) -> Option<()> {
25 let (editor, _) = SyntaxEditor::new(ctx.source_file().syntax().clone());
26 let make = editor.make();
27 let tree = ctx.find_node_at_offset::<ast::UseTree>()?;
28
29 let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
30 if tree_list.use_trees().count() < 2 {
31 cov_mark::hit!(skip_single_import);
32 return None;
33 }
34
35 let use_ = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
36 let path = resolve_full_path(&tree, make)?;
37
38 let label = match tree.path().and_then(|path| path.first_segment()) {
40 Some(name) => format!("Unmerge use of `{name}`"),
41 None => "Unmerge use".into(),
42 };
43
44 let target = tree.syntax().text_range();
45 acc.add(AssistId::refactor_rewrite("unmerge_imports"), label, target, |builder| {
46 let make = editor.make();
47 let new_use = make.use_(
48 use_.attrs(),
49 use_.visibility(),
50 make.use_tree(path, tree.use_tree_list(), tree.rename(), tree.star_token().is_some()),
51 );
52
53 tree.remove(&editor);
55 editor.insert_all(
57 Position::after(use_.syntax()),
58 vec![
59 make.whitespace(&format!("\n{}", IndentLevel::from_node(use_.syntax())))
60 .syntax_element(),
61 new_use.syntax().syntax_element(),
62 ],
63 );
64 builder.add_file_edits(ctx.vfs_file_id(), editor);
65 })
66}
67
68fn resolve_full_path(tree: &ast::UseTree, make: &SyntaxFactory) -> Option<ast::Path> {
69 let paths = tree
70 .syntax()
71 .ancestors()
72 .take_while(|n| n.kind() != SyntaxKind::USE)
73 .filter_map(ast::UseTree::cast)
74 .filter_map(|t| t.path());
75
76 let final_path = paths.reduce(|prev, next| make.path_concat(next, prev))?;
77 if final_path.segment().is_some_and(|it| it.self_token().is_some()) {
78 final_path.qualifier()
79 } else {
80 Some(final_path)
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use crate::tests::{check_assist, check_assist_not_applicable};
87
88 use super::*;
89
90 #[test]
91 fn skip_single_import() {
92 cov_mark::check!(skip_single_import);
93 check_assist_not_applicable(
94 unmerge_imports,
95 r"
96use std::fmt::Debug$0;
97",
98 );
99 check_assist_not_applicable(
100 unmerge_imports,
101 r"
102use std::fmt::{Debug$0};
103",
104 );
105 check_assist_not_applicable(
106 unmerge_imports,
107 r"
108use std::fmt::Debug as Dbg$0;
109",
110 );
111 }
112
113 #[test]
114 fn skip_single_glob_import() {
115 check_assist_not_applicable(
116 unmerge_imports,
117 r"
118use std::fmt::*$0;
119",
120 );
121 }
122
123 #[test]
124 fn unmerge_import() {
125 check_assist(
126 unmerge_imports,
127 r"
128use std::fmt::{Debug, Display$0};
129",
130 r"
131use std::fmt::{Debug};
132use std::fmt::Display;
133",
134 );
135
136 check_assist(
137 unmerge_imports,
138 r"
139use std::fmt::{Debug, format$0, Display};
140",
141 r"
142use std::fmt::{Debug, Display};
143use std::fmt::format;
144",
145 );
146 }
147
148 #[test]
149 fn unmerge_glob_import() {
150 check_assist(
151 unmerge_imports,
152 r"
153use std::fmt::{*$0, Display};
154",
155 r"
156use std::fmt::{Display};
157use std::fmt::*;
158",
159 );
160 }
161
162 #[test]
163 fn unmerge_renamed_import() {
164 check_assist(
165 unmerge_imports,
166 r"
167use std::fmt::{Debug, Display as Disp$0};
168",
169 r"
170use std::fmt::{Debug};
171use std::fmt::Display as Disp;
172",
173 );
174 }
175
176 #[test]
177 fn unmerge_indented_import() {
178 check_assist(
179 unmerge_imports,
180 r"
181mod format {
182 use std::fmt::{Debug, Display$0 as Disp, format};
183}
184",
185 r"
186mod format {
187 use std::fmt::{Debug, format};
188 use std::fmt::Display as Disp;
189}
190",
191 );
192 }
193
194 #[test]
195 fn unmerge_nested_import() {
196 check_assist(
197 unmerge_imports,
198 r"
199use foo::bar::{baz::{qux$0, foobar}, barbaz};
200",
201 r"
202use foo::bar::{baz::{foobar}, barbaz};
203use foo::bar::baz::qux;
204",
205 );
206 check_assist(
207 unmerge_imports,
208 r"
209use foo::bar::{baz$0::{qux, foobar}, barbaz};
210",
211 r"
212use foo::bar::{barbaz};
213use foo::bar::baz::{qux, foobar};
214",
215 );
216 }
217
218 #[test]
219 fn unmerge_import_with_visibility() {
220 check_assist(
221 unmerge_imports,
222 r"
223pub use std::fmt::{Debug, Display$0};
224",
225 r"
226pub use std::fmt::{Debug};
227pub use std::fmt::Display;
228",
229 );
230 }
231
232 #[test]
233 fn unmerge_import_on_self() {
234 check_assist(
235 unmerge_imports,
236 r"use std::process::{Command, self$0};",
237 r"use std::process::{Command};
238use std::process;",
239 );
240 }
241
242 #[test]
243 fn unmerge_import_with_attributes() {
244 check_assist(
245 unmerge_imports,
246 r"
247#[allow(deprecated)]
248use foo::{bar, baz$0};",
249 r"
250#[allow(deprecated)]
251use foo::{bar};
252#[allow(deprecated)]
253use foo::baz;",
254 );
255 }
256}