aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJan Jansen <[email protected]>2018-12-27 20:45:16 +0000
committerJan Jansen <[email protected]>2018-12-31 14:00:04 +0000
commit05daa86634b41aa3f311a1ac0b02bf7fed7ed569 (patch)
treeb703cfe2fc7788631703141d9f4be63b0f5d7fe5
parent690826871202c77326836a895a38532ebd83c54b (diff)
Make modules with tests runnable
Fixes #154
-rw-r--r--crates/ra_analysis/src/imp.rs22
-rw-r--r--crates/ra_analysis/src/lib.rs14
-rw-r--r--crates/ra_analysis/src/runnables.rs72
-rw-r--r--crates/ra_analysis/tests/runnables.rs118
-rw-r--r--crates/ra_analysis/tests/tests.rs50
-rw-r--r--crates/ra_editor/src/lib.rs60
-rw-r--r--crates/ra_hir/src/module.rs5
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs10
8 files changed, 288 insertions, 63 deletions
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs
index 5ed374c79..5669aa94d 100644
--- a/crates/ra_analysis/src/imp.rs
+++ b/crates/ra_analysis/src/imp.rs
@@ -181,6 +181,28 @@ impl AnalysisImpl {
181 }; 181 };
182 Ok(query.search(&buf)) 182 Ok(query.search(&buf))
183 } 183 }
184
185 pub(crate) fn module_path(&self, position: FilePosition) -> Cancelable<Option<String>> {
186 let descr = match source_binder::module_from_position(&*self.db, position)? {
187 None => return Ok(None),
188 Some(it) => it,
189 };
190 let name = match descr.name() {
191 None => return Ok(None),
192 Some(it) => it.to_string(),
193 };
194
195 let modules = descr.path_to_root();
196
197 let path = modules
198 .iter()
199 .filter_map(|s| s.name())
200 .skip(1) // name is already part of the string.
201 .fold(name, |path, it| format!("{}::{}", it, path));
202
203 Ok(Some(path.to_string()))
204 }
205
184 /// This returns `Vec` because a module may be included from several places. We 206 /// This returns `Vec` because a module may be included from several places. We
185 /// don't handle this case yet though, so the Vec has length at most one. 207 /// don't handle this case yet though, so the Vec has length at most one.
186 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> { 208 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs
index e56168510..e6cfaecc3 100644
--- a/crates/ra_analysis/src/lib.rs
+++ b/crates/ra_analysis/src/lib.rs
@@ -15,6 +15,7 @@ mod imp;
15mod completion; 15mod completion;
16mod symbol_index; 16mod symbol_index;
17pub mod mock_analysis; 17pub mod mock_analysis;
18mod runnables;
18 19
19mod extend_selection; 20mod extend_selection;
20mod syntax_highlighting; 21mod syntax_highlighting;
@@ -33,10 +34,12 @@ use crate::{
33 symbol_index::SymbolIndex, 34 symbol_index::SymbolIndex,
34}; 35};
35 36
36pub use crate::completion::{CompletionItem, CompletionItemKind, InsertText}; 37pub use crate::{
38 completion::{CompletionItem, CompletionItemKind, InsertText},
39 runnables::{Runnable, RunnableKind}
40};
37pub use ra_editor::{ 41pub use ra_editor::{
38 FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode, 42 FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity
39 Severity
40}; 43};
41pub use hir::FnSignatureInfo; 44pub use hir::FnSignatureInfo;
42 45
@@ -336,6 +339,9 @@ impl Analysis {
336 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> { 339 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
337 self.imp.parent_module(position) 340 self.imp.parent_module(position)
338 } 341 }
342 pub fn module_path(&self, position: FilePosition) -> Cancelable<Option<String>> {
343 self.imp.module_path(position)
344 }
339 pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { 345 pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
340 self.imp.crate_for(file_id) 346 self.imp.crate_for(file_id)
341 } 347 }
@@ -344,7 +350,7 @@ impl Analysis {
344 } 350 }
345 pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> { 351 pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> {
346 let file = self.imp.file_syntax(file_id); 352 let file = self.imp.file_syntax(file_id);
347 Ok(ra_editor::runnables(&file)) 353 Ok(runnables::runnables(self, &file, file_id))
348 } 354 }
349 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> { 355 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
350 syntax_highlighting::highlight(&*self.imp.db, file_id) 356 syntax_highlighting::highlight(&*self.imp.db, file_id)
diff --git a/crates/ra_analysis/src/runnables.rs b/crates/ra_analysis/src/runnables.rs
new file mode 100644
index 000000000..61ca0930a
--- /dev/null
+++ b/crates/ra_analysis/src/runnables.rs
@@ -0,0 +1,72 @@
1use ra_syntax::{
2 ast::{self, AstNode, NameOwner, ModuleItemOwner},
3 SourceFileNode, TextRange, SyntaxNodeRef,
4 TextUnit,
5};
6use crate::{
7 Analysis, FileId, FilePosition
8};
9
10#[derive(Debug)]
11pub struct Runnable {
12 pub range: TextRange,
13 pub kind: RunnableKind,
14}
15
16#[derive(Debug)]
17pub enum RunnableKind {
18 Test { name: String },
19 TestMod { path: String },
20 Bin,
21}
22
23pub fn runnables(
24 analysis: &Analysis,
25 file_node: &SourceFileNode,
26 file_id: FileId,
27) -> Vec<Runnable> {
28 file_node
29 .syntax()
30 .descendants()
31 .filter_map(|i| runnable(analysis, i, file_id))
32 .collect()
33}
34
35fn runnable<'a>(analysis: &Analysis, item: SyntaxNodeRef<'a>, file_id: FileId) -> Option<Runnable> {
36 if let Some(f) = ast::FnDef::cast(item) {
37 let name = f.name()?.text();
38 let kind = if name == "main" {
39 RunnableKind::Bin
40 } else if f.has_atom_attr("test") {
41 RunnableKind::Test {
42 name: name.to_string(),
43 }
44 } else {
45 return None;
46 };
47 Some(Runnable {
48 range: f.syntax().range(),
49 kind,
50 })
51 } else if let Some(m) = ast::Module::cast(item) {
52 if m.item_list()?
53 .items()
54 .map(ast::ModuleItem::syntax)
55 .filter_map(ast::FnDef::cast)
56 .any(|f| f.has_atom_attr("test"))
57 {
58 let postition = FilePosition {
59 file_id,
60 offset: m.syntax().range().start() + TextUnit::from_usize(1),
61 };
62 analysis.module_path(postition).ok()?.map(|path| Runnable {
63 range: m.syntax().range(),
64 kind: RunnableKind::TestMod { path },
65 })
66 } else {
67 None
68 }
69 } else {
70 None
71 }
72}
diff --git a/crates/ra_analysis/tests/runnables.rs b/crates/ra_analysis/tests/runnables.rs
new file mode 100644
index 000000000..9e5342c46
--- /dev/null
+++ b/crates/ra_analysis/tests/runnables.rs
@@ -0,0 +1,118 @@
1extern crate ra_analysis;
2extern crate ra_editor;
3extern crate ra_syntax;
4extern crate relative_path;
5extern crate rustc_hash;
6extern crate test_utils;
7
8use test_utils::assert_eq_dbg;
9
10use ra_analysis::{
11 mock_analysis::{analysis_and_position},
12};
13
14#[test]
15fn test_runnables() {
16 let (analysis, pos) = analysis_and_position(
17 r#"
18 //- /lib.rs
19 <|> //empty
20 fn main() {}
21
22 #[test]
23 fn test_foo() {}
24
25 #[test]
26 #[ignore]
27 fn test_foo() {}
28 "#,
29 );
30 let runnables = analysis.runnables(pos.file_id).unwrap();
31 assert_eq_dbg(
32 r#"[Runnable { range: [1; 21), kind: Bin },
33 Runnable { range: [22; 46), kind: Test { name: "test_foo" } },
34 Runnable { range: [47; 81), kind: Test { name: "test_foo" } }]"#,
35 &runnables,
36 )
37}
38
39#[test]
40fn test_runnables_module() {
41 let (analysis, pos) = analysis_and_position(
42 r#"
43 //- /lib.rs
44 <|> //empty
45 mod test_mod {
46 #[test]
47 fn test_foo1() {}
48 }
49 "#,
50 );
51 let runnables = analysis.runnables(pos.file_id).unwrap();
52 assert_eq_dbg(
53 r#"[Runnable { range: [1; 59), kind: TestMod { path: "test_mod" } },
54 Runnable { range: [28; 57), kind: Test { name: "test_foo1" } }]"#,
55 &runnables,
56 )
57}
58
59#[test]
60fn test_runnables_one_depth_layer_module() {
61 let (analysis, pos) = analysis_and_position(
62 r#"
63 //- /lib.rs
64 <|> //empty
65 mod foo {
66 mod test_mod {
67 #[test]
68 fn test_foo1() {}
69 }
70 }
71 "#,
72 );
73 let runnables = analysis.runnables(pos.file_id).unwrap();
74 assert_eq_dbg(
75 r#"[Runnable { range: [23; 85), kind: TestMod { path: "foo::test_mod" } },
76 Runnable { range: [46; 79), kind: Test { name: "test_foo1" } }]"#,
77 &runnables,
78 )
79}
80
81#[test]
82fn test_runnables_multiple_depth_module() {
83 let (analysis, pos) = analysis_and_position(
84 r#"
85 //- /lib.rs
86 <|> //empty
87 mod foo {
88 mod bar {
89 mod test_mod {
90 #[test]
91 fn test_foo1() {}
92 }
93 }
94 }
95 "#,
96 );
97 let runnables = analysis.runnables(pos.file_id).unwrap();
98 assert_eq_dbg(
99 r#"[Runnable { range: [41; 115), kind: TestMod { path: "foo::bar::test_mod" } },
100 Runnable { range: [68; 105), kind: Test { name: "test_foo1" } }]"#,
101 &runnables,
102 )
103}
104
105#[test]
106fn test_runnables_no_test_function_in_module() {
107 let (analysis, pos) = analysis_and_position(
108 r#"
109 //- /lib.rs
110 <|> //empty
111 mod test_mod {
112 fn foo1() {}
113 }
114 "#,
115 );
116 let runnables = analysis.runnables(pos.file_id).unwrap();
117 assert_eq_dbg(r#"[]"#, &runnables)
118}
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs
index a314fbc40..b61ead752 100644
--- a/crates/ra_analysis/tests/tests.rs
+++ b/crates/ra_analysis/tests/tests.rs
@@ -132,6 +132,56 @@ fn test_resolve_parent_module_for_inline() {
132} 132}
133 133
134#[test] 134#[test]
135fn test_path_one_layer() {
136 let (analysis, pos) = analysis_and_position(
137 "
138 //- /lib.rs
139 mod foo;
140 //- /foo/mod.rs
141 mod bla;
142 //- /foo/bla.rs
143 <|> //empty
144 ",
145 );
146 let symbols = analysis.module_path(pos).unwrap().unwrap();
147 assert_eq!("foo::bla", &symbols);
148}
149
150#[test]
151fn test_path_two_layer() {
152 let (analysis, pos) = analysis_and_position(
153 "
154 //- /lib.rs
155 mod foo;
156 //- /foo/mod.rs
157 mod bla;
158 //- /foo/bla/mod.rs
159 mod more;
160 //- /foo/bla/more.rs
161 <|> //empty
162 ",
163 );
164 let symbols = analysis.module_path(pos).unwrap().unwrap();
165 assert_eq!("foo::bla::more", &symbols);
166}
167
168#[test]
169fn test_path_in_file_mod() {
170 let (analysis, pos) = analysis_and_position(
171 "
172 //- /lib.rs
173 mod foo;
174 //- /foo.rs
175 mod bar {
176 <|> //empty
177 }
178 ",
179 );
180 let symbols = analysis.module_path(pos).unwrap().unwrap();
181 assert_eq!("foo::bar", &symbols);
182}
183
184#[test]
135fn test_resolve_crate_root() { 185fn test_resolve_crate_root() {
136 let mock = MockAnalysis::with_files( 186 let mock = MockAnalysis::with_files(
137 " 187 "
diff --git a/crates/ra_editor/src/lib.rs b/crates/ra_editor/src/lib.rs
index a65637d52..412b8aea9 100644
--- a/crates/ra_editor/src/lib.rs
+++ b/crates/ra_editor/src/lib.rs
@@ -22,7 +22,7 @@ pub use self::{
22use ra_text_edit::{TextEdit, TextEditBuilder}; 22use ra_text_edit::{TextEdit, TextEditBuilder};
23use ra_syntax::{ 23use ra_syntax::{
24 algo::find_leaf_at_offset, 24 algo::find_leaf_at_offset,
25 ast::{self, AstNode, NameOwner}, 25 ast::{self, AstNode},
26 SourceFileNode, 26 SourceFileNode,
27 SyntaxKind::{self, *}, 27 SyntaxKind::{self, *},
28 SyntaxNodeRef, TextRange, TextUnit, Direction, 28 SyntaxNodeRef, TextRange, TextUnit, Direction,
@@ -49,18 +49,6 @@ pub struct Diagnostic {
49 pub fix: Option<LocalEdit>, 49 pub fix: Option<LocalEdit>,
50} 50}
51 51
52#[derive(Debug)]
53pub struct Runnable {
54 pub range: TextRange,
55 pub kind: RunnableKind,
56}
57
58#[derive(Debug)]
59pub enum RunnableKind {
60 Test { name: String },
61 Bin,
62}
63
64pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> { 52pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option<TextUnit> {
65 const BRACES: &[SyntaxKind] = &[ 53 const BRACES: &[SyntaxKind] = &[
66 L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE, 54 L_CURLY, R_CURLY, L_BRACK, R_BRACK, L_PAREN, R_PAREN, L_ANGLE, R_ANGLE,
@@ -133,29 +121,6 @@ pub fn syntax_tree(file: &SourceFileNode) -> String {
133 ::ra_syntax::utils::dump_tree(file.syntax()) 121 ::ra_syntax::utils::dump_tree(file.syntax())
134} 122}
135 123
136pub fn runnables(file: &SourceFileNode) -> Vec<Runnable> {
137 file.syntax()
138 .descendants()
139 .filter_map(ast::FnDef::cast)
140 .filter_map(|f| {
141 let name = f.name()?.text();
142 let kind = if name == "main" {
143 RunnableKind::Bin
144 } else if f.has_atom_attr("test") {
145 RunnableKind::Test {
146 name: name.to_string(),
147 }
148 } else {
149 return None;
150 };
151 Some(Runnable {
152 range: f.syntax().range(),
153 kind,
154 })
155 })
156 .collect()
157}
158
159pub fn find_node_at_offset<'a, N: AstNode<'a>>( 124pub fn find_node_at_offset<'a, N: AstNode<'a>>(
160 syntax: SyntaxNodeRef<'a>, 125 syntax: SyntaxNodeRef<'a>,
161 offset: TextUnit, 126 offset: TextUnit,
@@ -191,29 +156,6 @@ fn main() {}
191 } 156 }
192 157
193 #[test] 158 #[test]
194 fn test_runnables() {
195 let file = SourceFileNode::parse(
196 r#"
197fn main() {}
198
199#[test]
200fn test_foo() {}
201
202#[test]
203#[ignore]
204fn test_foo() {}
205"#,
206 );
207 let runnables = runnables(&file);
208 assert_eq_dbg(
209 r#"[Runnable { range: [1; 13), kind: Bin },
210 Runnable { range: [15; 39), kind: Test { name: "test_foo" } },
211 Runnable { range: [41; 75), kind: Test { name: "test_foo" } }]"#,
212 &runnables,
213 )
214 }
215
216 #[test]
217 fn test_matching_brace() { 159 fn test_matching_brace() {
218 fn do_check(before: &str, after: &str) { 160 fn do_check(before: &str, after: &str) {
219 let (pos, before) = extract_offset(before); 161 let (pos, before) = extract_offset(before);
diff --git a/crates/ra_hir/src/module.rs b/crates/ra_hir/src/module.rs
index 24c346984..87e30191f 100644
--- a/crates/ra_hir/src/module.rs
+++ b/crates/ra_hir/src/module.rs
@@ -75,6 +75,11 @@ impl Module {
75 Some(Crate::new(crate_id)) 75 Some(Crate::new(crate_id))
76 } 76 }
77 77
78 /// Returns the all modulkes on the way to the root.
79 pub fn path_to_root(&self) -> Vec<Module> {
80 generate(Some(self.clone()), move |it| it.parent()).collect::<Vec<Module>>()
81 }
82
78 /// The root of the tree this module is part of 83 /// The root of the tree this module is part of
79 pub fn crate_root(&self) -> Module { 84 pub fn crate_root(&self) -> Module {
80 let root_id = self.module_id.crate_root(&self.tree); 85 let root_id = self.module_id.crate_root(&self.tree);
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 3b7a14a5c..1d93e8f4d 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -257,6 +257,7 @@ pub fn handle_runnables(
257 range: runnable.range.conv_with(&line_index), 257 range: runnable.range.conv_with(&line_index),
258 label: match &runnable.kind { 258 label: match &runnable.kind {
259 RunnableKind::Test { name } => format!("test {}", name), 259 RunnableKind::Test { name } => format!("test {}", name),
260 RunnableKind::TestMod { path } => format!("test-mod {}", path),
260 RunnableKind::Bin => "run binary".to_string(), 261 RunnableKind::Bin => "run binary".to_string(),
261 }, 262 },
262 bin: "cargo".to_string(), 263 bin: "cargo".to_string(),
@@ -308,6 +309,15 @@ pub fn handle_runnables(
308 res.push(name.to_string()); 309 res.push(name.to_string());
309 res.push("--nocapture".to_string()); 310 res.push("--nocapture".to_string());
310 } 311 }
312 RunnableKind::TestMod { path } => {
313 res.push("test".to_string());
314 if let Some(spec) = spec {
315 spec.push_to(&mut res);
316 }
317 res.push("--".to_string());
318 res.push(path.to_string());
319 res.push("--nocapture".to_string());
320 }
311 RunnableKind::Bin => { 321 RunnableKind::Bin => {
312 res.push("run".to_string()); 322 res.push("run".to_string());
313 if let Some(spec) = spec { 323 if let Some(spec) = spec {