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