]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/unmerge_use.rs
Merge #9467
[rust.git] / crates / ide_assists / src / handlers / unmerge_use.rs
1 use ast::make;
2 use itertools::Itertools;
3 use syntax::{
4     ast::{self, VisibilityOwner},
5     ted::{self, Position},
6     AstNode, SyntaxKind,
7 };
8
9 use crate::{
10     assist_context::{AssistContext, Assists},
11     AssistId, AssistKind,
12 };
13
14 // Assist: unmerge_use
15 //
16 // Extracts single use item from use list.
17 //
18 // ```
19 // use std::fmt::{Debug, Display$0};
20 // ```
21 // ->
22 // ```
23 // use std::fmt::{Debug};
24 // use std::fmt::Display;
25 // ```
26 pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27     let tree: ast::UseTree = ctx.find_node_at_offset::<ast::UseTree>()?.clone_for_update();
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_use_item);
32         return None;
33     }
34
35     let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
36     let path = resolve_full_path(&tree)?;
37
38     let old_parent_range = use_.syntax().parent()?.text_range();
39     let new_parent = use_.syntax().parent()?;
40
41     let target = tree.syntax().text_range();
42     acc.add(
43         AssistId("unmerge_use", AssistKind::RefactorRewrite),
44         "Unmerge use",
45         target,
46         |builder| {
47             let new_use = make::use_(
48                 use_.visibility(),
49                 make::use_tree(
50                     path,
51                     tree.use_tree_list(),
52                     tree.rename(),
53                     tree.star_token().is_some(),
54                 ),
55             )
56             .clone_for_update();
57
58             tree.remove();
59             ted::insert(Position::after(use_.syntax()), new_use.syntax());
60
61             builder.replace(old_parent_range, new_parent.to_string());
62         },
63     )
64 }
65
66 fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
67     let paths = tree
68         .syntax()
69         .ancestors()
70         .take_while(|n| n.kind() != SyntaxKind::USE)
71         .filter_map(ast::UseTree::cast)
72         .filter_map(|t| t.path());
73
74     let final_path = paths.fold1(|prev, next| make::path_concat(next, prev))?;
75     if final_path.segment().map_or(false, |it| it.self_token().is_some()) {
76         final_path.qualifier()
77     } else {
78         Some(final_path)
79     }
80 }
81
82 #[cfg(test)]
83 mod tests {
84     use crate::tests::{check_assist, check_assist_not_applicable};
85
86     use super::*;
87
88     #[test]
89     fn skip_single_use_item() {
90         cov_mark::check!(skip_single_use_item);
91         check_assist_not_applicable(
92             unmerge_use,
93             r"
94 use std::fmt::Debug$0;
95 ",
96         );
97         check_assist_not_applicable(
98             unmerge_use,
99             r"
100 use std::fmt::{Debug$0};
101 ",
102         );
103         check_assist_not_applicable(
104             unmerge_use,
105             r"
106 use std::fmt::Debug as Dbg$0;
107 ",
108         );
109     }
110
111     #[test]
112     fn skip_single_glob_import() {
113         check_assist_not_applicable(
114             unmerge_use,
115             r"
116 use std::fmt::*$0;
117 ",
118         );
119     }
120
121     #[test]
122     fn unmerge_use_item() {
123         check_assist(
124             unmerge_use,
125             r"
126 use std::fmt::{Debug, Display$0};
127 ",
128             r"
129 use std::fmt::{Debug};
130 use std::fmt::Display;
131 ",
132         );
133
134         check_assist(
135             unmerge_use,
136             r"
137 use std::fmt::{Debug, format$0, Display};
138 ",
139             r"
140 use std::fmt::{Debug, Display};
141 use std::fmt::format;
142 ",
143         );
144     }
145
146     #[test]
147     fn unmerge_glob_import() {
148         check_assist(
149             unmerge_use,
150             r"
151 use std::fmt::{*$0, Display};
152 ",
153             r"
154 use std::fmt::{Display};
155 use std::fmt::*;
156 ",
157         );
158     }
159
160     #[test]
161     fn unmerge_renamed_use_item() {
162         check_assist(
163             unmerge_use,
164             r"
165 use std::fmt::{Debug, Display as Disp$0};
166 ",
167             r"
168 use std::fmt::{Debug};
169 use std::fmt::Display as Disp;
170 ",
171         );
172     }
173
174     #[test]
175     fn unmerge_indented_use_item() {
176         check_assist(
177             unmerge_use,
178             r"
179 mod format {
180     use std::fmt::{Debug, Display$0 as Disp, format};
181 }
182 ",
183             r"
184 mod format {
185     use std::fmt::{Debug, format};
186     use std::fmt::Display as Disp;
187 }
188 ",
189         );
190     }
191
192     #[test]
193     fn unmerge_nested_use_item() {
194         check_assist(
195             unmerge_use,
196             r"
197 use foo::bar::{baz::{qux$0, foobar}, barbaz};
198 ",
199             r"
200 use foo::bar::{baz::{foobar}, barbaz};
201 use foo::bar::baz::qux;
202 ",
203         );
204         check_assist(
205             unmerge_use,
206             r"
207 use foo::bar::{baz$0::{qux, foobar}, barbaz};
208 ",
209             r"
210 use foo::bar::{barbaz};
211 use foo::bar::baz::{qux, foobar};
212 ",
213         );
214     }
215
216     #[test]
217     fn unmerge_use_item_with_visibility() {
218         check_assist(
219             unmerge_use,
220             r"
221 pub use std::fmt::{Debug, Display$0};
222 ",
223             r"
224 pub use std::fmt::{Debug};
225 pub use std::fmt::Display;
226 ",
227         );
228     }
229
230     #[test]
231     fn unmerge_use_item_on_self() {
232         check_assist(
233             unmerge_use,
234             r"use std::process::{Command, self$0};",
235             r"use std::process::{Command};
236 use std::process;",
237         );
238     }
239 }