From 05daa86634b41aa3f311a1ac0b02bf7fed7ed569 Mon Sep 17 00:00:00 2001 From: Jan Jansen Date: Thu, 27 Dec 2018 21:45:16 +0100 Subject: Make modules with tests runnable Fixes #154 --- crates/ra_analysis/src/imp.rs | 22 +++++ crates/ra_analysis/src/lib.rs | 14 ++- crates/ra_analysis/src/runnables.rs | 72 +++++++++++++++ crates/ra_analysis/tests/runnables.rs | 118 +++++++++++++++++++++++++ crates/ra_analysis/tests/tests.rs | 50 +++++++++++ crates/ra_editor/src/lib.rs | 60 +------------ crates/ra_hir/src/module.rs | 5 ++ crates/ra_lsp_server/src/main_loop/handlers.rs | 10 +++ 8 files changed, 288 insertions(+), 63 deletions(-) create mode 100644 crates/ra_analysis/src/runnables.rs create mode 100644 crates/ra_analysis/tests/runnables.rs 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 { }; Ok(query.search(&buf)) } + + pub(crate) fn module_path(&self, position: FilePosition) -> Cancelable> { + let descr = match source_binder::module_from_position(&*self.db, position)? { + None => return Ok(None), + Some(it) => it, + }; + let name = match descr.name() { + None => return Ok(None), + Some(it) => it.to_string(), + }; + + let modules = descr.path_to_root(); + + let path = modules + .iter() + .filter_map(|s| s.name()) + .skip(1) // name is already part of the string. + .fold(name, |path, it| format!("{}::{}", it, path)); + + Ok(Some(path.to_string())) + } + /// This returns `Vec` because a module may be included from several places. We /// don't handle this case yet though, so the Vec has length at most one. pub fn parent_module(&self, position: FilePosition) -> Cancelable> { 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; mod completion; mod symbol_index; pub mod mock_analysis; +mod runnables; mod extend_selection; mod syntax_highlighting; @@ -33,10 +34,12 @@ use crate::{ symbol_index::SymbolIndex, }; -pub use crate::completion::{CompletionItem, CompletionItemKind, InsertText}; +pub use crate::{ + completion::{CompletionItem, CompletionItemKind, InsertText}, + runnables::{Runnable, RunnableKind} +}; pub use ra_editor::{ - FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode, - Severity + FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity }; pub use hir::FnSignatureInfo; @@ -336,6 +339,9 @@ impl Analysis { pub fn parent_module(&self, position: FilePosition) -> Cancelable> { self.imp.parent_module(position) } + pub fn module_path(&self, position: FilePosition) -> Cancelable> { + self.imp.module_path(position) + } pub fn crate_for(&self, file_id: FileId) -> Cancelable> { self.imp.crate_for(file_id) } @@ -344,7 +350,7 @@ impl Analysis { } pub fn runnables(&self, file_id: FileId) -> Cancelable> { let file = self.imp.file_syntax(file_id); - Ok(ra_editor::runnables(&file)) + Ok(runnables::runnables(self, &file, file_id)) } pub fn highlight(&self, file_id: FileId) -> Cancelable> { 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 @@ +use ra_syntax::{ + ast::{self, AstNode, NameOwner, ModuleItemOwner}, + SourceFileNode, TextRange, SyntaxNodeRef, + TextUnit, +}; +use crate::{ + Analysis, FileId, FilePosition +}; + +#[derive(Debug)] +pub struct Runnable { + pub range: TextRange, + pub kind: RunnableKind, +} + +#[derive(Debug)] +pub enum RunnableKind { + Test { name: String }, + TestMod { path: String }, + Bin, +} + +pub fn runnables( + analysis: &Analysis, + file_node: &SourceFileNode, + file_id: FileId, +) -> Vec { + file_node + .syntax() + .descendants() + .filter_map(|i| runnable(analysis, i, file_id)) + .collect() +} + +fn runnable<'a>(analysis: &Analysis, item: SyntaxNodeRef<'a>, file_id: FileId) -> Option { + if let Some(f) = ast::FnDef::cast(item) { + let name = f.name()?.text(); + let kind = if name == "main" { + RunnableKind::Bin + } else if f.has_atom_attr("test") { + RunnableKind::Test { + name: name.to_string(), + } + } else { + return None; + }; + Some(Runnable { + range: f.syntax().range(), + kind, + }) + } else if let Some(m) = ast::Module::cast(item) { + if m.item_list()? + .items() + .map(ast::ModuleItem::syntax) + .filter_map(ast::FnDef::cast) + .any(|f| f.has_atom_attr("test")) + { + let postition = FilePosition { + file_id, + offset: m.syntax().range().start() + TextUnit::from_usize(1), + }; + analysis.module_path(postition).ok()?.map(|path| Runnable { + range: m.syntax().range(), + kind: RunnableKind::TestMod { path }, + }) + } else { + None + } + } else { + None + } +} 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 @@ +extern crate ra_analysis; +extern crate ra_editor; +extern crate ra_syntax; +extern crate relative_path; +extern crate rustc_hash; +extern crate test_utils; + +use test_utils::assert_eq_dbg; + +use ra_analysis::{ + mock_analysis::{analysis_and_position}, +}; + +#[test] +fn test_runnables() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + fn main() {} + + #[test] + fn test_foo() {} + + #[test] + #[ignore] + fn test_foo() {} + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_eq_dbg( + r#"[Runnable { range: [1; 21), kind: Bin }, + Runnable { range: [22; 46), kind: Test { name: "test_foo" } }, + Runnable { range: [47; 81), kind: Test { name: "test_foo" } }]"#, + &runnables, + ) +} + +#[test] +fn test_runnables_module() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + mod test_mod { + #[test] + fn test_foo1() {} + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_eq_dbg( + r#"[Runnable { range: [1; 59), kind: TestMod { path: "test_mod" } }, + Runnable { range: [28; 57), kind: Test { name: "test_foo1" } }]"#, + &runnables, + ) +} + +#[test] +fn test_runnables_one_depth_layer_module() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + mod foo { + mod test_mod { + #[test] + fn test_foo1() {} + } + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_eq_dbg( + r#"[Runnable { range: [23; 85), kind: TestMod { path: "foo::test_mod" } }, + Runnable { range: [46; 79), kind: Test { name: "test_foo1" } }]"#, + &runnables, + ) +} + +#[test] +fn test_runnables_multiple_depth_module() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + mod foo { + mod bar { + mod test_mod { + #[test] + fn test_foo1() {} + } + } + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_eq_dbg( + r#"[Runnable { range: [41; 115), kind: TestMod { path: "foo::bar::test_mod" } }, + Runnable { range: [68; 105), kind: Test { name: "test_foo1" } }]"#, + &runnables, + ) +} + +#[test] +fn test_runnables_no_test_function_in_module() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + mod test_mod { + fn foo1() {} + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_eq_dbg(r#"[]"#, &runnables) +} 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 @@ -131,6 +131,56 @@ fn test_resolve_parent_module_for_inline() { ); } +#[test] +fn test_path_one_layer() { + let (analysis, pos) = analysis_and_position( + " + //- /lib.rs + mod foo; + //- /foo/mod.rs + mod bla; + //- /foo/bla.rs + <|> //empty + ", + ); + let symbols = analysis.module_path(pos).unwrap().unwrap(); + assert_eq!("foo::bla", &symbols); +} + +#[test] +fn test_path_two_layer() { + let (analysis, pos) = analysis_and_position( + " + //- /lib.rs + mod foo; + //- /foo/mod.rs + mod bla; + //- /foo/bla/mod.rs + mod more; + //- /foo/bla/more.rs + <|> //empty + ", + ); + let symbols = analysis.module_path(pos).unwrap().unwrap(); + assert_eq!("foo::bla::more", &symbols); +} + +#[test] +fn test_path_in_file_mod() { + let (analysis, pos) = analysis_and_position( + " + //- /lib.rs + mod foo; + //- /foo.rs + mod bar { + <|> //empty + } + ", + ); + let symbols = analysis.module_path(pos).unwrap().unwrap(); + assert_eq!("foo::bar", &symbols); +} + #[test] fn test_resolve_crate_root() { let mock = MockAnalysis::with_files( 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::{ use ra_text_edit::{TextEdit, TextEditBuilder}; use ra_syntax::{ algo::find_leaf_at_offset, - ast::{self, AstNode, NameOwner}, + ast::{self, AstNode}, SourceFileNode, SyntaxKind::{self, *}, SyntaxNodeRef, TextRange, TextUnit, Direction, @@ -49,18 +49,6 @@ pub struct Diagnostic { pub fix: Option, } -#[derive(Debug)] -pub struct Runnable { - pub range: TextRange, - pub kind: RunnableKind, -} - -#[derive(Debug)] -pub enum RunnableKind { - Test { name: String }, - Bin, -} - pub fn matching_brace(file: &SourceFileNode, offset: TextUnit) -> Option { const BRACES: &[SyntaxKind] = &[ 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 { ::ra_syntax::utils::dump_tree(file.syntax()) } -pub fn runnables(file: &SourceFileNode) -> Vec { - file.syntax() - .descendants() - .filter_map(ast::FnDef::cast) - .filter_map(|f| { - let name = f.name()?.text(); - let kind = if name == "main" { - RunnableKind::Bin - } else if f.has_atom_attr("test") { - RunnableKind::Test { - name: name.to_string(), - } - } else { - return None; - }; - Some(Runnable { - range: f.syntax().range(), - kind, - }) - }) - .collect() -} - pub fn find_node_at_offset<'a, N: AstNode<'a>>( syntax: SyntaxNodeRef<'a>, offset: TextUnit, @@ -190,29 +155,6 @@ fn main() {} ); } - #[test] - fn test_runnables() { - let file = SourceFileNode::parse( - r#" -fn main() {} - -#[test] -fn test_foo() {} - -#[test] -#[ignore] -fn test_foo() {} -"#, - ); - let runnables = runnables(&file); - assert_eq_dbg( - r#"[Runnable { range: [1; 13), kind: Bin }, - Runnable { range: [15; 39), kind: Test { name: "test_foo" } }, - Runnable { range: [41; 75), kind: Test { name: "test_foo" } }]"#, - &runnables, - ) - } - #[test] fn test_matching_brace() { fn do_check(before: &str, after: &str) { 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 { Some(Crate::new(crate_id)) } + /// Returns the all modulkes on the way to the root. + pub fn path_to_root(&self) -> Vec { + generate(Some(self.clone()), move |it| it.parent()).collect::>() + } + /// The root of the tree this module is part of pub fn crate_root(&self) -> Module { 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( range: runnable.range.conv_with(&line_index), label: match &runnable.kind { RunnableKind::Test { name } => format!("test {}", name), + RunnableKind::TestMod { path } => format!("test-mod {}", path), RunnableKind::Bin => "run binary".to_string(), }, bin: "cargo".to_string(), @@ -308,6 +309,15 @@ pub fn handle_runnables( res.push(name.to_string()); res.push("--nocapture".to_string()); } + RunnableKind::TestMod { path } => { + res.push("test".to_string()); + if let Some(spec) = spec { + spec.push_to(&mut res); + } + res.push("--".to_string()); + res.push(path.to_string()); + res.push("--nocapture".to_string()); + } RunnableKind::Bin => { res.push("run".to_string()); if let Some(spec) = spec { -- cgit v1.2.3