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