diff options
Diffstat (limited to 'crates/ide/src/parent_module.rs')
-rw-r--r-- | crates/ide/src/parent_module.rs | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/crates/ide/src/parent_module.rs b/crates/ide/src/parent_module.rs new file mode 100644 index 000000000..59ed2967c --- /dev/null +++ b/crates/ide/src/parent_module.rs | |||
@@ -0,0 +1,155 @@ | |||
1 | use base_db::{CrateId, FileId, FilePosition}; | ||
2 | use hir::Semantics; | ||
3 | use ide_db::RootDatabase; | ||
4 | use syntax::{ | ||
5 | algo::find_node_at_offset, | ||
6 | ast::{self, AstNode}, | ||
7 | }; | ||
8 | use test_utils::mark; | ||
9 | |||
10 | use crate::NavigationTarget; | ||
11 | |||
12 | // Feature: Parent Module | ||
13 | // | ||
14 | // Navigates to the parent module of the current module. | ||
15 | // | ||
16 | // |=== | ||
17 | // | Editor | Action Name | ||
18 | // | ||
19 | // | VS Code | **Rust Analyzer: Locate parent module** | ||
20 | // |=== | ||
21 | |||
22 | /// This returns `Vec` because a module may be included from several places. We | ||
23 | /// don't handle this case yet though, so the Vec has length at most one. | ||
24 | pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec<NavigationTarget> { | ||
25 | let sema = Semantics::new(db); | ||
26 | let source_file = sema.parse(position.file_id); | ||
27 | |||
28 | let mut module = find_node_at_offset::<ast::Module>(source_file.syntax(), position.offset); | ||
29 | |||
30 | // If cursor is literally on `mod foo`, go to the grandpa. | ||
31 | if let Some(m) = &module { | ||
32 | if !m | ||
33 | .item_list() | ||
34 | .map_or(false, |it| it.syntax().text_range().contains_inclusive(position.offset)) | ||
35 | { | ||
36 | mark::hit!(test_resolve_parent_module_on_module_decl); | ||
37 | module = m.syntax().ancestors().skip(1).find_map(ast::Module::cast); | ||
38 | } | ||
39 | } | ||
40 | |||
41 | let module = match module { | ||
42 | Some(module) => sema.to_def(&module), | ||
43 | None => sema.to_module_def(position.file_id), | ||
44 | }; | ||
45 | let module = match module { | ||
46 | None => return Vec::new(), | ||
47 | Some(it) => it, | ||
48 | }; | ||
49 | let nav = NavigationTarget::from_module_to_decl(db, module); | ||
50 | vec![nav] | ||
51 | } | ||
52 | |||
53 | /// Returns `Vec` for the same reason as `parent_module` | ||
54 | pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec<CrateId> { | ||
55 | let sema = Semantics::new(db); | ||
56 | let module = match sema.to_module_def(file_id) { | ||
57 | Some(it) => it, | ||
58 | None => return Vec::new(), | ||
59 | }; | ||
60 | let krate = module.krate(); | ||
61 | vec![krate.into()] | ||
62 | } | ||
63 | |||
64 | #[cfg(test)] | ||
65 | mod tests { | ||
66 | use base_db::Env; | ||
67 | use cfg::CfgOptions; | ||
68 | use test_utils::mark; | ||
69 | |||
70 | use crate::{ | ||
71 | mock_analysis::{analysis_and_position, MockAnalysis}, | ||
72 | AnalysisChange, CrateGraph, | ||
73 | Edition::Edition2018, | ||
74 | }; | ||
75 | |||
76 | #[test] | ||
77 | fn test_resolve_parent_module() { | ||
78 | let (analysis, pos) = analysis_and_position( | ||
79 | " | ||
80 | //- /lib.rs | ||
81 | mod foo; | ||
82 | //- /foo.rs | ||
83 | <|>// empty | ||
84 | ", | ||
85 | ); | ||
86 | let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); | ||
87 | nav.assert_match("foo MODULE FileId(1) 0..8"); | ||
88 | } | ||
89 | |||
90 | #[test] | ||
91 | fn test_resolve_parent_module_on_module_decl() { | ||
92 | mark::check!(test_resolve_parent_module_on_module_decl); | ||
93 | let (analysis, pos) = analysis_and_position( | ||
94 | " | ||
95 | //- /lib.rs | ||
96 | mod foo; | ||
97 | |||
98 | //- /foo.rs | ||
99 | mod <|>bar; | ||
100 | |||
101 | //- /foo/bar.rs | ||
102 | // empty | ||
103 | ", | ||
104 | ); | ||
105 | let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); | ||
106 | nav.assert_match("foo MODULE FileId(1) 0..8"); | ||
107 | } | ||
108 | |||
109 | #[test] | ||
110 | fn test_resolve_parent_module_for_inline() { | ||
111 | let (analysis, pos) = analysis_and_position( | ||
112 | " | ||
113 | //- /lib.rs | ||
114 | mod foo { | ||
115 | mod bar { | ||
116 | mod baz { <|> } | ||
117 | } | ||
118 | } | ||
119 | ", | ||
120 | ); | ||
121 | let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); | ||
122 | nav.assert_match("baz MODULE FileId(1) 32..44"); | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn test_resolve_crate_root() { | ||
127 | let mock = MockAnalysis::with_files( | ||
128 | r#" | ||
129 | //- /bar.rs | ||
130 | mod foo; | ||
131 | //- /foo.rs | ||
132 | // empty | ||
133 | "#, | ||
134 | ); | ||
135 | let root_file = mock.id_of("/bar.rs"); | ||
136 | let mod_file = mock.id_of("/foo.rs"); | ||
137 | let mut host = mock.analysis_host(); | ||
138 | assert!(host.analysis().crate_for(mod_file).unwrap().is_empty()); | ||
139 | |||
140 | let mut crate_graph = CrateGraph::default(); | ||
141 | let crate_id = crate_graph.add_crate_root( | ||
142 | root_file, | ||
143 | Edition2018, | ||
144 | None, | ||
145 | CfgOptions::default(), | ||
146 | Env::default(), | ||
147 | Default::default(), | ||
148 | ); | ||
149 | let mut change = AnalysisChange::new(); | ||
150 | change.set_crate_graph(crate_graph); | ||
151 | host.apply_change(change); | ||
152 | |||
153 | assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]); | ||
154 | } | ||
155 | } | ||