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