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