]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide/src/parent_module.rs
Auto merge of #107843 - bjorn3:sync_cg_clif-2023-02-09, r=bjorn3
[rust.git] / src / tools / rust-analyzer / crates / ide / src / parent_module.rs
1 use hir::{db::DefDatabase, Semantics};
2 use ide_db::{
3     base_db::{CrateId, FileId, FileLoader, FilePosition},
4     RootDatabase,
5 };
6 use itertools::Itertools;
7 use syntax::{
8     algo::find_node_at_offset,
9     ast::{self, AstNode},
10 };
11
12 use crate::NavigationTarget;
13
14 // Feature: Parent Module
15 //
16 // Navigates to the parent module of the current module.
17 //
18 // |===
19 // | Editor  | Action Name
20 //
21 // | VS Code | **rust-analyzer: Locate parent module**
22 // |===
23 //
24 // image::https://user-images.githubusercontent.com/48062697/113065580-04c21800-91b1-11eb-9a32-00086161c0bd.gif[]
25
26 /// This returns `Vec` because a module may be included from several places.
27 pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> {
28     let sema = Semantics::new(db);
29     let source_file = sema.parse(position.file_id);
30
31     let mut module = find_node_at_offset::<ast::Module>(source_file.syntax(), position.offset);
32
33     // If cursor is literally on `mod foo`, go to the grandpa.
34     if let Some(m) = &module {
35         if !m
36             .item_list()
37             .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset))
38         {
39             cov_mark::hit!(test_resolve_parent_module_on_module_decl);
40             module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast);
41         }
42     }
43
44     match module {
45         Some(module) => sema
46             .to_def(&module)
47             .into_iter()
48             .map(|module| NavigationTarget::from_module_to_decl(db, module))
49             .collect(),
50         None => sema
51             .to_module_defs(position.file_id)
52             .map(|module| NavigationTarget::from_module_to_decl(db, module))
53             .collect(),
54     }
55 }
56
57 /// Returns `Vec` for the same reason as `parent_module`
58 pub(crate) fn crates_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> {
59     db.relevant_crates(file_id)
60         .iter()
61         .copied()
62         .filter(|&crate_id| db.crate_def_map(crate_id).modules_for_file(file_id).next().is_some())
63         .sorted()
64         .collect()
65 }
66
67 #[cfg(test)]
68 mod tests {
69     use ide_db::base_db::FileRange;
70
71     use crate::fixture;
72
73     fn check(ra_fixture: &str) {
74         let (analysis, position, expected) = fixture::annotations(ra_fixture);
75         let navs = analysis.parent_module(position).unwrap();
76         let navs = navs
77             .iter()
78             .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
79             .collect::<Vec<_>>();
80         assert_eq!(expected.into_iter().map(|(fr, _)| fr).collect::<Vec<_>>(), navs);
81     }
82
83     #[test]
84     fn test_resolve_parent_module() {
85         check(
86             r#"
87 //- /lib.rs
88 mod foo;
89   //^^^
90
91 //- /foo.rs
92 $0// empty
93 "#,
94         );
95     }
96
97     #[test]
98     fn test_resolve_parent_module_on_module_decl() {
99         cov_mark::check!(test_resolve_parent_module_on_module_decl);
100         check(
101             r#"
102 //- /lib.rs
103 mod foo;
104   //^^^
105 //- /foo.rs
106 mod $0bar;
107
108 //- /foo/bar.rs
109 // empty
110 "#,
111         );
112     }
113
114     #[test]
115     fn test_resolve_parent_module_for_inline() {
116         check(
117             r#"
118 //- /lib.rs
119 mod foo {
120     mod bar {
121         mod baz { $0 }
122     }     //^^^
123 }
124 "#,
125         );
126     }
127
128     #[test]
129     fn test_resolve_multi_parent_module() {
130         check(
131             r#"
132 //- /main.rs
133 mod foo;
134   //^^^
135 #[path = "foo.rs"]
136 mod bar;
137   //^^^
138 //- /foo.rs
139 $0
140 "#,
141         );
142     }
143
144     #[test]
145     fn test_resolve_crate_root() {
146         let (analysis, file_id) = fixture::file(
147             r#"
148 //- /foo.rs
149 $0
150 //- /main.rs
151 mod foo;
152 "#,
153         );
154         assert_eq!(analysis.crates_for(file_id).unwrap().len(), 1);
155     }
156
157     #[test]
158     fn test_resolve_multi_parent_crate() {
159         let (analysis, file_id) = fixture::file(
160             r#"
161 //- /baz.rs
162 $0
163 //- /foo.rs crate:foo
164 mod baz;
165 //- /bar.rs crate:bar
166 mod baz;
167 "#,
168         );
169         assert_eq!(analysis.crates_for(file_id).unwrap().len(), 2);
170     }
171 }