aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorunexge <[email protected]>2020-08-02 20:56:54 +0100
committerunexge <[email protected]>2020-08-02 20:56:54 +0100
commitedd79a6b1c6e019d69d8d1304686391ea9cb4209 (patch)
treeb4c29f01b25034794232a5f18ced823d6fe47529 /crates
parente96bfd812a0f883bc9aa6b5ebe3b0a712c860487 (diff)
Add expand glob import assist
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_assists/src/assist_context.rs4
-rw-r--r--crates/ra_assists/src/handlers/expand_glob_import.rs359
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_syntax/src/ast/make.rs2
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 @@
1use hir::{MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
2use ra_ide_db::{
3 defs::{classify_name_ref, Definition, NameRefClass},
4 RootDatabase,
5};
6use ra_syntax::{ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T};
7
8use 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// ```
38pub(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
61fn 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)]
85enum Def {
86 ModuleDef(ModuleDef),
87 MacroDef(MacroDef),
88}
89
90impl 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
99fn 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
127fn 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
149fn 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)]
180mod 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"
190mod foo {
191 pub struct Bar;
192 pub struct Baz;
193 pub struct Qux;
194
195 pub fn f() {}
196}
197
198use foo::*<|>;
199
200fn qux(bar: Bar, baz: Baz) {
201 f();
202}
203",
204 r"
205mod foo {
206 pub struct Bar;
207 pub struct Baz;
208 pub struct Qux;
209
210 pub fn f() {}
211}
212
213use foo::{Baz, Bar, f};
214
215fn 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"
227mod foo {
228 pub struct Bar;
229 pub struct Baz;
230 pub struct Qux;
231
232 pub fn f() {}
233}
234
235use foo::{*<|>, f};
236
237fn qux(bar: Bar, baz: Baz) {
238 f();
239}
240",
241 r"
242mod foo {
243 pub struct Bar;
244 pub struct Baz;
245 pub struct Qux;
246
247 pub fn f() {}
248}
249
250use foo::{Baz, Bar, f};
251
252fn 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"
264mod 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
278use foo::{bar::{*<|>, f}, baz::*};
279
280fn qux(bar: Bar, baz: Baz) {
281 f();
282 g();
283}
284",
285 r"
286mod 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
300use foo::{bar::{Baz, Bar, f}, baz::*};
301
302fn 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]
317macro_rules! bar {
318 () => ()
319}
320
321pub fn baz() {}
322
323//- /main.rs crate:main deps:foo
324use foo::*<|>;
325
326fn main() {
327 bar!();
328 baz();
329}
330",
331 r"
332use foo::{bar, baz};
333
334fn 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 {
30pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { 30pub 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}
33fn path_from_text(text: &str) -> ast::Path { 33pub fn path_from_text(text: &str) -> ast::Path {
34 ast_from_text(text) 34 ast_from_text(text)
35} 35}
36 36