]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/merge_imports.rs
Auto merge of #96869 - sunfishcode:main, r=joshtriplett
[rust.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / merge_imports.rs
1 use either::Either;
2 use ide_db::imports::merge_imports::{try_merge_imports, try_merge_trees, MergeBehavior};
3 use syntax::{algo::neighbor, ast, match_ast, ted, AstNode, SyntaxElement, SyntaxNode};
4
5 use crate::{
6     assist_context::{AssistContext, Assists},
7     utils::next_prev,
8     AssistId, AssistKind,
9 };
10
11 use Edit::*;
12
13 // Assist: merge_imports
14 //
15 // Merges two imports with a common prefix.
16 //
17 // ```
18 // use std::$0fmt::Formatter;
19 // use std::io;
20 // ```
21 // ->
22 // ```
23 // use std::{fmt::Formatter, io};
24 // ```
25 pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
26     let (target, edits) = if ctx.has_empty_selection() {
27         // Merge a neighbor
28         let tree: ast::UseTree = ctx.find_node_at_offset()?;
29         let target = tree.syntax().text_range();
30
31         let edits = if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
32             let mut neighbor = next_prev().find_map(|dir| neighbor(&use_item, dir)).into_iter();
33             use_item.try_merge_from(&mut neighbor)
34         } else {
35             let mut neighbor = next_prev().find_map(|dir| neighbor(&tree, dir)).into_iter();
36             tree.try_merge_from(&mut neighbor)
37         };
38         (target, edits?)
39     } else {
40         // Merge selected
41         let selection_range = ctx.selection_trimmed();
42         let parent_node = match ctx.covering_element() {
43             SyntaxElement::Node(n) => n,
44             SyntaxElement::Token(t) => t.parent()?,
45         };
46         let mut selected_nodes =
47             parent_node.children().filter(|it| selection_range.contains_range(it.text_range()));
48
49         let first_selected = selected_nodes.next()?;
50         let edits = match_ast! {
51             match first_selected {
52                 ast::Use(use_item) => {
53                     use_item.try_merge_from(&mut selected_nodes.filter_map(ast::Use::cast))
54                 },
55                 ast::UseTree(use_tree) => {
56                     use_tree.try_merge_from(&mut selected_nodes.filter_map(ast::UseTree::cast))
57                 },
58                 _ => return None,
59             }
60         };
61         (selection_range, edits?)
62     };
63
64     acc.add(
65         AssistId("merge_imports", AssistKind::RefactorRewrite),
66         "Merge imports",
67         target,
68         |builder| {
69             let edits_mut: Vec<Edit> = edits
70                 .into_iter()
71                 .map(|it| match it {
72                     Remove(Either::Left(it)) => Remove(Either::Left(builder.make_mut(it))),
73                     Remove(Either::Right(it)) => Remove(Either::Right(builder.make_mut(it))),
74                     Replace(old, new) => Replace(builder.make_syntax_mut(old), new),
75                 })
76                 .collect();
77             for edit in edits_mut {
78                 match edit {
79                     Remove(it) => it.as_ref().either(ast::Use::remove, ast::UseTree::remove),
80                     Replace(old, new) => ted::replace(old, new),
81                 }
82             }
83         },
84     )
85 }
86
87 trait Merge: AstNode + Clone {
88     fn try_merge_from(self, items: &mut dyn Iterator<Item = Self>) -> Option<Vec<Edit>> {
89         let mut edits = Vec::new();
90         let mut merged = self.clone();
91         while let Some(item) = items.next() {
92             merged = merged.try_merge(&item)?;
93             edits.push(Edit::Remove(item.into_either()));
94         }
95         if !edits.is_empty() {
96             edits.push(Edit::replace(self, merged));
97             Some(edits)
98         } else {
99             None
100         }
101     }
102     fn try_merge(&self, other: &Self) -> Option<Self>;
103     fn into_either(self) -> Either<ast::Use, ast::UseTree>;
104 }
105
106 impl Merge for ast::Use {
107     fn try_merge(&self, other: &Self) -> Option<Self> {
108         try_merge_imports(self, other, MergeBehavior::Crate)
109     }
110     fn into_either(self) -> Either<ast::Use, ast::UseTree> {
111         Either::Left(self)
112     }
113 }
114
115 impl Merge for ast::UseTree {
116     fn try_merge(&self, other: &Self) -> Option<Self> {
117         try_merge_trees(self, other, MergeBehavior::Crate)
118     }
119     fn into_either(self) -> Either<ast::Use, ast::UseTree> {
120         Either::Right(self)
121     }
122 }
123
124 enum Edit {
125     Remove(Either<ast::Use, ast::UseTree>),
126     Replace(SyntaxNode, SyntaxNode),
127 }
128
129 impl Edit {
130     fn replace(old: impl AstNode, new: impl AstNode) -> Self {
131         Edit::Replace(old.syntax().clone(), new.syntax().clone())
132     }
133 }
134
135 #[cfg(test)]
136 mod tests {
137     use crate::tests::{check_assist, check_assist_not_applicable};
138
139     use super::*;
140
141     #[test]
142     fn test_merge_equal() {
143         check_assist(
144             merge_imports,
145             r"
146 use std::fmt$0::{Display, Debug};
147 use std::fmt::{Display, Debug};
148 ",
149             r"
150 use std::fmt::{Display, Debug};
151 ",
152         )
153     }
154
155     #[test]
156     fn test_merge_first() {
157         check_assist(
158             merge_imports,
159             r"
160 use std::fmt$0::Debug;
161 use std::fmt::Display;
162 ",
163             r"
164 use std::fmt::{Debug, Display};
165 ",
166         )
167     }
168
169     #[test]
170     fn test_merge_second() {
171         check_assist(
172             merge_imports,
173             r"
174 use std::fmt::Debug;
175 use std::fmt$0::Display;
176 ",
177             r"
178 use std::fmt::{Display, Debug};
179 ",
180         );
181     }
182
183     #[test]
184     fn merge_self1() {
185         check_assist(
186             merge_imports,
187             r"
188 use std::fmt$0;
189 use std::fmt::Display;
190 ",
191             r"
192 use std::fmt::{self, Display};
193 ",
194         );
195     }
196
197     #[test]
198     fn merge_self2() {
199         check_assist(
200             merge_imports,
201             r"
202 use std::{fmt, $0fmt::Display};
203 ",
204             r"
205 use std::{fmt::{Display, self}};
206 ",
207         );
208     }
209
210     #[test]
211     fn skip_pub1() {
212         check_assist_not_applicable(
213             merge_imports,
214             r"
215 pub use std::fmt$0::Debug;
216 use std::fmt::Display;
217 ",
218         );
219     }
220
221     #[test]
222     fn skip_pub_last() {
223         check_assist_not_applicable(
224             merge_imports,
225             r"
226 use std::fmt$0::Debug;
227 pub use std::fmt::Display;
228 ",
229         );
230     }
231
232     #[test]
233     fn skip_pub_crate_pub() {
234         check_assist_not_applicable(
235             merge_imports,
236             r"
237 pub(crate) use std::fmt$0::Debug;
238 pub use std::fmt::Display;
239 ",
240         );
241     }
242
243     #[test]
244     fn skip_pub_pub_crate() {
245         check_assist_not_applicable(
246             merge_imports,
247             r"
248 pub use std::fmt$0::Debug;
249 pub(crate) use std::fmt::Display;
250 ",
251         );
252     }
253
254     #[test]
255     fn merge_pub() {
256         check_assist(
257             merge_imports,
258             r"
259 pub use std::fmt$0::Debug;
260 pub use std::fmt::Display;
261 ",
262             r"
263 pub use std::fmt::{Debug, Display};
264 ",
265         )
266     }
267
268     #[test]
269     fn merge_pub_crate() {
270         check_assist(
271             merge_imports,
272             r"
273 pub(crate) use std::fmt$0::Debug;
274 pub(crate) use std::fmt::Display;
275 ",
276             r"
277 pub(crate) use std::fmt::{Debug, Display};
278 ",
279         )
280     }
281
282     #[test]
283     fn merge_pub_in_path_crate() {
284         check_assist(
285             merge_imports,
286             r"
287 pub(in this::path) use std::fmt$0::Debug;
288 pub(in this::path) use std::fmt::Display;
289 ",
290             r"
291 pub(in this::path) use std::fmt::{Debug, Display};
292 ",
293         )
294     }
295
296     #[test]
297     fn test_merge_nested() {
298         check_assist(
299             merge_imports,
300             r"
301 use std::{fmt$0::Debug, fmt::Display};
302 ",
303             r"
304 use std::{fmt::{Debug, Display}};
305 ",
306         );
307     }
308
309     #[test]
310     fn test_merge_nested2() {
311         check_assist(
312             merge_imports,
313             r"
314 use std::{fmt::Debug, fmt$0::Display};
315 ",
316             r"
317 use std::{fmt::{Display, Debug}};
318 ",
319         );
320     }
321
322     #[test]
323     fn test_merge_with_nested_self_item() {
324         check_assist(
325             merge_imports,
326             r"
327 use std$0::{fmt::{Write, Display}};
328 use std::{fmt::{self, Debug}};
329 ",
330             r"
331 use std::{fmt::{Write, Display, self, Debug}};
332 ",
333         );
334     }
335
336     #[test]
337     fn test_merge_with_nested_self_item2() {
338         check_assist(
339             merge_imports,
340             r"
341 use std$0::{fmt::{self, Debug}};
342 use std::{fmt::{Write, Display}};
343 ",
344             r"
345 use std::{fmt::{self, Debug, Write, Display}};
346 ",
347         );
348     }
349
350     #[test]
351     fn test_merge_self_with_nested_self_item() {
352         check_assist(
353             merge_imports,
354             r"
355 use std::{fmt$0::{self, Debug}, fmt::{Write, Display}};
356 ",
357             r"
358 use std::{fmt::{self, Debug, Write, Display}};
359 ",
360         );
361     }
362
363     #[test]
364     fn test_merge_nested_self_and_empty() {
365         check_assist(
366             merge_imports,
367             r"
368 use foo::$0{bar::{self}};
369 use foo::{bar};
370 ",
371             r"
372 use foo::{bar::{self}};
373 ",
374         )
375     }
376
377     #[test]
378     fn test_merge_nested_empty_and_self() {
379         check_assist(
380             merge_imports,
381             r"
382 use foo::$0{bar};
383 use foo::{bar::{self}};
384 ",
385             r"
386 use foo::{bar::{self}};
387 ",
388         )
389     }
390
391     #[test]
392     fn test_merge_nested_list_self_and_glob() {
393         check_assist(
394             merge_imports,
395             r"
396 use std$0::{fmt::*};
397 use std::{fmt::{self, Display}};
398 ",
399             r"
400 use std::{fmt::{*, self, Display}};
401 ",
402         )
403     }
404
405     #[test]
406     fn test_merge_single_wildcard_diff_prefixes() {
407         check_assist(
408             merge_imports,
409             r"
410 use std$0::cell::*;
411 use std::str;
412 ",
413             r"
414 use std::{cell::*, str};
415 ",
416         )
417     }
418
419     #[test]
420     fn test_merge_both_wildcard_diff_prefixes() {
421         check_assist(
422             merge_imports,
423             r"
424 use std$0::cell::*;
425 use std::str::*;
426 ",
427             r"
428 use std::{cell::*, str::*};
429 ",
430         )
431     }
432
433     #[test]
434     fn removes_just_enough_whitespace() {
435         check_assist(
436             merge_imports,
437             r"
438 use foo$0::bar;
439 use foo::baz;
440
441 /// Doc comment
442 ",
443             r"
444 use foo::{bar, baz};
445
446 /// Doc comment
447 ",
448         );
449     }
450
451     #[test]
452     fn works_with_trailing_comma() {
453         check_assist(
454             merge_imports,
455             r"
456 use {
457     foo$0::bar,
458     foo::baz,
459 };
460 ",
461             r"
462 use {
463     foo::{bar, baz},
464 };
465 ",
466         );
467         check_assist(
468             merge_imports,
469             r"
470 use {
471     foo::baz,
472     foo$0::bar,
473 };
474 ",
475             r"
476 use {
477     foo::{bar, baz},
478 };
479 ",
480         );
481     }
482
483     #[test]
484     fn test_double_comma() {
485         check_assist(
486             merge_imports,
487             r"
488 use foo::bar::baz;
489 use foo::$0{
490     FooBar,
491 };
492 ",
493             r"
494 use foo::{
495     FooBar, bar::baz,
496 };
497 ",
498         )
499     }
500
501     #[test]
502     fn test_empty_use() {
503         check_assist_not_applicable(
504             merge_imports,
505             r"
506 use std::$0
507 fn main() {}",
508         );
509     }
510
511     #[test]
512     fn split_glob() {
513         check_assist(
514             merge_imports,
515             r"
516 use foo::$0*;
517 use foo::bar::Baz;
518 ",
519             r"
520 use foo::{*, bar::Baz};
521 ",
522         );
523     }
524
525     #[test]
526     fn merge_selection_uses() {
527         check_assist(
528             merge_imports,
529             r"
530 use std::fmt::Error;
531 $0use std::fmt::Display;
532 use std::fmt::Debug;
533 use std::fmt::Write;
534 $0use std::fmt::Result;
535 ",
536             r"
537 use std::fmt::Error;
538 use std::fmt::{Display, Debug, Write};
539 use std::fmt::Result;
540 ",
541         );
542     }
543
544     #[test]
545     fn merge_selection_use_trees() {
546         check_assist(
547             merge_imports,
548             r"
549 use std::{
550     fmt::Error,
551     $0fmt::Display,
552     fmt::Debug,
553     fmt::Write,$0
554     fmt::Result,
555 };",
556             r"
557 use std::{
558     fmt::Error,
559     fmt::{Display, Debug, Write},
560     fmt::Result,
561 };",
562         );
563         // FIXME: Remove redundant braces. See also unnecessary-braces diagnostic.
564         check_assist(
565             merge_imports,
566             r"use std::$0{fmt::Display, fmt::Debug}$0;",
567             r"use std::{fmt::{Display, Debug}};",
568         );
569     }
570 }