]> git.lizzy.rs Git - rust.git/blob - crates/assists/src/handlers/merge_imports.rs
Merge #6013
[rust.git] / crates / assists / src / handlers / merge_imports.rs
1 use syntax::{
2     algo::{neighbor, SyntaxRewriter},
3     ast, AstNode,
4 };
5
6 use crate::{
7     assist_context::{AssistContext, Assists},
8     utils::{
9         insert_use::{try_merge_imports, try_merge_trees},
10         next_prev, MergeBehaviour,
11     },
12     AssistId, AssistKind,
13 };
14
15 // Assist: merge_imports
16 //
17 // Merges two imports with a common prefix.
18 //
19 // ```
20 // use std::<|>fmt::Formatter;
21 // use std::io;
22 // ```
23 // ->
24 // ```
25 // use std::{fmt::Formatter, io};
26 // ```
27 pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28     let tree: ast::UseTree = ctx.find_node_at_offset()?;
29     let mut rewriter = SyntaxRewriter::default();
30     let mut offset = ctx.offset();
31
32     if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
33         let (merged, to_delete) =
34             next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
35                 try_merge_imports(&use_item, &use_item2, MergeBehaviour::Full).zip(Some(use_item2))
36             })?;
37
38         rewriter.replace_ast(&use_item, &merged);
39         rewriter += to_delete.remove();
40
41         if to_delete.syntax().text_range().end() < offset {
42             offset -= to_delete.syntax().text_range().len();
43         }
44     } else {
45         let (merged, to_delete) =
46             next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
47                 try_merge_trees(&tree, &use_tree, MergeBehaviour::Full).zip(Some(use_tree))
48             })?;
49
50         rewriter.replace_ast(&tree, &merged);
51         rewriter += to_delete.remove();
52
53         if to_delete.syntax().text_range().end() < offset {
54             offset -= to_delete.syntax().text_range().len();
55         }
56     };
57
58     let target = tree.syntax().text_range();
59     acc.add(
60         AssistId("merge_imports", AssistKind::RefactorRewrite),
61         "Merge imports",
62         target,
63         |builder| {
64             builder.rewrite(rewriter);
65         },
66     )
67 }
68
69 #[cfg(test)]
70 mod tests {
71     use crate::tests::{check_assist, check_assist_not_applicable};
72
73     use super::*;
74
75     #[test]
76     fn test_merge_first() {
77         check_assist(
78             merge_imports,
79             r"
80 use std::fmt<|>::Debug;
81 use std::fmt::Display;
82 ",
83             r"
84 use std::fmt::{Debug, Display};
85 ",
86         )
87     }
88
89     #[test]
90     fn test_merge_second() {
91         check_assist(
92             merge_imports,
93             r"
94 use std::fmt::Debug;
95 use std::fmt<|>::Display;
96 ",
97             r"
98 use std::fmt::{Display, Debug};
99 ",
100         );
101     }
102
103     #[test]
104     fn merge_self1() {
105         check_assist(
106             merge_imports,
107             r"
108 use std::fmt<|>;
109 use std::fmt::Display;
110 ",
111             r"
112 use std::fmt::{self, Display};
113 ",
114         );
115     }
116
117     #[test]
118     fn merge_self2() {
119         check_assist(
120             merge_imports,
121             r"
122 use std::{fmt, <|>fmt::Display};
123 ",
124             r"
125 use std::{fmt::{Display, self}};
126 ",
127         );
128     }
129
130     #[test]
131     fn skip_pub1() {
132         check_assist_not_applicable(
133             merge_imports,
134             r"
135 pub use std::fmt<|>::Debug;
136 use std::fmt::Display;
137 ",
138         );
139     }
140
141     #[test]
142     fn skip_pub_last() {
143         check_assist_not_applicable(
144             merge_imports,
145             r"
146 use std::fmt<|>::Debug;
147 pub use std::fmt::Display;
148 ",
149         );
150     }
151
152     #[test]
153     fn skip_pub_crate_pub() {
154         check_assist_not_applicable(
155             merge_imports,
156             r"
157 pub(crate) use std::fmt<|>::Debug;
158 pub use std::fmt::Display;
159 ",
160         );
161     }
162
163     #[test]
164     fn skip_pub_pub_crate() {
165         check_assist_not_applicable(
166             merge_imports,
167             r"
168 pub use std::fmt<|>::Debug;
169 pub(crate) use std::fmt::Display;
170 ",
171         );
172     }
173
174     #[test]
175     fn merge_pub() {
176         check_assist(
177             merge_imports,
178             r"
179 pub use std::fmt<|>::Debug;
180 pub use std::fmt::Display;
181 ",
182             r"
183 pub use std::fmt::{Debug, Display};
184 ",
185         )
186     }
187
188     #[test]
189     fn merge_pub_crate() {
190         check_assist(
191             merge_imports,
192             r"
193 pub(crate) use std::fmt<|>::Debug;
194 pub(crate) use std::fmt::Display;
195 ",
196             r"
197 pub(crate) use std::fmt::{Debug, Display};
198 ",
199         )
200     }
201
202     #[test]
203     fn test_merge_nested() {
204         check_assist(
205             merge_imports,
206             r"
207 use std::{fmt<|>::Debug, fmt::Display};
208 ",
209             r"
210 use std::{fmt::{Debug, Display}};
211 ",
212         );
213         check_assist(
214             merge_imports,
215             r"
216 use std::{fmt::Debug, fmt<|>::Display};
217 ",
218             r"
219 use std::{fmt::{Display, Debug}};
220 ",
221         );
222     }
223
224     #[test]
225     fn test_merge_single_wildcard_diff_prefixes() {
226         check_assist(
227             merge_imports,
228             r"
229 use std<|>::cell::*;
230 use std::str;
231 ",
232             r"
233 use std::{cell::*, str};
234 ",
235         )
236     }
237
238     #[test]
239     fn test_merge_both_wildcard_diff_prefixes() {
240         check_assist(
241             merge_imports,
242             r"
243 use std<|>::cell::*;
244 use std::str::*;
245 ",
246             r"
247 use std::{cell::*, str::*};
248 ",
249         )
250     }
251
252     #[test]
253     fn removes_just_enough_whitespace() {
254         check_assist(
255             merge_imports,
256             r"
257 use foo<|>::bar;
258 use foo::baz;
259
260 /// Doc comment
261 ",
262             r"
263 use foo::{bar, baz};
264
265 /// Doc comment
266 ",
267         );
268     }
269
270     #[test]
271     fn works_with_trailing_comma() {
272         check_assist(
273             merge_imports,
274             r"
275 use {
276     foo<|>::bar,
277     foo::baz,
278 };
279 ",
280             r"
281 use {
282     foo::{bar, baz},
283 };
284 ",
285         );
286         check_assist(
287             merge_imports,
288             r"
289 use {
290     foo::baz,
291     foo<|>::bar,
292 };
293 ",
294             r"
295 use {
296     foo::{bar, baz},
297 };
298 ",
299         );
300     }
301
302     #[test]
303     fn test_double_comma() {
304         check_assist(
305             merge_imports,
306             r"
307 use foo::bar::baz;
308 use foo::<|>{
309     FooBar,
310 };
311 ",
312             r"
313 use foo::{
314     FooBar,
315 bar::baz};
316 ",
317         )
318     }
319
320     #[test]
321     fn test_empty_use() {
322         check_assist_not_applicable(
323             merge_imports,
324             r"
325 use std::<|>
326 fn main() {}",
327         );
328     }
329 }