rust/crates/ra_ide_api/src/runnables.rs

188 lines
4.6 KiB
Rust
Raw Normal View History

2019-01-08 20:33:36 +01:00
use itertools::Itertools;
use ra_syntax::{
TextRange, SyntaxNode,
ast::{self, AstNode, NameOwner, ModuleItemOwner},
};
2019-01-15 19:02:42 +01:00
use ra_db::SyntaxDatabase;
2019-01-08 20:33:36 +01:00
use crate::{db::RootDatabase, FileId};
#[derive(Debug)]
pub struct Runnable {
pub range: TextRange,
pub kind: RunnableKind,
}
#[derive(Debug)]
pub enum RunnableKind {
Test { name: String },
TestMod { path: String },
2019-01-13 00:40:54 +01:00
Bench { name: String },
2019-01-08 20:33:36 +01:00
Bin,
}
2019-01-15 19:02:42 +01:00
pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
2019-01-08 20:33:36 +01:00
let source_file = db.source_file(file_id);
2019-01-15 19:02:42 +01:00
source_file
2019-01-08 20:33:36 +01:00
.syntax()
.descendants()
.filter_map(|i| runnable(db, file_id, i))
2019-01-15 19:02:42 +01:00
.collect()
2019-01-08 20:33:36 +01:00
}
fn runnable(db: &RootDatabase, file_id: FileId, item: &SyntaxNode) -> Option<Runnable> {
if let Some(fn_def) = ast::FnDef::cast(item) {
runnable_fn(fn_def)
} else if let Some(m) = ast::Module::cast(item) {
runnable_mod(db, file_id, m)
} else {
None
}
}
fn runnable_fn(fn_def: &ast::FnDef) -> Option<Runnable> {
let name = fn_def.name()?.text();
let kind = if name == "main" {
RunnableKind::Bin
} else if fn_def.has_atom_attr("test") {
RunnableKind::Test {
name: name.to_string(),
2019-01-13 00:40:54 +01:00
}
} else if fn_def.has_atom_attr("bench") {
RunnableKind::Bench {
name: name.to_string(),
2019-01-08 20:33:36 +01:00
}
} else {
return None;
};
Some(Runnable {
range: fn_def.syntax().range(),
kind,
})
}
fn runnable_mod(db: &RootDatabase, file_id: FileId, module: &ast::Module) -> Option<Runnable> {
let has_test_function = module
.item_list()?
.items()
.filter_map(|it| match it.kind() {
ast::ModuleItemKind::FnDef(it) => Some(it),
_ => None,
})
.any(|f| f.has_atom_attr("test"));
if !has_test_function {
return None;
}
let range = module.syntax().range();
2019-01-15 16:13:11 +01:00
let module = hir::source_binder::module_from_child_node(db, file_id, module.syntax())?;
2019-01-08 20:33:36 +01:00
// FIXME: thread cancellation instead of `.ok`ing
let path = module
.path_to_root(db)
.into_iter()
.rev()
2019-01-15 16:26:29 +01:00
.filter_map(|it| it.name(db))
2019-01-08 20:33:36 +01:00
.join("::");
Some(Runnable {
range,
kind: RunnableKind::TestMod { path },
})
}
2019-01-14 14:27:08 +01:00
#[cfg(test)]
mod tests {
use insta::assert_debug_snapshot_matches;
use crate::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_debug_snapshot_matches!("runnables", &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_debug_snapshot_matches!("runnables_module", &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_debug_snapshot_matches!("runnables_one_depth_layer_module", &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_debug_snapshot_matches!("runnables_multiple_depth_module", &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!(runnables.is_empty())
}
}