]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/replace_qualified_name_with_use.rs
Add macro test
[rust.git] / crates / ide_assists / src / handlers / replace_qualified_name_with_use.rs
1 use ide_db::helpers::insert_use::{insert_use, ImportScope};
2 use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode};
3
4 use crate::{AssistContext, AssistId, AssistKind, Assists};
5
6 // Assist: replace_qualified_name_with_use
7 //
8 // Adds a use statement for a given fully-qualified name.
9 //
10 // ```
11 // fn process(map: std::collections::$0HashMap<String, String>) {}
12 // ```
13 // ->
14 // ```
15 // use std::collections::HashMap;
16 //
17 // fn process(map: HashMap<String, String>) {}
18 // ```
19 pub(crate) fn replace_qualified_name_with_use(
20     acc: &mut Assists,
21     ctx: &AssistContext,
22 ) -> Option<()> {
23     let path: ast::Path = ctx.find_node_at_offset()?;
24     // We don't want to mess with use statements
25     if path.syntax().ancestors().find_map(ast::Use::cast).is_some() {
26         return None;
27     }
28     if path.qualifier().is_none() {
29         cov_mark::hit!(dont_import_trivial_paths);
30         return None;
31     }
32
33     let target = path.syntax().text_range();
34     let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
35     let syntax = scope.as_syntax_node();
36     acc.add(
37         AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
38         "Replace qualified path with use",
39         target,
40         |builder| {
41             // Now that we've brought the name into scope, re-qualify all paths that could be
42             // affected (that is, all paths inside the node we added the `use` to).
43             let mut rewriter = SyntaxRewriter::default();
44             shorten_paths(&mut rewriter, syntax.clone(), &path);
45             if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
46                 rewriter += insert_use(import_scope, path, ctx.config.insert_use);
47                 builder.rewrite(rewriter);
48             }
49         },
50     )
51 }
52
53 /// Adds replacements to `re` that shorten `path` in all descendants of `node`.
54 fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) {
55     for child in node.children() {
56         match_ast! {
57             match child {
58                 // Don't modify `use` items, as this can break the `use` item when injecting a new
59                 // import into the use tree.
60                 ast::Use(_it) => continue,
61                 // Don't descend into submodules, they don't have the same `use` items in scope.
62                 ast::Module(_it) => continue,
63
64                 ast::Path(p) => {
65                     match maybe_replace_path(rewriter, p.clone(), path.clone()) {
66                         Some(()) => {},
67                         None => shorten_paths(rewriter, p.syntax().clone(), path),
68                     }
69                 },
70                 _ => shorten_paths(rewriter, child, path),
71             }
72         }
73     }
74 }
75
76 fn maybe_replace_path(
77     rewriter: &mut SyntaxRewriter<'static>,
78     path: ast::Path,
79     target: ast::Path,
80 ) -> Option<()> {
81     if !path_eq(path.clone(), target) {
82         return None;
83     }
84
85     // Shorten `path`, leaving only its last segment.
86     if let Some(parent) = path.qualifier() {
87         rewriter.delete(parent.syntax());
88     }
89     if let Some(double_colon) = path.coloncolon_token() {
90         rewriter.delete(&double_colon);
91     }
92
93     Some(())
94 }
95
96 fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
97     let mut lhs_curr = lhs;
98     let mut rhs_curr = rhs;
99     loop {
100         match (lhs_curr.segment(), rhs_curr.segment()) {
101             (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
102             _ => return false,
103         }
104
105         match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
106             (Some(lhs), Some(rhs)) => {
107                 lhs_curr = lhs;
108                 rhs_curr = rhs;
109             }
110             (None, None) => return true,
111             _ => return false,
112         }
113     }
114 }
115
116 #[cfg(test)]
117 mod tests {
118     use crate::tests::{check_assist, check_assist_not_applicable};
119
120     use super::*;
121
122     #[test]
123     fn test_replace_already_imported() {
124         check_assist(
125             replace_qualified_name_with_use,
126             r"use std::fs;
127
128 fn main() {
129     std::f$0s::Path
130 }",
131             r"use std::fs;
132
133 fn main() {
134     fs::Path
135 }",
136         )
137     }
138
139     #[test]
140     fn test_replace_add_use_no_anchor() {
141         check_assist(
142             replace_qualified_name_with_use,
143             r"
144 std::fmt::Debug$0
145     ",
146             r"
147 use std::fmt::Debug;
148
149 Debug
150     ",
151         );
152     }
153     #[test]
154     fn test_replace_add_use_no_anchor_with_item_below() {
155         check_assist(
156             replace_qualified_name_with_use,
157             r"
158 std::fmt::Debug$0
159
160 fn main() {
161 }
162     ",
163             r"
164 use std::fmt::Debug;
165
166 Debug
167
168 fn main() {
169 }
170     ",
171         );
172     }
173
174     #[test]
175     fn test_replace_add_use_no_anchor_with_item_above() {
176         check_assist(
177             replace_qualified_name_with_use,
178             r"
179 fn main() {
180 }
181
182 std::fmt::Debug$0
183     ",
184             r"
185 use std::fmt::Debug;
186
187 fn main() {
188 }
189
190 Debug
191     ",
192         );
193     }
194
195     #[test]
196     fn test_replace_add_use_no_anchor_2seg() {
197         check_assist(
198             replace_qualified_name_with_use,
199             r"
200 std::fmt$0::Debug
201     ",
202             r"
203 use std::fmt;
204
205 fmt::Debug
206     ",
207         );
208     }
209
210     #[test]
211     fn test_replace_add_use() {
212         check_assist(
213             replace_qualified_name_with_use,
214             r"
215 use stdx;
216
217 impl std::fmt::Debug$0 for Foo {
218 }
219     ",
220             r"
221 use std::fmt::Debug;
222
223 use stdx;
224
225 impl Debug for Foo {
226 }
227     ",
228         );
229     }
230
231     #[test]
232     fn test_replace_file_use_other_anchor() {
233         check_assist(
234             replace_qualified_name_with_use,
235             r"
236 impl std::fmt::Debug$0 for Foo {
237 }
238     ",
239             r"
240 use std::fmt::Debug;
241
242 impl Debug for Foo {
243 }
244     ",
245         );
246     }
247
248     #[test]
249     fn test_replace_add_use_other_anchor_indent() {
250         check_assist(
251             replace_qualified_name_with_use,
252             r"
253     impl std::fmt::Debug$0 for Foo {
254     }
255     ",
256             r"
257     use std::fmt::Debug;
258
259     impl Debug for Foo {
260     }
261     ",
262         );
263     }
264
265     #[test]
266     fn test_replace_split_different() {
267         check_assist(
268             replace_qualified_name_with_use,
269             r"
270 use std::fmt;
271
272 impl std::io$0 for Foo {
273 }
274     ",
275             r"
276 use std::{fmt, io};
277
278 impl io for Foo {
279 }
280     ",
281         );
282     }
283
284     #[test]
285     fn test_replace_split_self_for_use() {
286         check_assist(
287             replace_qualified_name_with_use,
288             r"
289 use std::fmt;
290
291 impl std::fmt::Debug$0 for Foo {
292 }
293     ",
294             r"
295 use std::fmt::{self, Debug};
296
297 impl Debug for Foo {
298 }
299     ",
300         );
301     }
302
303     #[test]
304     fn test_replace_split_self_for_target() {
305         check_assist(
306             replace_qualified_name_with_use,
307             r"
308 use std::fmt::Debug;
309
310 impl std::fmt$0 for Foo {
311 }
312     ",
313             r"
314 use std::fmt::{self, Debug};
315
316 impl fmt for Foo {
317 }
318     ",
319         );
320     }
321
322     #[test]
323     fn test_replace_add_to_nested_self_nested() {
324         check_assist(
325             replace_qualified_name_with_use,
326             r"
327 use std::fmt::{Debug, nested::{Display}};
328
329 impl std::fmt::nested$0 for Foo {
330 }
331 ",
332             r"
333 use std::fmt::{Debug, nested::{self, Display}};
334
335 impl nested for Foo {
336 }
337 ",
338         );
339     }
340
341     #[test]
342     fn test_replace_add_to_nested_self_already_included() {
343         check_assist(
344             replace_qualified_name_with_use,
345             r"
346 use std::fmt::{Debug, nested::{self, Display}};
347
348 impl std::fmt::nested$0 for Foo {
349 }
350 ",
351             r"
352 use std::fmt::{Debug, nested::{self, Display}};
353
354 impl nested for Foo {
355 }
356 ",
357         );
358     }
359
360     #[test]
361     fn test_replace_add_to_nested_nested() {
362         check_assist(
363             replace_qualified_name_with_use,
364             r"
365 use std::fmt::{Debug, nested::{Display}};
366
367 impl std::fmt::nested::Debug$0 for Foo {
368 }
369 ",
370             r"
371 use std::fmt::{Debug, nested::{Debug, Display}};
372
373 impl Debug for Foo {
374 }
375 ",
376         );
377     }
378
379     #[test]
380     fn test_replace_split_common_target_longer() {
381         check_assist(
382             replace_qualified_name_with_use,
383             r"
384 use std::fmt::Debug;
385
386 impl std::fmt::nested::Display$0 for Foo {
387 }
388 ",
389             r"
390 use std::fmt::{Debug, nested::Display};
391
392 impl Display for Foo {
393 }
394 ",
395         );
396     }
397
398     #[test]
399     fn test_replace_split_common_use_longer() {
400         check_assist(
401             replace_qualified_name_with_use,
402             r"
403 use std::fmt::nested::Debug;
404
405 impl std::fmt::Display$0 for Foo {
406 }
407 ",
408             r"
409 use std::fmt::{Display, nested::Debug};
410
411 impl Display for Foo {
412 }
413 ",
414         );
415     }
416
417     #[test]
418     fn test_replace_use_nested_import() {
419         check_assist(
420             replace_qualified_name_with_use,
421             r"
422 use crate::{
423     ty::{Substs, Ty},
424     AssocItem,
425 };
426
427 fn foo() { crate::ty::lower$0::trait_env() }
428 ",
429             r"
430 use crate::{AssocItem, ty::{Substs, Ty, lower}};
431
432 fn foo() { lower::trait_env() }
433 ",
434         );
435     }
436
437     #[test]
438     fn test_replace_alias() {
439         check_assist(
440             replace_qualified_name_with_use,
441             r"
442 use std::fmt as foo;
443
444 impl foo::Debug$0 for Foo {
445 }
446 ",
447             r"
448 use std::fmt as foo;
449
450 use foo::Debug;
451
452 impl Debug for Foo {
453 }
454 ",
455         );
456     }
457
458     #[test]
459     fn dont_import_trivial_paths() {
460         cov_mark::check!(dont_import_trivial_paths);
461         check_assist_not_applicable(
462             replace_qualified_name_with_use,
463             r"
464 impl foo$0 for Foo {
465 }
466 ",
467         );
468     }
469
470     #[test]
471     fn test_replace_not_applicable_in_use() {
472         check_assist_not_applicable(
473             replace_qualified_name_with_use,
474             r"
475 use std::fmt$0;
476 ",
477         );
478     }
479
480     #[test]
481     fn test_replace_add_use_no_anchor_in_mod_mod() {
482         check_assist(
483             replace_qualified_name_with_use,
484             r"
485 mod foo {
486     mod bar {
487         std::fmt::Debug$0
488     }
489 }
490     ",
491             r"
492 mod foo {
493     mod bar {
494         use std::fmt::Debug;
495
496         Debug
497     }
498 }
499     ",
500         );
501     }
502
503     #[test]
504     fn inserts_imports_after_inner_attributes() {
505         check_assist(
506             replace_qualified_name_with_use,
507             r"
508 #![allow(dead_code)]
509
510 fn main() {
511     std::fmt::Debug$0
512 }
513     ",
514             r"
515 #![allow(dead_code)]
516
517 use std::fmt::Debug;
518
519 fn main() {
520     Debug
521 }
522     ",
523         );
524     }
525
526     #[test]
527     fn replaces_all_affected_paths() {
528         check_assist(
529             replace_qualified_name_with_use,
530             r"
531 fn main() {
532     std::fmt::Debug$0;
533     let x: std::fmt::Debug = std::fmt::Debug;
534 }
535     ",
536             r"
537 use std::fmt::Debug;
538
539 fn main() {
540     Debug;
541     let x: Debug = Debug;
542 }
543     ",
544         );
545     }
546
547     #[test]
548     fn replaces_all_affected_paths_mod() {
549         check_assist(
550             replace_qualified_name_with_use,
551             r"
552 mod m {
553     fn f() {
554         std::fmt::Debug$0;
555         let x: std::fmt::Debug = std::fmt::Debug;
556     }
557     fn g() {
558         std::fmt::Debug;
559     }
560 }
561
562 fn f() {
563     std::fmt::Debug;
564 }
565     ",
566             r"
567 mod m {
568     use std::fmt::Debug;
569
570     fn f() {
571         Debug;
572         let x: Debug = Debug;
573     }
574     fn g() {
575         Debug;
576     }
577 }
578
579 fn f() {
580     std::fmt::Debug;
581 }
582     ",
583         );
584     }
585
586     #[test]
587     fn does_not_replace_in_submodules() {
588         check_assist(
589             replace_qualified_name_with_use,
590             r"
591 fn main() {
592     std::fmt::Debug$0;
593 }
594
595 mod sub {
596     fn f() {
597         std::fmt::Debug;
598     }
599 }
600     ",
601             r"
602 use std::fmt::Debug;
603
604 fn main() {
605     Debug;
606 }
607
608 mod sub {
609     fn f() {
610         std::fmt::Debug;
611     }
612 }
613     ",
614         );
615     }
616
617     #[test]
618     fn does_not_replace_in_use() {
619         check_assist(
620             replace_qualified_name_with_use,
621             r"
622 use std::fmt::Display;
623
624 fn main() {
625     std::fmt$0;
626 }
627     ",
628             r"
629 use std::fmt::{self, Display};
630
631 fn main() {
632     fmt;
633 }
634     ",
635         );
636     }
637
638     #[test]
639     fn does_not_replace_pub_use() {
640         check_assist(
641             replace_qualified_name_with_use,
642             r"
643 pub use std::fmt;
644
645 impl std::io$0 for Foo {
646 }
647     ",
648             r"
649 pub use std::fmt;
650 use std::io;
651
652 impl io for Foo {
653 }
654     ",
655         );
656     }
657
658     #[test]
659     fn does_not_replace_pub_crate_use() {
660         check_assist(
661             replace_qualified_name_with_use,
662             r"
663 pub(crate) use std::fmt;
664
665 impl std::io$0 for Foo {
666 }
667     ",
668             r"
669 pub(crate) use std::fmt;
670 use std::io;
671
672 impl io for Foo {
673 }
674     ",
675         );
676     }
677 }