]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-completion/src/completions/mod_.rs
Rollup merge of #99742 - sigaloid:master, r=thomcc
[rust.git] / src / tools / rust-analyzer / crates / ide-completion / src / completions / mod_.rs
1 //! Completes mod declarations.
2
3 use std::iter;
4
5 use hir::{Module, ModuleSource};
6 use ide_db::{
7     base_db::{SourceDatabaseExt, VfsPath},
8     FxHashSet, RootDatabase, SymbolKind,
9 };
10 use syntax::{ast, AstNode, SyntaxKind};
11
12 use crate::{context::CompletionContext, CompletionItem, Completions};
13
14 /// Complete mod declaration, i.e. `mod $0;`
15 pub(crate) fn complete_mod(
16     acc: &mut Completions,
17     ctx: &CompletionContext<'_>,
18     mod_under_caret: &ast::Module,
19 ) -> Option<()> {
20     if mod_under_caret.item_list().is_some() {
21         return None;
22     }
23
24     let _p = profile::span("completion::complete_mod");
25
26     let mut current_module = ctx.module;
27     // For `mod $0`, `ctx.module` is its parent, but for `mod f$0`, it's `mod f` itself, but we're
28     // interested in its parent.
29     if ctx.original_token.kind() == SyntaxKind::IDENT {
30         if let Some(module) =
31             ctx.original_token.parent_ancestors().nth(1).and_then(ast::Module::cast)
32         {
33             match ctx.sema.to_def(&module) {
34                 Some(module) if module == current_module => {
35                     if let Some(parent) = current_module.parent(ctx.db) {
36                         current_module = parent;
37                     }
38                 }
39                 _ => {}
40             }
41         }
42     }
43
44     let module_definition_file =
45         current_module.definition_source(ctx.db).file_id.original_file(ctx.db);
46     let source_root = ctx.db.source_root(ctx.db.file_source_root(module_definition_file));
47     let directory_to_look_for_submodules = directory_to_look_for_submodules(
48         current_module,
49         ctx.db,
50         source_root.path_for_file(&module_definition_file)?,
51     )?;
52
53     let existing_mod_declarations = current_module
54         .children(ctx.db)
55         .filter_map(|module| Some(module.name(ctx.db)?.to_string()))
56         .collect::<FxHashSet<_>>();
57
58     let module_declaration_file =
59         current_module.declaration_source(ctx.db).map(|module_declaration_source_file| {
60             module_declaration_source_file.file_id.original_file(ctx.db)
61         });
62
63     source_root
64         .iter()
65         .filter(|submodule_candidate_file| submodule_candidate_file != &module_definition_file)
66         .filter(|submodule_candidate_file| {
67             Some(submodule_candidate_file) != module_declaration_file.as_ref()
68         })
69         .filter_map(|submodule_file| {
70             let submodule_path = source_root.path_for_file(&submodule_file)?;
71             let directory_with_submodule = submodule_path.parent()?;
72             let (name, ext) = submodule_path.name_and_extension()?;
73             if ext != Some("rs") {
74                 return None;
75             }
76             match name {
77                 "lib" | "main" => None,
78                 "mod" => {
79                     if directory_with_submodule.parent()? == directory_to_look_for_submodules {
80                         match directory_with_submodule.name_and_extension()? {
81                             (directory_name, None) => Some(directory_name.to_owned()),
82                             _ => None,
83                         }
84                     } else {
85                         None
86                     }
87                 }
88                 file_name if directory_with_submodule == directory_to_look_for_submodules => {
89                     Some(file_name.to_owned())
90                 }
91                 _ => None,
92             }
93         })
94         .filter(|name| !existing_mod_declarations.contains(name))
95         .for_each(|submodule_name| {
96             let mut label = submodule_name;
97             if mod_under_caret.semicolon_token().is_none() {
98                 label.push(';');
99             }
100             let item = CompletionItem::new(SymbolKind::Module, ctx.source_range(), &label);
101             item.add_to(acc)
102         });
103
104     Some(())
105 }
106
107 fn directory_to_look_for_submodules(
108     module: Module,
109     db: &RootDatabase,
110     module_file_path: &VfsPath,
111 ) -> Option<VfsPath> {
112     let directory_with_module_path = module_file_path.parent()?;
113     let (name, ext) = module_file_path.name_and_extension()?;
114     if ext != Some("rs") {
115         return None;
116     }
117     let base_directory = match name {
118         "mod" | "lib" | "main" => Some(directory_with_module_path),
119         regular_rust_file_name => {
120             if matches!(
121                 (
122                     directory_with_module_path
123                         .parent()
124                         .as_ref()
125                         .and_then(|path| path.name_and_extension()),
126                     directory_with_module_path.name_and_extension(),
127                 ),
128                 (Some(("src", None)), Some(("bin", None)))
129             ) {
130                 // files in /src/bin/ can import each other directly
131                 Some(directory_with_module_path)
132             } else {
133                 directory_with_module_path.join(regular_rust_file_name)
134             }
135         }
136     }?;
137
138     module_chain_to_containing_module_file(module, db)
139         .into_iter()
140         .filter_map(|module| module.name(db))
141         .try_fold(base_directory, |path, name| path.join(&name.to_smol_str()))
142 }
143
144 fn module_chain_to_containing_module_file(
145     current_module: Module,
146     db: &RootDatabase,
147 ) -> Vec<Module> {
148     let mut path =
149         iter::successors(Some(current_module), |current_module| current_module.parent(db))
150             .take_while(|current_module| {
151                 matches!(current_module.definition_source(db).value, ModuleSource::Module(_))
152             })
153             .collect::<Vec<_>>();
154     path.reverse();
155     path
156 }
157
158 #[cfg(test)]
159 mod tests {
160     use expect_test::{expect, Expect};
161
162     use crate::tests::completion_list;
163
164     fn check(ra_fixture: &str, expect: Expect) {
165         let actual = completion_list(ra_fixture);
166         expect.assert_eq(&actual);
167     }
168
169     #[test]
170     fn lib_module_completion() {
171         check(
172             r#"
173 //- /lib.rs
174 mod $0
175 //- /foo.rs
176 fn foo() {}
177 //- /foo/ignored_foo.rs
178 fn ignored_foo() {}
179 //- /bar/mod.rs
180 fn bar() {}
181 //- /bar/ignored_bar.rs
182 fn ignored_bar() {}
183 "#,
184             expect![[r#"
185                 md bar;
186                 md foo;
187             "#]],
188         );
189     }
190
191     #[test]
192     fn no_module_completion_with_module_body() {
193         check(
194             r#"
195 //- /lib.rs
196 mod $0 {
197
198 }
199 //- /foo.rs
200 fn foo() {}
201 "#,
202             expect![[r#""#]],
203         );
204     }
205
206     #[test]
207     fn main_module_completion() {
208         check(
209             r#"
210 //- /main.rs
211 mod $0
212 //- /foo.rs
213 fn foo() {}
214 //- /foo/ignored_foo.rs
215 fn ignored_foo() {}
216 //- /bar/mod.rs
217 fn bar() {}
218 //- /bar/ignored_bar.rs
219 fn ignored_bar() {}
220 "#,
221             expect![[r#"
222                 md bar;
223                 md foo;
224             "#]],
225         );
226     }
227
228     #[test]
229     fn main_test_module_completion() {
230         check(
231             r#"
232 //- /main.rs
233 mod tests {
234     mod $0;
235 }
236 //- /tests/foo.rs
237 fn foo() {}
238 "#,
239             expect![[r#"
240                 md foo
241             "#]],
242         );
243     }
244
245     #[test]
246     fn directly_nested_module_completion() {
247         check(
248             r#"
249 //- /lib.rs
250 mod foo;
251 //- /foo.rs
252 mod $0;
253 //- /foo/bar.rs
254 fn bar() {}
255 //- /foo/bar/ignored_bar.rs
256 fn ignored_bar() {}
257 //- /foo/baz/mod.rs
258 fn baz() {}
259 //- /foo/moar/ignored_moar.rs
260 fn ignored_moar() {}
261 "#,
262             expect![[r#"
263                 md bar
264                 md baz
265             "#]],
266         );
267     }
268
269     #[test]
270     fn nested_in_source_module_completion() {
271         check(
272             r#"
273 //- /lib.rs
274 mod foo;
275 //- /foo.rs
276 mod bar {
277     mod $0
278 }
279 //- /foo/bar/baz.rs
280 fn baz() {}
281 "#,
282             expect![[r#"
283                 md baz;
284             "#]],
285         );
286     }
287
288     // FIXME binary modules are not supported in tests properly
289     // Binary modules are a bit special, they allow importing the modules from `/src/bin`
290     // and that's why are good to test two things:
291     // * no cycles are allowed in mod declarations
292     // * no modules from the parent directory are proposed
293     // Unfortunately, binary modules support is in cargo not rustc,
294     // hence the test does not work now
295     //
296     // #[test]
297     // fn regular_bin_module_completion() {
298     //     check(
299     //         r#"
300     //         //- /src/bin.rs
301     //         fn main() {}
302     //         //- /src/bin/foo.rs
303     //         mod $0
304     //         //- /src/bin/bar.rs
305     //         fn bar() {}
306     //         //- /src/bin/bar/bar_ignored.rs
307     //         fn bar_ignored() {}
308     //     "#,
309     //         expect![[r#"
310     //             md bar;
311     //         "#]],foo
312     //     );
313     // }
314
315     #[test]
316     fn already_declared_bin_module_completion_omitted() {
317         check(
318             r#"
319 //- /src/bin.rs crate:main
320 fn main() {}
321 //- /src/bin/foo.rs
322 mod $0
323 //- /src/bin/bar.rs
324 mod foo;
325 fn bar() {}
326 //- /src/bin/bar/bar_ignored.rs
327 fn bar_ignored() {}
328 "#,
329             expect![[r#""#]],
330         );
331     }
332
333     #[test]
334     fn name_partially_typed() {
335         check(
336             r#"
337 //- /lib.rs
338 mod f$0
339 //- /foo.rs
340 fn foo() {}
341 //- /foo/ignored_foo.rs
342 fn ignored_foo() {}
343 //- /bar/mod.rs
344 fn bar() {}
345 //- /bar/ignored_bar.rs
346 fn ignored_bar() {}
347 "#,
348             expect![[r#"
349                 md bar;
350                 md foo;
351             "#]],
352         );
353     }
354 }