diff options
-rw-r--r-- | crates/ide/src/hover.rs | 7 | ||||
-rw-r--r-- | crates/ide/src/runnables.rs | 202 |
2 files changed, 136 insertions, 73 deletions
diff --git a/crates/ide/src/hover.rs b/crates/ide/src/hover.rs index 2024acd94..44ebdbd35 100644 --- a/crates/ide/src/hover.rs +++ b/crates/ide/src/hover.rs | |||
@@ -182,12 +182,7 @@ fn runnable_action( | |||
182 | ) -> Option<HoverAction> { | 182 | ) -> Option<HoverAction> { |
183 | match def { | 183 | match def { |
184 | Definition::ModuleDef(it) => match it { | 184 | Definition::ModuleDef(it) => match it { |
185 | ModuleDef::Module(it) => match it.definition_source(sema.db).value { | 185 | ModuleDef::Module(it) => runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)), |
186 | ModuleSource::Module(it) => { | ||
187 | runnable_mod(&sema, it).map(|it| HoverAction::Runnable(it)) | ||
188 | } | ||
189 | _ => None, | ||
190 | }, | ||
191 | ModuleDef::Function(func) => { | 186 | ModuleDef::Function(func) => { |
192 | let src = func.source(sema.db)?; | 187 | let src = func.source(sema.db)?; |
193 | if src.file_id != file_id.into() { | 188 | 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}; | |||
6 | use ide_db::{defs::Definition, RootDatabase}; | 6 | use ide_db::{defs::Definition, RootDatabase}; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use syntax::{ | 8 | use 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 | // |=== |
96 | pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> { | 96 | pub(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)?; | 106 | fn 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 | ||
121 | pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> { | 138 | pub(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 | ||
153 | pub(crate) fn runnable_mod( | 170 | pub(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 |
292 | fn has_test_function_or_multiple_test_submodules(module: &ast::Module) -> bool { | 299 | fn 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 | ||
1000 | macro_rules! gen { | ||
1001 | () => { | ||
1002 | #[test] | ||
1003 | fn foo_test() { | ||
1004 | } | ||
1005 | } | ||
1006 | } | ||
1007 | mod 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 | } |