]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/merge_imports.rs
Merge #11481
[rust.git] / crates / ide_assists / src / handlers / merge_imports.rs
1 use ide_db::helpers::merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior};
2 use syntax::{algo::neighbor, ast, ted, AstNode};
3
4 use crate::{
5     assist_context::{AssistContext, Assists},
6     utils::next_prev,
7     AssistId, AssistKind,
8 };
9
10 // Assist: merge_imports
11 //
12 // Merges two imports with a common prefix.
13 //
14 // ```
15 // use std::$0fmt::Formatter;
16 // use std::io;
17 // ```
18 // ->
19 // ```
20 // use std::{fmt::Formatter, io};
21 // ```
22 pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23     let tree: ast::UseTree = ctx.find_node_at_offset()?;
24
25     let mut imports = None;
26     let mut uses = None;
27     if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
28         let (merged, to_remove) =
29             next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
30                 try_merge_imports(&use_item, &use_item2, MergeBehavior::Crate).zip(Some(use_item2))
31             })?;
32
33         imports = Some((use_item, merged, to_remove));
34     } else {
35         let (merged, to_remove) =
36             next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
37                 try_merge_trees(&tree, &use_tree, MergeBehavior::Crate).zip(Some(use_tree))
38             })?;
39
40         uses = Some((tree.clone(), merged, to_remove))
41     };
42
43     let target = tree.syntax().text_range();
44     acc.add(
45         AssistId("merge_imports", AssistKind::RefactorRewrite),
46         "Merge imports",
47         target,
48         |builder| {
49             if let Some((to_replace, replacement, to_remove)) = imports {
50                 let to_replace = builder.make_mut(to_replace);
51                 let to_remove = builder.make_mut(to_remove);
52
53                 ted::replace(to_replace.syntax(), replacement.syntax());
54                 to_remove.remove();
55             }
56
57             if let Some((to_replace, replacement, to_remove)) = uses {
58                 let to_replace = builder.make_mut(to_replace);
59                 let to_remove = builder.make_mut(to_remove);
60
61                 ted::replace(to_replace.syntax(), replacement.syntax());
62                 to_remove.remove()
63             }
64         },
65     )
66 }
67
68 #[cfg(test)]
69 mod tests {
70     use crate::tests::{check_assist, check_assist_not_applicable};
71
72     use super::*;
73
74     #[test]
75     fn test_merge_equal() {
76         check_assist(
77             merge_imports,
78             r"
79 use std::fmt$0::{Display, Debug};
80 use std::fmt::{Display, Debug};
81 ",
82             r"
83 use std::fmt::{Display, Debug};
84 ",
85         )
86     }
87
88     #[test]
89     fn test_merge_first() {
90         check_assist(
91             merge_imports,
92             r"
93 use std::fmt$0::Debug;
94 use std::fmt::Display;
95 ",
96             r"
97 use std::fmt::{Debug, Display};
98 ",
99         )
100     }
101
102     #[test]
103     fn test_merge_second() {
104         check_assist(
105             merge_imports,
106             r"
107 use std::fmt::Debug;
108 use std::fmt$0::Display;
109 ",
110             r"
111 use std::fmt::{Display, Debug};
112 ",
113         );
114     }
115
116     #[test]
117     fn merge_self1() {
118         check_assist(
119             merge_imports,
120             r"
121 use std::fmt$0;
122 use std::fmt::Display;
123 ",
124             r"
125 use std::fmt::{self, Display};
126 ",
127         );
128     }
129
130     #[test]
131     fn merge_self2() {
132         check_assist(
133             merge_imports,
134             r"
135 use std::{fmt, $0fmt::Display};
136 ",
137             r"
138 use std::{fmt::{Display, self}};
139 ",
140         );
141     }
142
143     #[test]
144     fn skip_pub1() {
145         check_assist_not_applicable(
146             merge_imports,
147             r"
148 pub use std::fmt$0::Debug;
149 use std::fmt::Display;
150 ",
151         );
152     }
153
154     #[test]
155     fn skip_pub_last() {
156         check_assist_not_applicable(
157             merge_imports,
158             r"
159 use std::fmt$0::Debug;
160 pub use std::fmt::Display;
161 ",
162         );
163     }
164
165     #[test]
166     fn skip_pub_crate_pub() {
167         check_assist_not_applicable(
168             merge_imports,
169             r"
170 pub(crate) use std::fmt$0::Debug;
171 pub use std::fmt::Display;
172 ",
173         );
174     }
175
176     #[test]
177     fn skip_pub_pub_crate() {
178         check_assist_not_applicable(
179             merge_imports,
180             r"
181 pub use std::fmt$0::Debug;
182 pub(crate) use std::fmt::Display;
183 ",
184         );
185     }
186
187     #[test]
188     fn merge_pub() {
189         check_assist(
190             merge_imports,
191             r"
192 pub use std::fmt$0::Debug;
193 pub use std::fmt::Display;
194 ",
195             r"
196 pub use std::fmt::{Debug, Display};
197 ",
198         )
199     }
200
201     #[test]
202     fn merge_pub_crate() {
203         check_assist(
204             merge_imports,
205             r"
206 pub(crate) use std::fmt$0::Debug;
207 pub(crate) use std::fmt::Display;
208 ",
209             r"
210 pub(crate) use std::fmt::{Debug, Display};
211 ",
212         )
213     }
214
215     #[test]
216     fn merge_pub_in_path_crate() {
217         check_assist(
218             merge_imports,
219             r"
220 pub(in this::path) use std::fmt$0::Debug;
221 pub(in this::path) use std::fmt::Display;
222 ",
223             r"
224 pub(in this::path) use std::fmt::{Debug, Display};
225 ",
226         )
227     }
228
229     #[test]
230     fn test_merge_nested() {
231         check_assist(
232             merge_imports,
233             r"
234 use std::{fmt$0::Debug, fmt::Display};
235 ",
236             r"
237 use std::{fmt::{Debug, Display}};
238 ",
239         );
240     }
241
242     #[test]
243     fn test_merge_nested2() {
244         check_assist(
245             merge_imports,
246             r"
247 use std::{fmt::Debug, fmt$0::Display};
248 ",
249             r"
250 use std::{fmt::{Display, Debug}};
251 ",
252         );
253     }
254
255     #[test]
256     fn test_merge_with_nested_self_item() {
257         check_assist(
258             merge_imports,
259             r"
260 use std$0::{fmt::{Write, Display}};
261 use std::{fmt::{self, Debug}};
262 ",
263             r"
264 use std::{fmt::{Write, Display, self, Debug}};
265 ",
266         );
267     }
268
269     #[test]
270     fn test_merge_with_nested_self_item2() {
271         check_assist(
272             merge_imports,
273             r"
274 use std$0::{fmt::{self, Debug}};
275 use std::{fmt::{Write, Display}};
276 ",
277             r"
278 use std::{fmt::{self, Debug, Write, Display}};
279 ",
280         );
281     }
282
283     #[test]
284     fn test_merge_self_with_nested_self_item() {
285         check_assist(
286             merge_imports,
287             r"
288 use std::{fmt$0::{self, Debug}, fmt::{Write, Display}};
289 ",
290             r"
291 use std::{fmt::{self, Debug, Write, Display}};
292 ",
293         );
294     }
295
296     #[test]
297     fn test_merge_nested_self_and_empty() {
298         check_assist(
299             merge_imports,
300             r"
301 use foo::$0{bar::{self}};
302 use foo::{bar};
303 ",
304             r"
305 use foo::{bar::{self}};
306 ",
307         )
308     }
309
310     #[test]
311     fn test_merge_nested_empty_and_self() {
312         check_assist(
313             merge_imports,
314             r"
315 use foo::$0{bar};
316 use foo::{bar::{self}};
317 ",
318             r"
319 use foo::{bar::{self}};
320 ",
321         )
322     }
323
324     #[test]
325     fn test_merge_nested_list_self_and_glob() {
326         check_assist(
327             merge_imports,
328             r"
329 use std$0::{fmt::*};
330 use std::{fmt::{self, Display}};
331 ",
332             r"
333 use std::{fmt::{self, *, Display}};
334 ",
335         )
336     }
337
338     #[test]
339     fn test_merge_single_wildcard_diff_prefixes() {
340         check_assist(
341             merge_imports,
342             r"
343 use std$0::cell::*;
344 use std::str;
345 ",
346             r"
347 use std::{cell::*, str};
348 ",
349         )
350     }
351
352     #[test]
353     fn test_merge_both_wildcard_diff_prefixes() {
354         check_assist(
355             merge_imports,
356             r"
357 use std$0::cell::*;
358 use std::str::*;
359 ",
360             r"
361 use std::{cell::*, str::*};
362 ",
363         )
364     }
365
366     #[test]
367     fn removes_just_enough_whitespace() {
368         check_assist(
369             merge_imports,
370             r"
371 use foo$0::bar;
372 use foo::baz;
373
374 /// Doc comment
375 ",
376             r"
377 use foo::{bar, baz};
378
379 /// Doc comment
380 ",
381         );
382     }
383
384     #[test]
385     fn works_with_trailing_comma() {
386         check_assist(
387             merge_imports,
388             r"
389 use {
390     foo$0::bar,
391     foo::baz,
392 };
393 ",
394             r"
395 use {
396     foo::{bar, baz},
397 };
398 ",
399         );
400         check_assist(
401             merge_imports,
402             r"
403 use {
404     foo::baz,
405     foo$0::bar,
406 };
407 ",
408             r"
409 use {
410     foo::{bar, baz},
411 };
412 ",
413         );
414     }
415
416     #[test]
417     fn test_double_comma() {
418         check_assist(
419             merge_imports,
420             r"
421 use foo::bar::baz;
422 use foo::$0{
423     FooBar,
424 };
425 ",
426             r"
427 use foo::{
428     FooBar, bar::baz,
429 };
430 ",
431         )
432     }
433
434     #[test]
435     fn test_empty_use() {
436         check_assist_not_applicable(
437             merge_imports,
438             r"
439 use std::$0
440 fn main() {}",
441         );
442     }
443 }