]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/unmerge_use.rs
Rollup merge of #99460 - JanBeh:PR_asref_asmut_docs, r=joshtriplett
[rust.git] / src / tools / rust-analyzer / crates / ide-assists / src / handlers / unmerge_use.rs
1 use syntax::{
2     ast::{self, edit_in_place::Removable, make, HasVisibility},
3     ted::{self, Position},
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::<ast::UseTree>()?.clone_for_update();
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 old_parent_range = use_.syntax().parent()?.text_range();
37     let new_parent = use_.syntax().parent()?;
38
39     let target = tree.syntax().text_range();
40     acc.add(
41         AssistId("unmerge_use", AssistKind::RefactorRewrite),
42         "Unmerge use",
43         target,
44         |builder| {
45             let new_use = make::use_(
46                 use_.visibility(),
47                 make::use_tree(
48                     path,
49                     tree.use_tree_list(),
50                     tree.rename(),
51                     tree.star_token().is_some(),
52                 ),
53             )
54             .clone_for_update();
55
56             tree.remove();
57             ted::insert(Position::after(use_.syntax()), new_use.syntax());
58
59             builder.replace(old_parent_range, new_parent.to_string());
60         },
61     )
62 }
63
64 fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
65     let paths = tree
66         .syntax()
67         .ancestors()
68         .take_while(|n| n.kind() != SyntaxKind::USE)
69         .filter_map(ast::UseTree::cast)
70         .filter_map(|t| t.path());
71
72     let final_path = paths.reduce(|prev, next| make::path_concat(next, prev))?;
73     if final_path.segment().map_or(false, |it| it.self_token().is_some()) {
74         final_path.qualifier()
75     } else {
76         Some(final_path)
77     }
78 }
79
80 #[cfg(test)]
81 mod tests {
82     use crate::tests::{check_assist, check_assist_not_applicable};
83
84     use super::*;
85
86     #[test]
87     fn skip_single_use_item() {
88         cov_mark::check!(skip_single_use_item);
89         check_assist_not_applicable(
90             unmerge_use,
91             r"
92 use std::fmt::Debug$0;
93 ",
94         );
95         check_assist_not_applicable(
96             unmerge_use,
97             r"
98 use std::fmt::{Debug$0};
99 ",
100         );
101         check_assist_not_applicable(
102             unmerge_use,
103             r"
104 use std::fmt::Debug as Dbg$0;
105 ",
106         );
107     }
108
109     #[test]
110     fn skip_single_glob_import() {
111         check_assist_not_applicable(
112             unmerge_use,
113             r"
114 use std::fmt::*$0;
115 ",
116         );
117     }
118
119     #[test]
120     fn unmerge_use_item() {
121         check_assist(
122             unmerge_use,
123             r"
124 use std::fmt::{Debug, Display$0};
125 ",
126             r"
127 use std::fmt::{Debug};
128 use std::fmt::Display;
129 ",
130         );
131
132         check_assist(
133             unmerge_use,
134             r"
135 use std::fmt::{Debug, format$0, Display};
136 ",
137             r"
138 use std::fmt::{Debug, Display};
139 use std::fmt::format;
140 ",
141         );
142     }
143
144     #[test]
145     fn unmerge_glob_import() {
146         check_assist(
147             unmerge_use,
148             r"
149 use std::fmt::{*$0, Display};
150 ",
151             r"
152 use std::fmt::{Display};
153 use std::fmt::*;
154 ",
155         );
156     }
157
158     #[test]
159     fn unmerge_renamed_use_item() {
160         check_assist(
161             unmerge_use,
162             r"
163 use std::fmt::{Debug, Display as Disp$0};
164 ",
165             r"
166 use std::fmt::{Debug};
167 use std::fmt::Display as Disp;
168 ",
169         );
170     }
171
172     #[test]
173     fn unmerge_indented_use_item() {
174         check_assist(
175             unmerge_use,
176             r"
177 mod format {
178     use std::fmt::{Debug, Display$0 as Disp, format};
179 }
180 ",
181             r"
182 mod format {
183     use std::fmt::{Debug, format};
184     use std::fmt::Display as Disp;
185 }
186 ",
187         );
188     }
189
190     #[test]
191     fn unmerge_nested_use_item() {
192         check_assist(
193             unmerge_use,
194             r"
195 use foo::bar::{baz::{qux$0, foobar}, barbaz};
196 ",
197             r"
198 use foo::bar::{baz::{foobar}, barbaz};
199 use foo::bar::baz::qux;
200 ",
201         );
202         check_assist(
203             unmerge_use,
204             r"
205 use foo::bar::{baz$0::{qux, foobar}, barbaz};
206 ",
207             r"
208 use foo::bar::{barbaz};
209 use foo::bar::baz::{qux, foobar};
210 ",
211         );
212     }
213
214     #[test]
215     fn unmerge_use_item_with_visibility() {
216         check_assist(
217             unmerge_use,
218             r"
219 pub use std::fmt::{Debug, Display$0};
220 ",
221             r"
222 pub use std::fmt::{Debug};
223 pub use std::fmt::Display;
224 ",
225         );
226     }
227
228     #[test]
229     fn unmerge_use_item_on_self() {
230         check_assist(
231             unmerge_use,
232             r"use std::process::{Command, self$0};",
233             r"use std::process::{Command};
234 use std::process;",
235         );
236     }
237 }