aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ide/src/hover.rs7
-rw-r--r--crates/ide/src/runnables.rs202
2 files changed, 136 insertions, 73 deletions
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(
183) -> Option<HoverAction> { 183) -> Option<HoverAction> {
184 match def { 184 match def {
185 Definition::ModuleDef(it) => match it { 185 Definition::ModuleDef(it) => match it {
186 ModuleDef::Module(it) => match it.definition_source(sema.db).value { 186 ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)),
187 ModuleSource::Module(it) => {
188 runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it))
189 }
190 _ => None,
191 },
192 ModuleDef::Function(func) => { 187 ModuleDef::Function(func) => {
193 let src = func.source(sema.db)?; 188 let src = func.source(sema.db)?;
194 if src.file_id != file_id.into() { 189 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};
6use ide_db::{defs::Definition, RootDatabase}; 6use ide_db::{defs::Definition, RootDatabase};
7use itertools::Itertools; 7use itertools::Itertools;
8use syntax::{ 8use syntax::{
9 ast::{self, AstNode, AttrsOwner, ModuleItemOwner}, 9 ast::{self, AstNode, AttrsOwner},
10 match_ast, SyntaxNode, 10 match_ast, SyntaxNode,
11}; 11};
12 12
@@ -95,27 +95,44 @@ impl Runnable {
95// |=== 95// |===
96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { 96pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
97 let sema = Semantics::new(db); 97 let sema = Semantics::new(db);
98 let source_file = sema.parse(file_id); 98 let module = match sema.to_module_def(file_id) {
99 source_file 99 None => return vec![],
100 .syntax() 100 Some(it) => it,
101 .descendants() 101 };
102 .filter_map(|item| { 102
103 let runnable = match_ast! { 103 runnables_mod(&sema, module)
104 match item { 104}
105 ast::Fn(func) => { 105
106 let def = sema.to_def(&func)?; 106fn runnables_mod(sema: &Semantics<RootDatabase>, module: hir::Module) -> Vec<Runnable> {
107 runnable_fn(&sema, def) 107 let mut res: Vec<Runnable> = module
108 }, 108 .declarations(sema.db)
109 ast::Module(it) => runnable_mod(&sema, it), 109 .into_iter()
110 _ => None, 110 .filter_map(|def| {
111 } 111 let runnable = match def {
112 }; 112 hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
113 runnable.or_else(|| match doc_owner_to_def(&sema, item)? { 113 hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
114 Definition::ModuleDef(def) => module_def_doctest(&sema, def),
115 _ => None, 114 _ => None,
116 }) 115 };
116 runnable.or_else(|| module_def_doctest(&sema, def))
117 }) 117 })
118 .collect() 118 .collect();
119
120 res.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map(
121 |def| match def {
122 hir::AssocItem::Function(it) => {
123 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into()))
124 }
125 hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
126 hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
127 },
128 ));
129
130 res.extend(module.declarations(sema.db).into_iter().flat_map(|def| match def {
131 hir::ModuleDef::Module(it) => runnables_mod(sema, it),
132 _ => vec![],
133 }));
134
135 res
119} 136}
120 137
121pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { 138pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
@@ -150,26 +167,16 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
150 Some(Runnable { nav, kind, cfg }) 167 Some(Runnable { nav, kind, cfg })
151} 168}
152 169
153pub(crate) fn runnable_mod( 170pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
154 sema: &Semantics<RootDatabase>, 171 if !has_test_function_or_multiple_test_submodules(sema, &def) {
155 module: ast::Module,
156) -> Option<Runnable> {
157 if !has_test_function_or_multiple_test_submodules(&module) {
158 return None; 172 return None;
159 } 173 }
160 let module_def = sema.to_def(&module)?; 174 let path =
161 175 def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
162 let path = module_def
163 .path_to_root(sema.db)
164 .into_iter()
165 .rev()
166 .filter_map(|it| it.name(sema.db))
167 .join("::");
168 176
169 let def = sema.to_def(&module)?;
170 let attrs = def.attrs(sema.db); 177 let attrs = def.attrs(sema.db);
171 let cfg = attrs.cfg(); 178 let cfg = attrs.cfg();
172 let nav = module_def.to_nav(sema.db); 179 let nav = def.to_nav(sema.db);
173 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg }) 180 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
174} 181}
175 182
@@ -289,30 +296,31 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
289 296
290// We could create runnables for modules with number_of_test_submodules > 0, 297// We could create runnables for modules with number_of_test_submodules > 0,
291// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already 298// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
292fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { 299fn has_test_function_or_multiple_test_submodules(
293 if let Some(item_list) = module.item_list() { 300 sema: &Semantics<RootDatabase>,
294 let mut number_of_test_submodules = 0; 301 module: &hir::Module,
295 302) -> bool {
296 for item in item_list.items() { 303 let mut number_of_test_submodules = 0;
297 match item { 304
298 ast::Item::Fn(f) => { 305 for item in module.declarations(sema.db) {
299 if test_related_attribute(&f).is_some() { 306 match item {
307 hir::ModuleDef::Function(f) => {
308 if let Some(it) = f.source(sema.db) {
309 if test_related_attribute(&it.value).is_some() {
300 return true; 310 return true;
301 } 311 }
302 } 312 }
303 ast::Item::Module(submodule) => { 313 }
304 if has_test_function_or_multiple_test_submodules(&submodule) { 314 hir::ModuleDef::Module(submodule) => {
305 number_of_test_submodules += 1; 315 if has_test_function_or_multiple_test_submodules(sema, &submodule) {
306 } 316 number_of_test_submodules += 1;
307 } 317 }
308 _ => (),
309 } 318 }
319 _ => (),
310 } 320 }
311
312 number_of_test_submodules > 1
313 } else {
314 false
315 } 321 }
322
323 number_of_test_submodules > 1
316} 324}
317 325
318#[cfg(test)] 326#[cfg(test)]
@@ -753,6 +761,21 @@ mod root_tests {
753 file_id: FileId( 761 file_id: FileId(
754 0, 762 0,
755 ), 763 ),
764 full_range: 202..286,
765 focus_range: 206..220,
766 name: "nested_tests_2",
767 kind: Module,
768 },
769 kind: TestMod {
770 path: "root_tests::nested_tests_0::nested_tests_2",
771 },
772 cfg: None,
773 },
774 Runnable {
775 nav: NavigationTarget {
776 file_id: FileId(
777 0,
778 ),
756 full_range: 84..126, 779 full_range: 84..126,
757 focus_range: 107..121, 780 focus_range: 107..121,
758 name: "nested_test_11", 781 name: "nested_test_11",
@@ -793,21 +816,6 @@ mod root_tests {
793 file_id: FileId( 816 file_id: FileId(
794 0, 817 0,
795 ), 818 ),
796 full_range: 202..286,
797 focus_range: 206..220,
798 name: "nested_tests_2",
799 kind: Module,
800 },
801 kind: TestMod {
802 path: "root_tests::nested_tests_0::nested_tests_2",
803 },
804 cfg: None,
805 },
806 Runnable {
807 nav: NavigationTarget {
808 file_id: FileId(
809 0,
810 ),
811 full_range: 235..276, 819 full_range: 235..276,
812 focus_range: 258..271, 820 focus_range: 258..271,
813 name: "nested_test_2", 821 name: "nested_test_2",
@@ -982,4 +990,64 @@ impl Foo {
982 "#]], 990 "#]],
983 ); 991 );
984 } 992 }
993
994 #[test]
995 fn test_runnables_in_macro() {
996 check(
997 r#"
998//- /lib.rs
999$0
1000macro_rules! gen {
1001 () => {
1002 #[test]
1003 fn foo_test() {
1004 }
1005 }
1006}
1007mod tests {
1008 gen!();
1009}
1010"#,
1011 &[&TEST, &TEST],
1012 expect![[r#"
1013 [
1014 Runnable {
1015 nav: NavigationTarget {
1016 file_id: FileId(
1017 0,
1018 ),
1019 full_range: 90..115,
1020 focus_range: 94..99,
1021 name: "tests",
1022 kind: Module,
1023 },
1024 kind: TestMod {
1025 path: "tests",
1026 },
1027 cfg: None,
1028 },
1029 Runnable {
1030 nav: NavigationTarget {
1031 file_id: FileId(
1032 0,
1033 ),
1034 full_range: 106..113,
1035 focus_range: 106..113,
1036 name: "foo_test",
1037 kind: Function,
1038 },
1039 kind: Test {
1040 test_id: Path(
1041 "tests::foo_test",
1042 ),
1043 attr: TestAttr {
1044 ignore: false,
1045 },
1046 },
1047 cfg: None,
1048 },
1049 ]
1050 "#]],
1051 );
1052 }
985} 1053}