ide_completion/completions/
mod_.rs

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