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