diff options
author | Jonas Schievink <[email protected]> | 2020-06-13 18:05:31 +0100 |
---|---|---|
committer | Jonas Schievink <[email protected]> | 2020-06-13 18:12:05 +0100 |
commit | 5d66bfe16343141132c8a7aefa727b209ec3e66a (patch) | |
tree | 24cf6c4c08f8bc009edc4e3f0ce665509b4e86c6 /crates/ra_assists/src/handlers | |
parent | b65c0a5893e0ed88119a00062ae26e86375e4883 (diff) |
Shorten *all* qualified paths when adding use
Diffstat (limited to 'crates/ra_assists/src/handlers')
-rw-r--r-- | crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs | 201 |
1 files changed, 191 insertions, 10 deletions
diff --git a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs index 0197a8cf0..6cbf8309b 100644 --- a/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs | |||
@@ -1,7 +1,11 @@ | |||
1 | use hir; | 1 | use hir::{self, ModPath}; |
2 | use ra_syntax::{ast, AstNode, SmolStr, TextRange}; | 2 | use ra_syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SmolStr, SyntaxNode}; |
3 | 3 | ||
4 | use crate::{utils::insert_use_statement, AssistContext, AssistId, Assists}; | 4 | use crate::{ |
5 | utils::{find_insert_use_container, insert_use_statement}, | ||
6 | AssistContext, AssistId, Assists, | ||
7 | }; | ||
8 | use either::Either; | ||
5 | 9 | ||
6 | // Assist: replace_qualified_name_with_use | 10 | // Assist: replace_qualified_name_with_use |
7 | // | 11 | // |
@@ -39,16 +43,28 @@ pub(crate) fn replace_qualified_name_with_use( | |||
39 | target, | 43 | target, |
40 | |builder| { | 44 | |builder| { |
41 | let path_to_import = hir_path.mod_path().clone(); | 45 | let path_to_import = hir_path.mod_path().clone(); |
46 | let container = match find_insert_use_container(path.syntax(), ctx) { | ||
47 | Some(c) => c, | ||
48 | None => return, | ||
49 | }; | ||
42 | insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); | 50 | insert_use_statement(path.syntax(), &path_to_import, ctx, builder.text_edit_builder()); |
43 | 51 | ||
44 | if let Some(last) = path.segment() { | 52 | // Now that we've brought the name into scope, re-qualify all paths that could be |
45 | // Here we are assuming the assist will provide a correct use statement | 53 | // affected (that is, all paths inside the node we added the `use` to). |
46 | // so we can delete the path qualifier | 54 | let hir_path = match hir::Path::from_ast(path.clone()) { |
47 | builder.delete(TextRange::new( | 55 | Some(p) => p, |
48 | path.syntax().text_range().start(), | 56 | None => return, |
49 | last.syntax().text_range().start(), | 57 | }; |
50 | )); | 58 | let mut rewriter = SyntaxRewriter::default(); |
59 | match container { | ||
60 | Either::Left(l) => { | ||
61 | shorten_paths(&mut rewriter, l.syntax().clone(), hir_path.mod_path()); | ||
62 | } | ||
63 | Either::Right(r) => { | ||
64 | shorten_paths(&mut rewriter, r.syntax().clone(), hir_path.mod_path()); | ||
65 | } | ||
51 | } | 66 | } |
67 | builder.rewrite(rewriter); | ||
52 | }, | 68 | }, |
53 | ) | 69 | ) |
54 | } | 70 | } |
@@ -73,6 +89,59 @@ fn collect_hir_path_segments(path: &hir::Path) -> Option<Vec<SmolStr>> { | |||
73 | Some(ps) | 89 | Some(ps) |
74 | } | 90 | } |
75 | 91 | ||
92 | /// Adds replacements to `re` that shorten `path` in all descendants of `node`. | ||
93 | fn shorten_paths(re: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ModPath) { | ||
94 | for child in node.children() { | ||
95 | match_ast! { | ||
96 | match child { | ||
97 | // Don't modify `use` items, as this can break the `use` item when injecting a new | ||
98 | // import into the use tree. | ||
99 | ast::UseItem(_it) => continue, | ||
100 | // Don't descend into submodules, they don't have the same `use` items in scope. | ||
101 | ast::Module(_it) => continue, | ||
102 | |||
103 | ast::Path(p) => { | ||
104 | match maybe_replace_path(re, &p, path) { | ||
105 | Some(()) => {}, | ||
106 | None => shorten_paths(re, p.syntax().clone(), path), | ||
107 | } | ||
108 | }, | ||
109 | _ => shorten_paths(re, child, path), | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | } | ||
114 | |||
115 | fn maybe_replace_path( | ||
116 | re: &mut SyntaxRewriter<'static>, | ||
117 | p: &ast::Path, | ||
118 | path: &ModPath, | ||
119 | ) -> Option<()> { | ||
120 | let hir_path = hir::Path::from_ast(p.clone())?; | ||
121 | |||
122 | if hir_path.mod_path() != path { | ||
123 | return None; | ||
124 | } | ||
125 | |||
126 | // Replace path with its last "plain" segment. | ||
127 | let mut mod_path = hir_path.mod_path().clone(); | ||
128 | let last = mod_path.segments.len() - 1; | ||
129 | mod_path.segments.swap(0, last); | ||
130 | mod_path.segments.truncate(1); | ||
131 | mod_path.kind = hir::PathKind::Plain; | ||
132 | |||
133 | let mut new_path = crate::ast_transform::path_to_ast(mod_path); | ||
134 | |||
135 | let type_args = p.segment().and_then(|s| s.type_arg_list()); | ||
136 | if let Some(type_args) = type_args { | ||
137 | let last_segment = new_path.segment().unwrap(); | ||
138 | new_path = new_path.with_segment(last_segment.with_type_args(type_args)); | ||
139 | } | ||
140 | |||
141 | re.replace(p.syntax(), new_path.syntax()); | ||
142 | Some(()) | ||
143 | } | ||
144 | |||
76 | #[cfg(test)] | 145 | #[cfg(test)] |
77 | mod tests { | 146 | mod tests { |
78 | use crate::tests::{check_assist, check_assist_not_applicable}; | 147 | use crate::tests::{check_assist, check_assist_not_applicable}; |
@@ -463,4 +532,116 @@ fn main() { | |||
463 | ", | 532 | ", |
464 | ); | 533 | ); |
465 | } | 534 | } |
535 | |||
536 | #[test] | ||
537 | fn replaces_all_affected_paths() { | ||
538 | check_assist( | ||
539 | replace_qualified_name_with_use, | ||
540 | " | ||
541 | fn main() { | ||
542 | std::fmt::Debug<|>; | ||
543 | let x: std::fmt::Debug = std::fmt::Debug; | ||
544 | } | ||
545 | ", | ||
546 | " | ||
547 | use std::fmt::Debug; | ||
548 | |||
549 | fn main() { | ||
550 | Debug; | ||
551 | let x: Debug = Debug; | ||
552 | } | ||
553 | ", | ||
554 | ); | ||
555 | } | ||
556 | |||
557 | #[test] | ||
558 | fn replaces_all_affected_paths_mod() { | ||
559 | check_assist( | ||
560 | replace_qualified_name_with_use, | ||
561 | " | ||
562 | mod m { | ||
563 | fn f() { | ||
564 | std::fmt::Debug<|>; | ||
565 | let x: std::fmt::Debug = std::fmt::Debug; | ||
566 | } | ||
567 | fn g() { | ||
568 | std::fmt::Debug; | ||
569 | } | ||
570 | } | ||
571 | |||
572 | fn f() { | ||
573 | std::fmt::Debug; | ||
574 | } | ||
575 | ", | ||
576 | " | ||
577 | mod m { | ||
578 | use std::fmt::Debug; | ||
579 | |||
580 | fn f() { | ||
581 | Debug; | ||
582 | let x: Debug = Debug; | ||
583 | } | ||
584 | fn g() { | ||
585 | Debug; | ||
586 | } | ||
587 | } | ||
588 | |||
589 | fn f() { | ||
590 | std::fmt::Debug; | ||
591 | } | ||
592 | ", | ||
593 | ); | ||
594 | } | ||
595 | |||
596 | #[test] | ||
597 | fn does_not_replace_in_submodules() { | ||
598 | check_assist( | ||
599 | replace_qualified_name_with_use, | ||
600 | " | ||
601 | fn main() { | ||
602 | std::fmt::Debug<|>; | ||
603 | } | ||
604 | |||
605 | mod sub { | ||
606 | fn f() { | ||
607 | std::fmt::Debug; | ||
608 | } | ||
609 | } | ||
610 | ", | ||
611 | " | ||
612 | use std::fmt::Debug; | ||
613 | |||
614 | fn main() { | ||
615 | Debug; | ||
616 | } | ||
617 | |||
618 | mod sub { | ||
619 | fn f() { | ||
620 | std::fmt::Debug; | ||
621 | } | ||
622 | } | ||
623 | ", | ||
624 | ); | ||
625 | } | ||
626 | |||
627 | #[test] | ||
628 | fn does_not_replace_in_use() { | ||
629 | check_assist( | ||
630 | replace_qualified_name_with_use, | ||
631 | " | ||
632 | use std::fmt::Display; | ||
633 | |||
634 | fn main() { | ||
635 | std::fmt<|>; | ||
636 | } | ||
637 | ", | ||
638 | " | ||
639 | use std::fmt::{self, Display}; | ||
640 | |||
641 | fn main() { | ||
642 | fmt; | ||
643 | } | ||
644 | ", | ||
645 | ); | ||
646 | } | ||
466 | } | 647 | } |