aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/parent_module.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/parent_module.rs')
-rw-r--r--crates/ide/src/parent_module.rs155
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 @@
1use base_db::{CrateId, FileId, FilePosition};
2use hir::Semantics;
3use ide_db::RootDatabase;
4use syntax::{
5 algo::find_node_at_offset,
6 ast::{self, AstNode},
7};
8use test_utils::mark;
9
10use 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.
24pub(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`
54pub(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)]
65mod 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
130mod 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}