]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/runnables.rs
Run cargo fmt
[rust.git] / crates / ide / src / runnables.rs
index 5d0cf7bba4b5c3143d346f461423c5be6e324b8a..bec770ed99f1661b71c8a9ce4f7b24a1dd1218fc 100644 (file)
@@ -2,25 +2,24 @@
 
 use ast::HasName;
 use cfg::CfgExpr;
-use either::Either;
-use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, InFile, Semantics};
+use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
 use ide_assists::utils::test_related_attribute;
 use ide_db::{
     base_db::{FilePosition, FileRange},
+    defs::Definition,
     helpers::visit_file_defs,
     search::SearchScope,
-    RootDatabase, SymbolKind,
+    FxHashMap, FxHashSet, RootDatabase, SymbolKind,
 };
 use itertools::Itertools;
-use rustc_hash::{FxHashMap, FxHashSet};
 use stdx::{always, format_to};
-use syntax::ast::{self, AstNode, HasAttrs as _};
-
-use crate::{
-    display::{ToNav, TryToNav},
-    references, FileId, NavigationTarget,
+use syntax::{
+    ast::{self, AstNode, HasAttrs as _},
+    SmolStr, SyntaxNode,
 };
 
+use crate::{references, FileId, NavigationTarget, ToNav, TryToNav};
+
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Runnable {
     pub use_name_in_title: bool,
@@ -31,15 +30,15 @@ pub struct Runnable {
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub enum TestId {
-    Name(String),
+    Name(SmolStr),
     Path(String),
 }
 
 impl fmt::Display for TestId {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            TestId::Name(name) => write!(f, "{}", name),
-            TestId::Path(path) => write!(f, "{}", path),
+            TestId::Name(name) => name.fmt(f),
+            TestId::Path(path) => path.fmt(f),
         }
     }
 }
@@ -138,8 +137,8 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
         }) {
             if let Some(def) = def {
                 let file_id = match def {
-                    hir::ModuleDef::Module(it) => it.declaration_source(db).map(|src| src.file_id),
-                    hir::ModuleDef::Function(it) => it.source(db).map(|src| src.file_id),
+                    Definition::Module(it) => it.declaration_source(db).map(|src| src.file_id),
+                    Definition::Function(it) => it.source(db).map(|src| src.file_id),
                     _ => None,
                 };
                 if let Some(file_id) = file_id.filter(|file| file.call_node(db).is_some()) {
@@ -150,32 +149,31 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
             res.push(runnable);
         }
     };
-    visit_file_defs(&sema, file_id, &mut |def| match def {
-        Either::Left(def) => {
-            let runnable = match def {
-                hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
-                hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
-                _ => None,
-            };
-            add_opt(runnable.or_else(|| module_def_doctest(sema.db, def)), Some(def));
-        }
-        Either::Right(impl_) => {
-            add_opt(runnable_impl(&sema, &impl_), None);
-            impl_
-                .items(db)
-                .into_iter()
-                .map(|assoc| {
-                    (
-                        match assoc {
-                            hir::AssocItem::Function(it) => runnable_fn(&sema, it)
-                                .or_else(|| module_def_doctest(sema.db, it.into())),
-                            hir::AssocItem::Const(it) => module_def_doctest(sema.db, it.into()),
-                            hir::AssocItem::TypeAlias(it) => module_def_doctest(sema.db, it.into()),
-                        },
-                        assoc,
-                    )
-                })
-                .for_each(|(r, assoc)| add_opt(r, Some(assoc.into())));
+    visit_file_defs(&sema, file_id, &mut |def| {
+        let runnable = match def {
+            Definition::Module(it) => runnable_mod(&sema, it),
+            Definition::Function(it) => runnable_fn(&sema, it),
+            Definition::SelfType(impl_) => runnable_impl(&sema, &impl_),
+            _ => None,
+        };
+        add_opt(
+            runnable
+                .or_else(|| module_def_doctest(sema.db, def))
+                // #[macro_export] mbe macros are declared in the root, while their definition may reside in a different module
+                .filter(|it| it.nav.file_id == file_id),
+            Some(def),
+        );
+        if let Definition::SelfType(impl_) = def {
+            impl_.items(db).into_iter().for_each(|assoc| {
+                let runnable = match assoc {
+                    hir::AssocItem::Function(it) => {
+                        runnable_fn(&sema, it).or_else(|| module_def_doctest(sema.db, it.into()))
+                    }
+                    hir::AssocItem::Const(it) => module_def_doctest(sema.db, it.into()),
+                    hir::AssocItem::TypeAlias(it) => module_def_doctest(sema.db, it.into()),
+                };
+                add_opt(runnable, Some(assoc.into()))
+            });
         }
     });
 
@@ -213,72 +211,78 @@ pub(crate) fn related_tests(
 ) -> Vec<Runnable> {
     let sema = Semantics::new(db);
     let mut res: FxHashSet<Runnable> = FxHashSet::default();
+    let syntax = sema.parse(position.file_id).syntax().clone();
 
-    find_related_tests(&sema, position, search_scope, &mut res);
+    find_related_tests(&sema, &syntax, position, search_scope, &mut res);
 
-    res.into_iter().collect_vec()
+    res.into_iter().collect()
 }
 
 fn find_related_tests(
-    sema: &Semantics<RootDatabase>,
+    sema: &Semantics<'_, RootDatabase>,
+    syntax: &SyntaxNode,
     position: FilePosition,
     search_scope: Option<SearchScope>,
     tests: &mut FxHashSet<Runnable>,
 ) {
-    if let Some(refs) = references::find_all_refs(sema, position, search_scope) {
-        for (file_id, refs) in refs.into_iter().flat_map(|refs| refs.references) {
-            let file = sema.parse(file_id);
-            let file = file.syntax();
-
-            // create flattened vec of tokens
-            let tokens = refs.iter().flat_map(|(range, _)| {
-                match file.token_at_offset(range.start()).next() {
-                    Some(token) => sema.descend_into_macros_many(token),
-                    None => Default::default(),
-                }
-            });
-
-            // find first suitable ancestor
-            let functions = tokens
-                .filter_map(|token| token.ancestors().find_map(ast::Fn::cast))
-                .map(|f| hir::InFile::new(sema.hir_file_for(f.syntax()), f));
-
-            for fn_def in functions {
-                // #[test/bench] expands to just the item causing us to lose the attribute, so recover them by going out of the attribute
-                let InFile { value: fn_def, .. } = &fn_def.node_with_attributes(sema.db);
-                if let Some(runnable) = as_test_runnable(sema, fn_def) {
+    // FIXME: why is this using references::find_defs, this should use ide_db::search
+    let defs = match references::find_defs(sema, syntax, position.offset) {
+        Some(defs) => defs,
+        None => return,
+    };
+    for def in defs {
+        let defs = def
+            .usages(sema)
+            .set_scope(search_scope.clone())
+            .all()
+            .references
+            .into_values()
+            .flatten();
+        for ref_ in defs {
+            let name_ref = match ref_.name {
+                ast::NameLike::NameRef(name_ref) => name_ref,
+                _ => continue,
+            };
+            if let Some(fn_def) =
+                sema.ancestors_with_macros(name_ref.syntax().clone()).find_map(ast::Fn::cast)
+            {
+                if let Some(runnable) = as_test_runnable(sema, &fn_def) {
                     // direct test
                     tests.insert(runnable);
-                } else if let Some(module) = parent_test_module(sema, fn_def) {
+                } else if let Some(module) = parent_test_module(sema, &fn_def) {
                     // indirect test
-                    find_related_tests_in_module(sema, fn_def, &module, tests);
+                    find_related_tests_in_module(sema, syntax, &fn_def, &module, tests);
                 }
             }
         }
     }
 }
+
 fn find_related_tests_in_module(
-    sema: &Semantics<RootDatabase>,
+    sema: &Semantics<'_, RootDatabase>,
+    syntax: &SyntaxNode,
     fn_def: &ast::Fn,
     parent_module: &hir::Module,
     tests: &mut FxHashSet<Runnable>,
 ) {
-    if let Some(fn_name) = fn_def.name() {
-        let mod_source = parent_module.definition_source(sema.db);
-        let range = match mod_source.value {
-            hir::ModuleSource::Module(m) => m.syntax().text_range(),
-            hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(),
-            hir::ModuleSource::SourceFile(f) => f.syntax().text_range(),
-        };
+    let fn_name = match fn_def.name() {
+        Some(it) => it,
+        _ => return,
+    };
+    let mod_source = parent_module.definition_source(sema.db);
+    let range = match &mod_source.value {
+        hir::ModuleSource::Module(m) => m.syntax().text_range(),
+        hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(),
+        hir::ModuleSource::SourceFile(f) => f.syntax().text_range(),
+    };
 
-        let file_id = mod_source.file_id.original_file(sema.db);
-        let mod_scope = SearchScope::file_range(FileRange { file_id, range });
-        let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() };
-        find_related_tests(sema, fn_pos, Some(mod_scope), tests)
-    }
+    let file_id = mod_source.file_id.original_file(sema.db);
+    let mod_scope = SearchScope::file_range(FileRange { file_id, range });
+    let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() };
+    find_related_tests(sema, syntax, fn_pos, Some(mod_scope), tests)
 }
 
-fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
+fn as_test_runnable(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
     if test_related_attribute(fn_def).is_some() {
         let function = sema.to_def(fn_def)?;
         runnable_fn(sema, function)
@@ -287,7 +291,7 @@ fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<
     }
 }
 
-fn parent_test_module(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> {
+fn parent_test_module(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> {
     fn_def.syntax().ancestors().find_map(|node| {
         let module = ast::Module::cast(node)?;
         let module = sema.to_def(&module)?;
@@ -300,27 +304,31 @@ fn parent_test_module(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Optio
     })
 }
 
-pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
-    // #[test/bench] expands to just the item causing us to lose the attribute, so recover them by going out of the attribute
-    let func = def.source(sema.db)?.node_with_attributes(sema.db);
-    let name_string = def.name(sema.db).to_string();
+pub(crate) fn runnable_fn(
+    sema: &Semantics<'_, RootDatabase>,
+    def: hir::Function,
+) -> Option<Runnable> {
+    let func = def.source(sema.db)?;
+    let name = def.name(sema.db).to_smol_str();
 
     let root = def.module(sema.db).krate().root_module(sema.db);
 
-    let kind = if name_string == "main" && def.module(sema.db) == root {
+    let kind = if name == "main" && def.module(sema.db) == root {
         RunnableKind::Bin
     } else {
-        let canonical_path = {
-            let def: hir::ModuleDef = def.into();
-            def.canonical_path(sema.db)
+        let test_id = || {
+            let canonical_path = {
+                let def: hir::ModuleDef = def.into();
+                def.canonical_path(sema.db)
+            };
+            canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name))
         };
-        let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string));
 
         if test_related_attribute(&func.value).is_some() {
             let attr = TestAttr::from_fn(&func.value);
-            RunnableKind::Test { test_id, attr }
+            RunnableKind::Test { test_id: test_id(), attr }
         } else if func.value.has_atom_attr("bench") {
-            RunnableKind::Bench { test_id }
+            RunnableKind::Bench { test_id: test_id() }
         } else {
             return None;
         }
@@ -335,7 +343,10 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
     Some(Runnable { use_name_in_title: false, nav, kind, cfg })
 }
 
-pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
+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;
     }
@@ -348,7 +359,10 @@ pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) ->
     Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::TestMod { path }, cfg })
 }
 
-pub(crate) fn runnable_impl(sema: &Semantics<RootDatabase>, def: &hir::Impl) -> Option<Runnable> {
+pub(crate) fn runnable_impl(
+    sema: &Semantics<'_, RootDatabase>,
+    def: &hir::Impl,
+) -> Option<Runnable> {
     let attrs = def.attrs(sema.db);
     if !has_runnable_doc_test(&attrs) {
         return None;
@@ -370,7 +384,7 @@ pub(crate) fn runnable_impl(sema: &Semantics<RootDatabase>, def: &hir::Impl) ->
 
 /// Creates a test mod runnable for outline modules at the top of their definition.
 fn runnable_mod_outline_definition(
-    sema: &Semantics<RootDatabase>,
+    sema: &Semantics<'_, RootDatabase>,
     def: hir::Module,
 ) -> Option<Runnable> {
     if !has_test_function_or_multiple_test_submodules(sema, &def) {
@@ -392,17 +406,19 @@ fn runnable_mod_outline_definition(
     }
 }
 
-fn module_def_doctest(db: &RootDatabase, def: hir::ModuleDef) -> Option<Runnable> {
+fn module_def_doctest(db: &RootDatabase, def: Definition) -> Option<Runnable> {
     let attrs = match def {
-        hir::ModuleDef::Module(it) => it.attrs(db),
-        hir::ModuleDef::Function(it) => it.attrs(db),
-        hir::ModuleDef::Adt(it) => it.attrs(db),
-        hir::ModuleDef::Variant(it) => it.attrs(db),
-        hir::ModuleDef::Const(it) => it.attrs(db),
-        hir::ModuleDef::Static(it) => it.attrs(db),
-        hir::ModuleDef::Trait(it) => it.attrs(db),
-        hir::ModuleDef::TypeAlias(it) => it.attrs(db),
-        hir::ModuleDef::BuiltinType(_) => return None,
+        Definition::Module(it) => it.attrs(db),
+        Definition::Function(it) => it.attrs(db),
+        Definition::Adt(it) => it.attrs(db),
+        Definition::Variant(it) => it.attrs(db),
+        Definition::Const(it) => it.attrs(db),
+        Definition::Static(it) => it.attrs(db),
+        Definition::Trait(it) => it.attrs(db),
+        Definition::TypeAlias(it) => it.attrs(db),
+        Definition::Macro(it) => it.attrs(db),
+        Definition::SelfType(it) => it.attrs(db),
+        _ => return None,
     };
     if !has_runnable_doc_test(&attrs) {
         return None;
@@ -437,10 +453,10 @@ fn module_def_doctest(db: &RootDatabase, def: hir::ModuleDef) -> Option<Runnable
         Some(path)
     })();
 
-    let test_id = path.map_or_else(|| TestId::Name(def_name.to_string()), TestId::Path);
+    let test_id = path.map_or_else(|| TestId::Name(def_name.to_smol_str()), TestId::Path);
 
     let mut nav = match def {
-        hir::ModuleDef::Module(def) => NavigationTarget::from_module_to_decl(db, def),
+        Definition::Module(def) => NavigationTarget::from_module_to_decl(db, def),
         def => def.try_to_nav(db)?,
     };
     nav.focus_range = None;
@@ -471,7 +487,7 @@ fn from_fn(fn_def: &ast::Fn) -> TestAttr {
     }
 }
 
-const RUSTDOC_FENCE: &str = "```";
+const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"];
 const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
     &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"];
 
@@ -480,7 +496,9 @@ fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
         let mut in_code_block = false;
 
         for line in String::from(doc).lines() {
-            if let Some(header) = line.strip_prefix(RUSTDOC_FENCE) {
+            if let Some(header) =
+                RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence))
+            {
                 in_code_block = !in_code_block;
 
                 if in_code_block
@@ -500,7 +518,7 @@ 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(
-    sema: &Semantics<RootDatabase>,
+    sema: &Semantics<'_, RootDatabase>,
     module: &hir::Module,
 ) -> bool {
     let mut number_of_test_submodules = 0;
@@ -509,8 +527,6 @@ fn has_test_function_or_multiple_test_submodules(
         match item {
             hir::ModuleDef::Function(f) => {
                 if let Some(it) = f.source(sema.db) {
-                    // #[test/bench] expands to just the item causing us to lose the attribute, so recover them by going out of the attribute
-                    let it = it.node_with_attributes(sema.db);
                     if test_related_attribute(&it.value).is_some() {
                         return true;
                     }
@@ -543,7 +559,8 @@ fn check(
         expect: Expect,
     ) {
         let (analysis, position) = fixture::position(ra_fixture);
-        let runnables = analysis.runnables(position.file_id).unwrap();
+        let mut runnables = analysis.runnables(position.file_id).unwrap();
+        runnables.sort_by_key(|it| (it.nav.full_range.start(), it.nav.name.clone()));
         expect.assert_debug_eq(&runnables);
         assert_eq!(
             actions,
@@ -579,9 +596,24 @@ mod not_a_root {
     fn main() {}
 }
 "#,
-            &[Bin, Test, Test, Bench, TestMod],
+            &[TestMod, Bin, Test, Test, Bench],
             expect![[r#"
                 [
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 0..137,
+                            name: "",
+                            kind: Module,
+                        },
+                        kind: TestMod {
+                            path: "",
+                        },
+                        cfg: None,
+                    },
                     Runnable {
                         use_name_in_title: false,
                         nav: NavigationTarget {
@@ -656,21 +688,6 @@ fn main() {}
                         },
                         cfg: None,
                     },
-                    Runnable {
-                        use_name_in_title: false,
-                        nav: NavigationTarget {
-                            file_id: FileId(
-                                0,
-                            ),
-                            full_range: 0..137,
-                            name: "",
-                            kind: Module,
-                        },
-                        kind: TestMod {
-                            path: "",
-                        },
-                        cfg: None,
-                    },
                 ]
             "#]],
         );
@@ -1064,7 +1081,7 @@ mod nested_tests_3 {}
     mod nested_tests_4 {}
 }
 "#,
-            &[TestMod, TestMod, TestMod, Test, Test, Test],
+            &[TestMod, TestMod, Test, Test, TestMod, Test],
             expect![[r#"
                 [
                     Runnable {
@@ -1101,23 +1118,6 @@ mod nested_tests_4 {}
                         },
                         cfg: None,
                     },
-                    Runnable {
-                        use_name_in_title: false,
-                        nav: NavigationTarget {
-                            file_id: FileId(
-                                0,
-                            ),
-                            full_range: 202..286,
-                            focus_range: 206..220,
-                            name: "nested_tests_2",
-                            kind: Module,
-                            description: "mod nested_tests_2",
-                        },
-                        kind: TestMod {
-                            path: "root_tests::nested_tests_0::nested_tests_2",
-                        },
-                        cfg: None,
-                    },
                     Runnable {
                         use_name_in_title: false,
                         nav: NavigationTarget {
@@ -1160,6 +1160,23 @@ mod nested_tests_4 {}
                         },
                         cfg: None,
                     },
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 202..286,
+                            focus_range: 206..220,
+                            name: "nested_tests_2",
+                            kind: Module,
+                            description: "mod nested_tests_2",
+                        },
+                        kind: TestMod {
+                            path: "root_tests::nested_tests_0::nested_tests_2",
+                        },
+                        cfg: None,
+                    },
                     Runnable {
                         use_name_in_title: false,
                         nav: NavigationTarget {
@@ -1196,9 +1213,24 @@ fn test_runnables_with_feature() {
 #[cfg(feature = "foo")]
 fn test_foo1() {}
 "#,
-            &[Test, TestMod],
+            &[TestMod, Test],
             expect![[r#"
                 [
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 0..51,
+                            name: "",
+                            kind: Module,
+                        },
+                        kind: TestMod {
+                            path: "",
+                        },
+                        cfg: None,
+                    },
                     Runnable {
                         use_name_in_title: false,
                         nav: NavigationTarget {
@@ -1227,21 +1259,6 @@ fn test_foo1() {}
                             ),
                         ),
                     },
-                    Runnable {
-                        use_name_in_title: false,
-                        nav: NavigationTarget {
-                            file_id: FileId(
-                                0,
-                            ),
-                            full_range: 0..51,
-                            name: "",
-                            kind: Module,
-                        },
-                        kind: TestMod {
-                            path: "",
-                        },
-                        cfg: None,
-                    },
                 ]
             "#]],
         );
@@ -1257,9 +1274,24 @@ fn test_runnables_with_features() {
 #[cfg(all(feature = "foo", feature = "bar"))]
 fn test_foo1() {}
 "#,
-            &[Test, TestMod],
+            &[TestMod, Test],
             expect![[r#"
                 [
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 0..73,
+                            name: "",
+                            kind: Module,
+                        },
+                        kind: TestMod {
+                            path: "",
+                        },
+                        cfg: None,
+                    },
                     Runnable {
                         use_name_in_title: false,
                         nav: NavigationTarget {
@@ -1298,21 +1330,6 @@ fn test_foo1() {}
                             ),
                         ),
                     },
-                    Runnable {
-                        use_name_in_title: false,
-                        nav: NavigationTarget {
-                            file_id: FileId(
-                                0,
-                            ),
-                            full_range: 0..73,
-                            name: "",
-                            kind: Module,
-                        },
-                        kind: TestMod {
-                            path: "",
-                        },
-                        cfg: None,
-                    },
                 ]
             "#]],
         );
@@ -1399,7 +1416,7 @@ mod tests {
 }
 gen2!();
 "#,
-            &[TestMod, TestMod, TestMod, Test, Test],
+            &[TestMod, TestMod, Test, Test, TestMod],
             expect![[r#"
                 [
                     Runnable {
@@ -1408,14 +1425,12 @@ mod tests {
                             file_id: FileId(
                                 0,
                             ),
-                            full_range: 202..227,
-                            focus_range: 206..211,
-                            name: "tests",
+                            full_range: 0..237,
+                            name: "",
                             kind: Module,
-                            description: "mod tests",
                         },
                         kind: TestMod {
-                            path: "tests",
+                            path: "",
                         },
                         cfg: None,
                     },
@@ -1425,29 +1440,34 @@ mod tests {
                             file_id: FileId(
                                 0,
                             ),
-                            full_range: 0..237,
-                            name: "",
+                            full_range: 202..227,
+                            focus_range: 206..211,
+                            name: "tests",
                             kind: Module,
+                            description: "mod tests",
                         },
                         kind: TestMod {
-                            path: "",
+                            path: "tests",
                         },
                         cfg: None,
                     },
                     Runnable {
-                        use_name_in_title: true,
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
                             ),
-                            full_range: 228..236,
-                            focus_range: 228..236,
-                            name: "tests2",
-                            kind: Module,
-                            description: "mod tests2",
+                            full_range: 218..225,
+                            name: "foo_test",
+                            kind: Function,
                         },
-                        kind: TestMod {
-                            path: "tests2",
+                        kind: Test {
+                            test_id: Path(
+                                "tests::foo_test",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
                         },
                         cfg: None,
                     },
@@ -1472,22 +1492,18 @@ mod tests {
                         cfg: None,
                     },
                     Runnable {
-                        use_name_in_title: false,
+                        use_name_in_title: true,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
                             ),
-                            full_range: 218..225,
-                            name: "foo_test",
-                            kind: Function,
+                            full_range: 228..236,
+                            name: "tests2",
+                            kind: Module,
+                            description: "mod tests2",
                         },
-                        kind: Test {
-                            test_id: Path(
-                                "tests::foo_test",
-                            ),
-                            attr: TestAttr {
-                                ignore: false,
-                            },
+                        kind: TestMod {
+                            path: "tests2",
                         },
                         cfg: None,
                     },
@@ -1516,26 +1532,9 @@ fn foo2() {}
 }
 foo!();
 "#,
-            &[TestMod, Test, Test, Test],
+            &[Test, Test, Test, TestMod],
             expect![[r#"
                 [
-                    Runnable {
-                        use_name_in_title: true,
-                        nav: NavigationTarget {
-                            file_id: FileId(
-                                0,
-                            ),
-                            full_range: 210..217,
-                            focus_range: 210..217,
-                            name: "foo_tests",
-                            kind: Module,
-                            description: "mod foo_tests",
-                        },
-                        kind: TestMod {
-                            path: "foo_tests",
-                        },
-                        cfg: None,
-                    },
                     Runnable {
                         use_name_in_title: true,
                         nav: NavigationTarget {
@@ -1596,6 +1595,22 @@ fn foo2() {}
                         },
                         cfg: None,
                     },
+                    Runnable {
+                        use_name_in_title: true,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 210..217,
+                            name: "foo_tests",
+                            kind: Module,
+                            description: "mod foo_tests",
+                        },
+                        kind: TestMod {
+                            path: "foo_tests",
+                        },
+                        cfg: None,
+                    },
                 ]
             "#]],
         );
@@ -1672,9 +1687,24 @@ fn t0() {}
 #[test]
 fn t1() {}
 "#,
-            &[Test, Test, TestMod],
+            &[TestMod, Test, Test],
             expect![[r#"
                 [
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                1,
+                            ),
+                            full_range: 0..39,
+                            name: "m",
+                            kind: Module,
+                        },
+                        kind: TestMod {
+                            path: "m",
+                        },
+                        cfg: None,
+                    },
                     Runnable {
                         use_name_in_title: false,
                         nav: NavigationTarget {
@@ -1717,21 +1747,6 @@ fn t1() {}
                         },
                         cfg: None,
                     },
-                    Runnable {
-                        use_name_in_title: false,
-                        nav: NavigationTarget {
-                            file_id: FileId(
-                                1,
-                            ),
-                            full_range: 0..39,
-                            name: "m",
-                            kind: Module,
-                        },
-                        kind: TestMod {
-                            path: "m",
-                        },
-                        cfg: None,
-                    },
                 ]
             "#]],
         );
@@ -2081,4 +2096,68 @@ fn t() {}
             "#]],
         );
     }
+
+    #[test]
+    fn doc_test_macro_export_mbe() {
+        check(
+            r#"
+//- /lib.rs
+$0
+mod foo;
+
+//- /foo.rs
+/// ```
+/// fn foo() {
+/// }
+/// ```
+#[macro_export]
+macro_rules! foo {
+    () => {
+
+    };
+}
+"#,
+            &[],
+            expect![[r#"
+                []
+            "#]],
+        );
+        check(
+            r#"
+//- /lib.rs
+$0
+/// ```
+/// fn foo() {
+/// }
+/// ```
+#[macro_export]
+macro_rules! foo {
+    () => {
+
+    };
+}
+"#,
+            &[DocTest],
+            expect![[r#"
+                [
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 1..94,
+                            name: "foo",
+                        },
+                        kind: DocTest {
+                            test_id: Path(
+                                "foo",
+                            ),
+                        },
+                        cfg: None,
+                    },
+                ]
+            "#]],
+        );
+    }
 }