aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/auto_import.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/auto_import.rs')
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs293
1 files changed, 293 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
new file mode 100644
index 000000000..1fb701da5
--- /dev/null
+++ b/crates/ra_assists/src/handlers/auto_import.rs
@@ -0,0 +1,293 @@
1use ra_ide_db::imports_locator::ImportsLocator;
2use ra_syntax::ast::{self, AstNode};
3
4use crate::{
5 assist_ctx::{Assist, AssistCtx},
6 insert_use_statement, AssistId,
7};
8use std::collections::BTreeSet;
9
10// Assist: auto_import
11//
12// If the name is unresolved, provides all possible imports for it.
13//
14// ```
15// fn main() {
16// let map = HashMap<|>::new();
17// }
18// # pub mod std { pub mod collections { pub struct HashMap { } } }
19// ```
20// ->
21// ```
22// use std::collections::HashMap;
23//
24// fn main() {
25// let map = HashMap::new();
26// }
27// # pub mod std { pub mod collections { pub struct HashMap { } } }
28// ```
29pub(crate) fn auto_import(ctx: AssistCtx) -> Option<Assist> {
30 let path_under_caret: ast::Path = ctx.find_node_at_offset()?;
31 if path_under_caret.syntax().ancestors().find_map(ast::UseItem::cast).is_some() {
32 return None;
33 }
34
35 let module = path_under_caret.syntax().ancestors().find_map(ast::Module::cast);
36 let position = match module.and_then(|it| it.item_list()) {
37 Some(item_list) => item_list.syntax().clone(),
38 None => {
39 let current_file =
40 path_under_caret.syntax().ancestors().find_map(ast::SourceFile::cast)?;
41 current_file.syntax().clone()
42 }
43 };
44 let source_analyzer = ctx.source_analyzer(&position, None);
45 let module_with_name_to_import = source_analyzer.module()?;
46
47 let name_ref_to_import =
48 path_under_caret.syntax().descendants().find_map(ast::NameRef::cast)?;
49 if source_analyzer
50 .resolve_path(ctx.db, &name_ref_to_import.syntax().ancestors().find_map(ast::Path::cast)?)
51 .is_some()
52 {
53 return None;
54 }
55
56 let name_to_import = name_ref_to_import.syntax().to_string();
57 let proposed_imports = ImportsLocator::new(ctx.db)
58 .find_imports(&name_to_import)
59 .into_iter()
60 .filter_map(|module_def| module_with_name_to_import.find_use_path(ctx.db, module_def))
61 .filter(|use_path| !use_path.segments.is_empty())
62 .take(20)
63 .collect::<BTreeSet<_>>();
64
65 if proposed_imports.is_empty() {
66 return None;
67 }
68
69 let mut group = ctx.add_assist_group(format!("Import {}", name_to_import));
70 for import in proposed_imports {
71 group.add_assist(AssistId("auto_import"), format!("Import `{}`", &import), |edit| {
72 edit.target(path_under_caret.syntax().text_range());
73 insert_use_statement(
74 &position,
75 path_under_caret.syntax(),
76 &import,
77 edit.text_edit_builder(),
78 );
79 });
80 }
81 group.finish()
82}
83
84#[cfg(test)]
85mod tests {
86 use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
87
88 use super::*;
89
90 #[test]
91 fn applicable_when_found_an_import() {
92 check_assist(
93 auto_import,
94 r"
95 <|>PubStruct
96
97 pub mod PubMod {
98 pub struct PubStruct;
99 }
100 ",
101 r"
102 <|>use PubMod::PubStruct;
103
104 PubStruct
105
106 pub mod PubMod {
107 pub struct PubStruct;
108 }
109 ",
110 );
111 }
112
113 #[test]
114 fn auto_imports_are_merged() {
115 check_assist(
116 auto_import,
117 r"
118 use PubMod::PubStruct1;
119
120 struct Test {
121 test: Pub<|>Struct2<u8>,
122 }
123
124 pub mod PubMod {
125 pub struct PubStruct1;
126 pub struct PubStruct2<T> {
127 _t: T,
128 }
129 }
130 ",
131 r"
132 use PubMod::{PubStruct2, PubStruct1};
133
134 struct Test {
135 test: Pub<|>Struct2<u8>,
136 }
137
138 pub mod PubMod {
139 pub struct PubStruct1;
140 pub struct PubStruct2<T> {
141 _t: T,
142 }
143 }
144 ",
145 );
146 }
147
148 #[test]
149 fn applicable_when_found_multiple_imports() {
150 check_assist(
151 auto_import,
152 r"
153 PubSt<|>ruct
154
155 pub mod PubMod1 {
156 pub struct PubStruct;
157 }
158 pub mod PubMod2 {
159 pub struct PubStruct;
160 }
161 pub mod PubMod3 {
162 pub struct PubStruct;
163 }
164 ",
165 r"
166 use PubMod1::PubStruct;
167
168 PubSt<|>ruct
169
170 pub mod PubMod1 {
171 pub struct PubStruct;
172 }
173 pub mod PubMod2 {
174 pub struct PubStruct;
175 }
176 pub mod PubMod3 {
177 pub struct PubStruct;
178 }
179 ",
180 );
181 }
182
183 #[test]
184 fn not_applicable_for_already_imported_types() {
185 check_assist_not_applicable(
186 auto_import,
187 r"
188 use PubMod::PubStruct;
189
190 PubStruct<|>
191
192 pub mod PubMod {
193 pub struct PubStruct;
194 }
195 ",
196 );
197 }
198
199 #[test]
200 fn not_applicable_for_types_with_private_paths() {
201 check_assist_not_applicable(
202 auto_import,
203 r"
204 PrivateStruct<|>
205
206 pub mod PubMod {
207 struct PrivateStruct;
208 }
209 ",
210 );
211 }
212
213 #[test]
214 fn not_applicable_when_no_imports_found() {
215 check_assist_not_applicable(
216 auto_import,
217 "
218 PubStruct<|>",
219 );
220 }
221
222 #[test]
223 fn not_applicable_in_import_statements() {
224 check_assist_not_applicable(
225 auto_import,
226 r"
227 use PubStruct<|>;
228
229 pub mod PubMod {
230 pub struct PubStruct;
231 }",
232 );
233 }
234
235 #[test]
236 fn function_import() {
237 check_assist(
238 auto_import,
239 r"
240 test_function<|>
241
242 pub mod PubMod {
243 pub fn test_function() {};
244 }
245 ",
246 r"
247 use PubMod::test_function;
248
249 test_function<|>
250
251 pub mod PubMod {
252 pub fn test_function() {};
253 }
254 ",
255 );
256 }
257
258 #[test]
259 fn auto_import_target() {
260 check_assist_target(
261 auto_import,
262 r"
263 struct AssistInfo {
264 group_label: Option<<|>GroupLabel>,
265 }
266
267 mod m { pub struct GroupLabel; }
268 ",
269 "GroupLabel",
270 )
271 }
272
273 #[test]
274 fn not_applicable_when_path_start_is_imported() {
275 check_assist_not_applicable(
276 auto_import,
277 r"
278 pub mod mod1 {
279 pub mod mod2 {
280 pub mod mod3 {
281 pub struct TestStruct;
282 }
283 }
284 }
285
286 use mod1::mod2;
287 fn main() {
288 mod2::mod3::TestStruct<|>
289 }
290 ",
291 );
292 }
293}