diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:19:53 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-08-24 10:20:13 +0100 |
commit | 7bbca7a1b3f9293d2f5cc5745199bc5f8396f2f0 (patch) | |
tree | bdb47765991cb973b2cd5481a088fac636bd326c /crates/assists/src/handlers/expand_glob_import.rs | |
parent | ca464650eeaca6195891199a93f4f76cf3e7e697 (diff) | |
parent | e65d48d1fb3d4d91d9dc1148a7a836ff5c9a3c87 (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Diffstat (limited to 'crates/assists/src/handlers/expand_glob_import.rs')
-rw-r--r-- | crates/assists/src/handlers/expand_glob_import.rs | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs new file mode 100644 index 000000000..81d0af2f3 --- /dev/null +++ b/crates/assists/src/handlers/expand_glob_import.rs | |||
@@ -0,0 +1,385 @@ | |||
1 | use either::Either; | ||
2 | use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; | ||
3 | use ide_db::{ | ||
4 | defs::{classify_name_ref, Definition, NameRefClass}, | ||
5 | RootDatabase, | ||
6 | }; | ||
7 | use syntax::{algo, ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T}; | ||
8 | |||
9 | use crate::{ | ||
10 | assist_context::{AssistBuilder, AssistContext, Assists}, | ||
11 | AssistId, AssistKind, | ||
12 | }; | ||
13 | |||
14 | // Assist: expand_glob_import | ||
15 | // | ||
16 | // Expands glob imports. | ||
17 | // | ||
18 | // ``` | ||
19 | // mod foo { | ||
20 | // pub struct Bar; | ||
21 | // pub struct Baz; | ||
22 | // } | ||
23 | // | ||
24 | // use foo::*<|>; | ||
25 | // | ||
26 | // fn qux(bar: Bar, baz: Baz) {} | ||
27 | // ``` | ||
28 | // -> | ||
29 | // ``` | ||
30 | // mod foo { | ||
31 | // pub struct Bar; | ||
32 | // pub struct Baz; | ||
33 | // } | ||
34 | // | ||
35 | // use foo::{Baz, Bar}; | ||
36 | // | ||
37 | // fn qux(bar: Bar, baz: Baz) {} | ||
38 | // ``` | ||
39 | pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
40 | let star = ctx.find_token_at_offset(T![*])?; | ||
41 | let mod_path = find_mod_path(&star)?; | ||
42 | let module = match ctx.sema.resolve_path(&mod_path)? { | ||
43 | PathResolution::Def(ModuleDef::Module(it)) => it, | ||
44 | _ => return None, | ||
45 | }; | ||
46 | |||
47 | let source_file = ctx.source_file(); | ||
48 | let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); | ||
49 | |||
50 | let defs_in_mod = find_defs_in_mod(ctx, scope, module)?; | ||
51 | let name_refs_in_source_file = | ||
52 | source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect(); | ||
53 | let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file); | ||
54 | |||
55 | let parent = star.parent().parent()?; | ||
56 | acc.add( | ||
57 | AssistId("expand_glob_import", AssistKind::RefactorRewrite), | ||
58 | "Expand glob import", | ||
59 | parent.text_range(), | ||
60 | |builder| { | ||
61 | replace_ast(builder, &parent, mod_path, used_names); | ||
62 | }, | ||
63 | ) | ||
64 | } | ||
65 | |||
66 | fn find_mod_path(star: &SyntaxToken) -> Option<ast::Path> { | ||
67 | star.ancestors().find_map(|n| ast::UseTree::cast(n).and_then(|u| u.path())) | ||
68 | } | ||
69 | |||
70 | #[derive(PartialEq)] | ||
71 | enum Def { | ||
72 | ModuleDef(ModuleDef), | ||
73 | MacroDef(MacroDef), | ||
74 | } | ||
75 | |||
76 | impl Def { | ||
77 | fn name(&self, db: &RootDatabase) -> Option<Name> { | ||
78 | match self { | ||
79 | Def::ModuleDef(def) => def.name(db), | ||
80 | Def::MacroDef(def) => def.name(db), | ||
81 | } | ||
82 | } | ||
83 | } | ||
84 | |||
85 | fn find_defs_in_mod( | ||
86 | ctx: &AssistContext, | ||
87 | from: SemanticsScope<'_>, | ||
88 | module: hir::Module, | ||
89 | ) -> Option<Vec<Def>> { | ||
90 | let module_scope = module.scope(ctx.db(), from.module()); | ||
91 | |||
92 | let mut defs = vec![]; | ||
93 | for (_, def) in module_scope { | ||
94 | match def { | ||
95 | ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)), | ||
96 | ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)), | ||
97 | _ => continue, | ||
98 | } | ||
99 | } | ||
100 | |||
101 | Some(defs) | ||
102 | } | ||
103 | |||
104 | fn find_used_names( | ||
105 | ctx: &AssistContext, | ||
106 | defs_in_mod: Vec<Def>, | ||
107 | name_refs_in_source_file: Vec<ast::NameRef>, | ||
108 | ) -> Vec<Name> { | ||
109 | let defs_in_source_file = name_refs_in_source_file | ||
110 | .iter() | ||
111 | .filter_map(|r| classify_name_ref(&ctx.sema, r)) | ||
112 | .filter_map(|rc| match rc { | ||
113 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), | ||
114 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), | ||
115 | _ => None, | ||
116 | }) | ||
117 | .collect::<Vec<Def>>(); | ||
118 | |||
119 | defs_in_mod | ||
120 | .iter() | ||
121 | .filter(|def| { | ||
122 | if let Def::ModuleDef(ModuleDef::Trait(tr)) = def { | ||
123 | for item in tr.items(ctx.db()) { | ||
124 | if let AssocItem::Function(f) = item { | ||
125 | if defs_in_source_file.contains(&Def::ModuleDef(ModuleDef::Function(f))) { | ||
126 | return true; | ||
127 | } | ||
128 | } | ||
129 | } | ||
130 | } | ||
131 | |||
132 | defs_in_source_file.contains(def) | ||
133 | }) | ||
134 | .filter_map(|d| d.name(ctx.db())) | ||
135 | .collect() | ||
136 | } | ||
137 | |||
138 | fn replace_ast( | ||
139 | builder: &mut AssistBuilder, | ||
140 | node: &SyntaxNode, | ||
141 | path: ast::Path, | ||
142 | used_names: Vec<Name>, | ||
143 | ) { | ||
144 | let replacement: Either<ast::UseTree, ast::UseTreeList> = match used_names.as_slice() { | ||
145 | [name] => Either::Left(ast::make::use_tree( | ||
146 | ast::make::path_from_text(&format!("{}::{}", path, name)), | ||
147 | None, | ||
148 | None, | ||
149 | false, | ||
150 | )), | ||
151 | names => Either::Right(ast::make::use_tree_list(names.iter().map(|n| { | ||
152 | ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false) | ||
153 | }))), | ||
154 | }; | ||
155 | |||
156 | let mut replace_node = |replacement: Either<ast::UseTree, ast::UseTreeList>| { | ||
157 | algo::diff(node, &replacement.either(|u| u.syntax().clone(), |ut| ut.syntax().clone())) | ||
158 | .into_text_edit(builder.text_edit_builder()); | ||
159 | }; | ||
160 | |||
161 | match_ast! { | ||
162 | match node { | ||
163 | ast::UseTree(use_tree) => { | ||
164 | replace_node(replacement); | ||
165 | }, | ||
166 | ast::UseTreeList(use_tree_list) => { | ||
167 | replace_node(replacement); | ||
168 | }, | ||
169 | ast::Use(use_item) => { | ||
170 | builder.replace_ast(use_item, ast::make::use_(replacement.left_or_else(|ut| ast::make::use_tree(path, Some(ut), None, false)))); | ||
171 | }, | ||
172 | _ => {}, | ||
173 | } | ||
174 | } | ||
175 | } | ||
176 | |||
177 | #[cfg(test)] | ||
178 | mod tests { | ||
179 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
180 | |||
181 | use super::*; | ||
182 | |||
183 | #[test] | ||
184 | fn expanding_glob_import() { | ||
185 | check_assist( | ||
186 | expand_glob_import, | ||
187 | r" | ||
188 | mod foo { | ||
189 | pub struct Bar; | ||
190 | pub struct Baz; | ||
191 | pub struct Qux; | ||
192 | |||
193 | pub fn f() {} | ||
194 | } | ||
195 | |||
196 | use foo::*<|>; | ||
197 | |||
198 | fn qux(bar: Bar, baz: Baz) { | ||
199 | f(); | ||
200 | } | ||
201 | ", | ||
202 | r" | ||
203 | mod foo { | ||
204 | pub struct Bar; | ||
205 | pub struct Baz; | ||
206 | pub struct Qux; | ||
207 | |||
208 | pub fn f() {} | ||
209 | } | ||
210 | |||
211 | use foo::{Baz, Bar, f}; | ||
212 | |||
213 | fn qux(bar: Bar, baz: Baz) { | ||
214 | f(); | ||
215 | } | ||
216 | ", | ||
217 | ) | ||
218 | } | ||
219 | |||
220 | #[test] | ||
221 | fn expanding_glob_import_with_existing_explicit_names() { | ||
222 | check_assist( | ||
223 | expand_glob_import, | ||
224 | r" | ||
225 | mod foo { | ||
226 | pub struct Bar; | ||
227 | pub struct Baz; | ||
228 | pub struct Qux; | ||
229 | |||
230 | pub fn f() {} | ||
231 | } | ||
232 | |||
233 | use foo::{*<|>, f}; | ||
234 | |||
235 | fn qux(bar: Bar, baz: Baz) { | ||
236 | f(); | ||
237 | } | ||
238 | ", | ||
239 | r" | ||
240 | mod foo { | ||
241 | pub struct Bar; | ||
242 | pub struct Baz; | ||
243 | pub struct Qux; | ||
244 | |||
245 | pub fn f() {} | ||
246 | } | ||
247 | |||
248 | use foo::{Baz, Bar, f}; | ||
249 | |||
250 | fn qux(bar: Bar, baz: Baz) { | ||
251 | f(); | ||
252 | } | ||
253 | ", | ||
254 | ) | ||
255 | } | ||
256 | |||
257 | #[test] | ||
258 | fn expanding_nested_glob_import() { | ||
259 | check_assist( | ||
260 | expand_glob_import, | ||
261 | r" | ||
262 | mod foo { | ||
263 | mod bar { | ||
264 | pub struct Bar; | ||
265 | pub struct Baz; | ||
266 | pub struct Qux; | ||
267 | |||
268 | pub fn f() {} | ||
269 | } | ||
270 | |||
271 | mod baz { | ||
272 | pub fn g() {} | ||
273 | } | ||
274 | } | ||
275 | |||
276 | use foo::{bar::{*<|>, f}, baz::*}; | ||
277 | |||
278 | fn qux(bar: Bar, baz: Baz) { | ||
279 | f(); | ||
280 | g(); | ||
281 | } | ||
282 | ", | ||
283 | r" | ||
284 | mod foo { | ||
285 | mod bar { | ||
286 | pub struct Bar; | ||
287 | pub struct Baz; | ||
288 | pub struct Qux; | ||
289 | |||
290 | pub fn f() {} | ||
291 | } | ||
292 | |||
293 | mod baz { | ||
294 | pub fn g() {} | ||
295 | } | ||
296 | } | ||
297 | |||
298 | use foo::{bar::{Baz, Bar, f}, baz::*}; | ||
299 | |||
300 | fn qux(bar: Bar, baz: Baz) { | ||
301 | f(); | ||
302 | g(); | ||
303 | } | ||
304 | ", | ||
305 | ) | ||
306 | } | ||
307 | |||
308 | #[test] | ||
309 | fn expanding_glob_import_with_macro_defs() { | ||
310 | check_assist( | ||
311 | expand_glob_import, | ||
312 | r" | ||
313 | //- /lib.rs crate:foo | ||
314 | #[macro_export] | ||
315 | macro_rules! bar { | ||
316 | () => () | ||
317 | } | ||
318 | |||
319 | pub fn baz() {} | ||
320 | |||
321 | //- /main.rs crate:main deps:foo | ||
322 | use foo::*<|>; | ||
323 | |||
324 | fn main() { | ||
325 | bar!(); | ||
326 | baz(); | ||
327 | } | ||
328 | ", | ||
329 | r" | ||
330 | use foo::{bar, baz}; | ||
331 | |||
332 | fn main() { | ||
333 | bar!(); | ||
334 | baz(); | ||
335 | } | ||
336 | ", | ||
337 | ) | ||
338 | } | ||
339 | |||
340 | #[test] | ||
341 | fn expanding_glob_import_with_trait_method_uses() { | ||
342 | check_assist( | ||
343 | expand_glob_import, | ||
344 | r" | ||
345 | //- /lib.rs crate:foo | ||
346 | pub trait Tr { | ||
347 | fn method(&self) {} | ||
348 | } | ||
349 | impl Tr for () {} | ||
350 | |||
351 | //- /main.rs crate:main deps:foo | ||
352 | use foo::*<|>; | ||
353 | |||
354 | fn main() { | ||
355 | ().method(); | ||
356 | } | ||
357 | ", | ||
358 | r" | ||
359 | use foo::Tr; | ||
360 | |||
361 | fn main() { | ||
362 | ().method(); | ||
363 | } | ||
364 | ", | ||
365 | ) | ||
366 | } | ||
367 | |||
368 | #[test] | ||
369 | fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() { | ||
370 | check_assist_not_applicable( | ||
371 | expand_glob_import, | ||
372 | r" | ||
373 | mod foo { | ||
374 | pub struct Bar; | ||
375 | pub struct Baz; | ||
376 | pub struct Qux; | ||
377 | } | ||
378 | |||
379 | use foo::Bar<|>; | ||
380 | |||
381 | fn qux(bar: Bar, baz: Baz) {} | ||
382 | ", | ||
383 | ) | ||
384 | } | ||
385 | } | ||