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