From 8b03b58ad77f90eb6184fdc5db18555bf69adab8 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Tue, 19 Jan 2021 16:58:11 +0800 Subject: Support runnables in macros --- crates/ide/src/hover.rs | 7 +- crates/ide/src/runnables.rs | 202 +++++++++++++++++++++++++++++--------------- 2 files changed, 136 insertions(+), 73 deletions(-) (limited to 'crates') diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 6022bd275..ac2d7727e 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs @@ -183,12 +183,7 @@ fn runnable_action( ) -> Option { match def { Definition::ModuleDef(it) => match it { - ModuleDef::Module(it) => match it.definition_source(sema.db).value { - ModuleSource::Module(it) => { - runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)) - } - _ => None, - }, + ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)), ModuleDef::Function(func) => { let src = func.source(sema.db)?; if src.file_id != file_id.into() { diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs index f5ee7de86..8976f1080 100644 --- a/crates/ide/src/runnables.rs +++ b/crates/ide/src/runnables.rs @@ -6,7 +6,7 @@ use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; use ide_db::{defs::Definition, RootDatabase}; use itertools::Itertools; use syntax::{ - ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, + ast::{self, AstNode, AttrsOwner}, match_ast, SyntaxNode, }; @@ -95,27 +95,44 @@ impl Runnable { // |=== pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { let sema = Semantics::new(db); - let source_file = sema.parse(file_id); - source_file - .syntax() - .descendants() - .filter_map(|item| { - let runnable = match_ast! { - match item { - ast::Fn(func) => { - let def = sema.to_def(&func)?; - runnable_fn(&sema, def) - }, - ast::Module(it) => runnable_mod(&sema, it), - _ => None, - } - }; - runnable.or_else(|| match doc_owner_to_def(&sema, item)? { - Definition::ModuleDef(def) => module_def_doctest(&sema, def), + let module = match sema.to_module_def(file_id) { + None => return vec![], + Some(it) => it, + }; + + runnables_mod(&sema, module) +} + +fn runnables_mod(sema: &Semantics, module: hir::Module) -> Vec { + let mut res: Vec = module + .declarations(sema.db) + .into_iter() + .filter_map(|def| { + let runnable = match def { + hir::ModuleDef::Module(it) => runnable_mod(&sema, it), + hir::ModuleDef::Function(it) => runnable_fn(&sema, it), _ => None, - }) + }; + runnable.or_else(|| module_def_doctest(&sema, def)) }) - .collect() + .collect(); + + res.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map( + |def| match def { + hir::AssocItem::Function(it) => { + runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into())) + } + hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()), + hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()), + }, + )); + + res.extend(module.declarations(sema.db).into_iter().flat_map(|def| match def { + hir::ModuleDef::Module(it) => runnables_mod(sema, it), + _ => vec![], + })); + + res } pub(crate) fn runnable_fn(sema: &Semantics, def: hir::Function) -> Option { @@ -150,26 +167,16 @@ pub(crate) fn runnable_fn(sema: &Semantics, def: hir::Function) -> Some(Runnable { nav, kind, cfg }) } -pub(crate) fn runnable_mod( - sema: &Semantics, - module: ast::Module, -) -> Option { - if !has_test_function_or_multiple_test_submodules(&module) { +pub(crate) fn runnable_mod(sema: &Semantics, def: hir::Module) -> Option { + if !has_test_function_or_multiple_test_submodules(sema, &def) { return None; } - let module_def = sema.to_def(&module)?; - - let path = module_def - .path_to_root(sema.db) - .into_iter() - .rev() - .filter_map(|it| it.name(sema.db)) - .join("::"); + let path = + def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::"); - let def = sema.to_def(&module)?; let attrs = def.attrs(sema.db); let cfg = attrs.cfg(); - let nav = module_def.to_nav(sema.db); + let nav = def.to_nav(sema.db); Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) } @@ -289,30 +296,31 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool { // We could create runnables for modules with number_of_test_submodules > 0, // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already -fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { - if let Some(item_list) = module.item_list() { - let mut number_of_test_submodules = 0; - - for item in item_list.items() { - match item { - ast::Item::Fn(f) => { - if test_related_attribute(&f).is_some() { +fn has_test_function_or_multiple_test_submodules( + sema: &Semantics, + module: &hir::Module, +) -> bool { + let mut number_of_test_submodules = 0; + + for item in module.declarations(sema.db) { + match item { + hir::ModuleDef::Function(f) => { + if let Some(it) = f.source(sema.db) { + if test_related_attribute(&it.value).is_some() { return true; } } - ast::Item::Module(submodule) => { - if has_test_function_or_multiple_test_submodules(&submodule) { - number_of_test_submodules += 1; - } + } + hir::ModuleDef::Module(submodule) => { + if has_test_function_or_multiple_test_submodules(sema, &submodule) { + number_of_test_submodules += 1; } - _ => (), } + _ => (), } - - number_of_test_submodules > 1 - } else { - false } + + number_of_test_submodules > 1 } #[cfg(test)] @@ -748,6 +756,21 @@ mod root_tests { }, cfg: None, }, + Runnable { + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 202..286, + focus_range: 206..220, + name: "nested_tests_2", + kind: Module, + }, + kind: TestMod { + path: "root_tests::nested_tests_0::nested_tests_2", + }, + cfg: None, + }, Runnable { nav: NavigationTarget { file_id: FileId( @@ -788,21 +811,6 @@ mod root_tests { }, cfg: None, }, - Runnable { - nav: NavigationTarget { - file_id: FileId( - 0, - ), - full_range: 202..286, - focus_range: 206..220, - name: "nested_tests_2", - kind: Module, - }, - kind: TestMod { - path: "root_tests::nested_tests_0::nested_tests_2", - }, - cfg: None, - }, Runnable { nav: NavigationTarget { file_id: FileId( @@ -982,4 +990,64 @@ impl Foo { "#]], ); } + + #[test] + fn test_runnables_in_macro() { + check( + r#" +//- /lib.rs +$0 +macro_rules! gen { + () => { + #[test] + fn foo_test() { + } + } +} +mod tests { + gen!(); +} +"#, + &[&TEST, &TEST], + expect![[r#" + [ + Runnable { + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 90..115, + focus_range: 94..99, + name: "tests", + kind: Module, + }, + kind: TestMod { + path: "tests", + }, + cfg: None, + }, + Runnable { + nav: NavigationTarget { + file_id: FileId( + 0, + ), + full_range: 106..113, + focus_range: 106..113, + name: "foo_test", + kind: Function, + }, + kind: Test { + test_id: Path( + "tests::foo_test", + ), + attr: TestAttr { + ignore: false, + }, + }, + cfg: None, + }, + ] + "#]], + ); + } } -- cgit v1.2.3