]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/unmerge_use.rs
Merge #7941
[rust.git] / crates / ide_assists / src / handlers / unmerge_use.rs
1 use syntax::{
2     algo::SyntaxRewriter,
3     ast::{self, edit::AstNodeEdit, VisibilityOwner},
4     AstNode, SyntaxKind,
5 };
6
7 use crate::{
8     assist_context::{AssistContext, Assists},
9     AssistId, AssistKind,
10 };
11
12 // Assist: unmerge_use
13 //
14 // Extracts single use item from use list.
15 //
16 // ```
17 // use std::fmt::{Debug, Display$0};
18 // ```
19 // ->
20 // ```
21 // use std::fmt::{Debug};
22 // use std::fmt::Display;
23 // ```
24 pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25     let tree: ast::UseTree = ctx.find_node_at_offset()?;
26
27     let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
28     if tree_list.use_trees().count() < 2 {
29         cov_mark::hit!(skip_single_use_item);
30         return None;
31     }
32
33     let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
34     let path = resolve_full_path(&tree)?;
35
36     let target = tree.syntax().text_range();
37     acc.add(
38         AssistId("unmerge_use", AssistKind::RefactorRewrite),
39         "Unmerge use",
40         target,
41         |builder| {
42             let new_use = ast::make::use_(
43                 use_.visibility(),
44                 ast::make::use_tree(
45                     path,
46                     tree.use_tree_list(),
47                     tree.rename(),
48                     tree.star_token().is_some(),
49                 ),
50             );
51
52             let mut rewriter = SyntaxRewriter::default();
53             rewriter += tree.remove();
54             rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
55             if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
56                 rewriter.insert_after(
57                     use_.syntax(),
58                     &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
59                 );
60             }
61             rewriter.insert_after(use_.syntax(), new_use.syntax());
62
63             builder.rewrite(rewriter);
64         },
65     )
66 }
67
68 fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
69     let mut paths = tree
70         .syntax()
71         .ancestors()
72         .take_while(|n| n.kind() != SyntaxKind::USE_KW)
73         .filter_map(ast::UseTree::cast)
74         .filter_map(|t| t.path());
75
76     let mut final_path = paths.next()?;
77     for path in paths {
78         final_path = ast::make::path_concat(path, final_path)
79     }
80     Some(final_path)
81 }
82
83 #[cfg(test)]
84 mod tests {
85     use crate::tests::{check_assist, check_assist_not_applicable};
86
87     use super::*;
88
89     #[test]
90     fn skip_single_use_item() {
91         cov_mark::check!(skip_single_use_item);
92         check_assist_not_applicable(
93             unmerge_use,
94             r"
95 use std::fmt::Debug$0;
96 ",
97         );
98         check_assist_not_applicable(
99             unmerge_use,
100             r"
101 use std::fmt::{Debug$0};
102 ",
103         );
104         check_assist_not_applicable(
105             unmerge_use,
106             r"
107 use std::fmt::Debug as Dbg$0;
108 ",
109         );
110     }
111
112     #[test]
113     fn skip_single_glob_import() {
114         check_assist_not_applicable(
115             unmerge_use,
116             r"
117 use std::fmt::*$0;
118 ",
119         );
120     }
121
122     #[test]
123     fn unmerge_use_item() {
124         check_assist(
125             unmerge_use,
126             r"
127 use std::fmt::{Debug, Display$0};
128 ",
129             r"
130 use std::fmt::{Debug};
131 use std::fmt::Display;
132 ",
133         );
134
135         check_assist(
136             unmerge_use,
137             r"
138 use std::fmt::{Debug, format$0, Display};
139 ",
140             r"
141 use std::fmt::{Debug, Display};
142 use std::fmt::format;
143 ",
144         );
145     }
146
147     #[test]
148     fn unmerge_glob_import() {
149         check_assist(
150             unmerge_use,
151             r"
152 use std::fmt::{*$0, Display};
153 ",
154             r"
155 use std::fmt::{Display};
156 use std::fmt::*;
157 ",
158         );
159     }
160
161     #[test]
162     fn unmerge_renamed_use_item() {
163         check_assist(
164             unmerge_use,
165             r"
166 use std::fmt::{Debug, Display as Disp$0};
167 ",
168             r"
169 use std::fmt::{Debug};
170 use std::fmt::Display as Disp;
171 ",
172         );
173     }
174
175     #[test]
176     fn unmerge_indented_use_item() {
177         check_assist(
178             unmerge_use,
179             r"
180 mod format {
181     use std::fmt::{Debug, Display$0 as Disp, format};
182 }
183 ",
184             r"
185 mod format {
186     use std::fmt::{Debug, format};
187     use std::fmt::Display as Disp;
188 }
189 ",
190         );
191     }
192
193     #[test]
194     fn unmerge_nested_use_item() {
195         check_assist(
196             unmerge_use,
197             r"
198 use foo::bar::{baz::{qux$0, foobar}, barbaz};
199 ",
200             r"
201 use foo::bar::{baz::{foobar}, barbaz};
202 use foo::bar::baz::qux;
203 ",
204         );
205         check_assist(
206             unmerge_use,
207             r"
208 use foo::bar::{baz$0::{qux, foobar}, barbaz};
209 ",
210             r"
211 use foo::bar::{barbaz};
212 use foo::bar::baz::{qux, foobar};
213 ",
214         );
215     }
216
217     #[test]
218     fn unmerge_use_item_with_visibility() {
219         check_assist(
220             unmerge_use,
221             r"
222 pub use std::fmt::{Debug, Display$0};
223 ",
224             r"
225 pub use std::fmt::{Debug};
226 pub use std::fmt::Display;
227 ",
228         );
229     }
230 }