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