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