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