]> git.lizzy.rs Git - rust.git/commitdiff
Support runnables in macros
authorEdwin Cheng <edwin0cheng@gmail.com>
Tue, 19 Jan 2021 08:58:11 +0000 (16:58 +0800)
committerEdwin Cheng <edwin0cheng@gmail.com>
Tue, 19 Jan 2021 08:58:11 +0000 (16:58 +0800)
crates/ide/src/hover.rs
crates/ide/src/runnables.rs

index 6022bd275265bd029b3829b68675b03b897a517f..ac2d7727ee117aa49e16ea49c89e057a8f702273 100644 (file)
@@ -183,12 +183,7 @@ fn runnable_action(
 ) -> Option<HoverAction> {
     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() {
index f5ee7de86d33424f0477fcb27c4694e7e3f31f01..8976f1080f58588d880c47ea20ec101f03d04784 100644 (file)
@@ -6,7 +6,7 @@
 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 @@ pub fn action(&self) -> &'static RunnableAction {
 // |===
 pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
     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<RootDatabase>, module: hir::Module) -> Vec<Runnable> {
+    let mut res: Vec<Runnable> = 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<RootDatabase>, def: hir::Function) -> Option<Runnable> {
@@ -150,26 +167,16 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
     Some(Runnable { nav, kind, cfg })
 }
 
-pub(crate) fn runnable_mod(
-    sema: &Semantics<RootDatabase>,
-    module: ast::Module,
-) -> Option<Runnable> {
-    if !has_test_function_or_multiple_test_submodules(&module) {
+pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
+    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<RootDatabase>,
+    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 nested_tests_4 {}
                         },
                         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 nested_tests_4 {}
                         },
                         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 @@ fn 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,
+                    },
+                ]
+            "#]],
+        );
+    }
 }