]> git.lizzy.rs Git - rust.git/blobdiff - crates/ide/src/runnables.rs
Unnest ide::display::navigation_target module
[rust.git] / crates / ide / src / runnables.rs
index d1ea3491e83829c36d4b1cd809b5201166168978..3078789d1236fe309b83813b7a3f9fce14f73059 100644 (file)
@@ -1,25 +1,29 @@
 use std::fmt;
 
-use ast::NameOwner;
+use ast::HasName;
 use cfg::CfgExpr;
-use either::Either;
 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,
 };
 use itertools::Itertools;
-use rustc_hash::FxHashSet;
-use stdx::always;
-use syntax::ast::{self, AstNode, AttrsOwner};
+use rustc_hash::{FxHashMap, FxHashSet};
+use stdx::{always, format_to};
+use syntax::{
+    ast::{self, AstNode, HasAttrs as _},
+    SmolStr, SyntaxNode,
+};
 
-use crate::{display::TryToNav, references, FileId, NavigationTarget};
+use crate::{references, FileId, NavigationTarget, ToNav, TryToNav};
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub struct Runnable {
+    pub use_name_in_title: bool,
     pub nav: NavigationTarget,
     pub kind: RunnableKind,
     pub cfg: Option<CfgExpr>,
@@ -27,7 +31,7 @@ pub struct Runnable {
 
 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
 pub enum TestId {
-    Name(String),
+    Name(SmolStr),
     Path(String),
 }
 
@@ -49,20 +53,16 @@ pub enum RunnableKind {
     Bin,
 }
 
-#[derive(Debug, Eq, PartialEq)]
-pub struct RunnableAction {
-    pub run_title: &'static str,
-    pub debugee: bool,
+#[cfg(test)]
+#[derive(Debug, Clone, Hash, PartialEq, Eq)]
+enum RunnableTestKind {
+    Test,
+    TestMod,
+    DocTest,
+    Bench,
+    Bin,
 }
 
-const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
-const TEST_MOD: RunnableAction =
-    RunnableAction { run_title: "▶\u{fe0e} Run Tests", debugee: true };
-const DOCTEST: RunnableAction =
-    RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
-const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
-const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
-
 impl Runnable {
     // test package::module::testname
     pub fn label(&self, target: Option<String>) -> String {
@@ -77,13 +77,33 @@ pub fn label(&self, target: Option<String>) -> String {
         }
     }
 
-    pub fn action(&self) -> &'static RunnableAction {
+    pub fn title(&self) -> String {
+        let mut s = String::from("▶\u{fe0e} Run ");
+        if self.use_name_in_title {
+            format_to!(s, "{}", self.nav.name);
+            if !matches!(self.kind, RunnableKind::Bin) {
+                s.push(' ');
+            }
+        }
+        let suffix = match &self.kind {
+            RunnableKind::TestMod { .. } => "Tests",
+            RunnableKind::Test { .. } => "Test",
+            RunnableKind::DocTest { .. } => "Doctest",
+            RunnableKind::Bench { .. } => "Bench",
+            RunnableKind::Bin => return s,
+        };
+        s.push_str(suffix);
+        s
+    }
+
+    #[cfg(test)]
+    fn test_kind(&self) -> RunnableTestKind {
         match &self.kind {
-            RunnableKind::TestMod { .. } => &TEST_MOD,
-            RunnableKind::Test { .. } => &TEST,
-            RunnableKind::DocTest { .. } => &DOCTEST,
-            RunnableKind::Bench { .. } => &BENCH,
-            RunnableKind::Bin => &BIN,
+            RunnableKind::TestMod { .. } => RunnableTestKind::TestMod,
+            RunnableKind::Test { .. } => RunnableTestKind::Test,
+            RunnableKind::DocTest { .. } => RunnableTestKind::DocTest,
+            RunnableKind::Bench { .. } => RunnableTestKind::Bench,
+            RunnableKind::Bin => RunnableTestKind::Bin,
         }
     }
 }
@@ -104,40 +124,65 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
     let sema = Semantics::new(db);
 
     let mut res = Vec::new();
-    let mut add_opt = |runnable: Option<Runnable>| {
-        if let Some(runnable) = runnable.filter(|r| {
+    // Record all runnables that come from macro expansions here instead.
+    // In case an expansion creates multiple runnables we want to name them to avoid emitting a bunch of equally named runnables.
+    let mut in_macro_expansion = FxHashMap::<hir::HirFileId, Vec<Runnable>>::default();
+    let mut add_opt = |runnable: Option<Runnable>, def| {
+        if let Some(runnable) = runnable.filter(|runnable| {
             always!(
-                r.nav.file_id == file_id,
-                "tried adding a runnable pointing to a different file"
+                runnable.nav.file_id == file_id,
+                "tried adding a runnable pointing to a different file: {:?} for {:?}",
+                runnable.kind,
+                file_id
             )
         }) {
+            if let Some(def) = def {
+                let file_id = match def {
+                    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()) {
+                    in_macro_expansion.entry(file_id).or_default().push(runnable);
+                    return;
+                }
+            }
             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, def)));
-        }
-        Either::Right(impl_) => {
-            add_opt(runnable_impl(&sema, &impl_));
-            impl_
-                .items(db)
-                .into_iter()
-                .map(|assoc| match assoc {
+    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)), 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, it.into()))
+                        runnable_fn(&sema, it).or_else(|| module_def_doctest(sema.db, it.into()))
                     }
-                    hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
-                    hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
-                })
-                .for_each(|r| add_opt(r));
+                    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()))
+            });
         }
     });
+
+    sema.to_module_defs(file_id)
+        .map(|it| runnable_mod_outline_definition(&sema, it))
+        .for_each(|it| add_opt(it, None));
+
+    res.extend(in_macro_expansion.into_iter().flat_map(|(_, runnables)| {
+        let use_name_in_title = runnables.len() != 1;
+        runnables.into_iter().map(move |mut r| {
+            r.use_name_in_title = use_name_in_title;
+            r
+        })
+    }));
     res
 }
 
@@ -161,35 +206,43 @@ 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>,
+    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.references {
-            let file = sema.parse(file_id);
-            let file = file.syntax();
-            let functions = refs.iter().filter_map(|(range, _)| {
-                let token = file.token_at_offset(range.start()).next()?;
-                let token = sema.descend_into_macros(token);
-                token.ancestors().find_map(ast::Fn::cast)
-            });
-
-            for fn_def in functions {
+    let defs = references::find_defs(sema, syntax, position.offset);
+    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) {
                     // indirect test
-                    find_related_tests_in_module(sema, &fn_def, &module, tests);
+                    find_related_tests_in_module(sema, syntax, &fn_def, &module, tests);
                 }
             }
         }
@@ -198,23 +251,26 @@ fn find_related_tests(
 
 fn find_related_tests_in_module(
     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> {
@@ -241,24 +297,26 @@ 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> {
     let func = def.source(sema.db)?;
-    let name_string = def.name(sema.db).to_string();
+    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;
         }
@@ -266,11 +324,11 @@ pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) ->
 
     let nav = NavigationTarget::from_named(
         sema.db,
-        func.as_ref().map(|it| it as &dyn ast::NameOwner),
+        func.as_ref().map(|it| it as &dyn ast::HasName),
         SymbolKind::Function,
     );
     let cfg = def.attrs(sema.db).cfg();
-    Some(Runnable { nav, kind, cfg })
+    Some(Runnable { use_name_in_title: false, nav, kind, cfg })
 }
 
 pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
@@ -283,7 +341,7 @@ pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) ->
     let attrs = def.attrs(sema.db);
     let cfg = attrs.cfg();
     let nav = NavigationTarget::from_module_to_decl(sema.db, def);
-    Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
+    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> {
@@ -303,67 +361,96 @@ pub(crate) fn runnable_impl(sema: &Semantics<RootDatabase>, def: &hir::Impl) ->
     };
     let test_id = TestId::Path(format!("{}{}", adt_name, params));
 
-    Some(Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg })
+    Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, cfg })
+}
+
+/// Creates a test mod runnable for outline modules at the top of their definition.
+fn runnable_mod_outline_definition(
+    sema: &Semantics<RootDatabase>,
+    def: hir::Module,
+) -> Option<Runnable> {
+    if !has_test_function_or_multiple_test_submodules(sema, &def) {
+        return None;
+    }
+    let path =
+        def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
+
+    let attrs = def.attrs(sema.db);
+    let cfg = attrs.cfg();
+    match def.definition_source(sema.db).value {
+        hir::ModuleSource::SourceFile(_) => Some(Runnable {
+            use_name_in_title: false,
+            nav: def.to_nav(sema.db),
+            kind: RunnableKind::TestMod { path },
+            cfg,
+        }),
+        _ => None,
+    }
 }
 
-fn module_def_doctest(sema: &Semantics<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(sema.db),
-        hir::ModuleDef::Function(it) => it.attrs(sema.db),
-        hir::ModuleDef::Adt(it) => it.attrs(sema.db),
-        hir::ModuleDef::Variant(it) => it.attrs(sema.db),
-        hir::ModuleDef::Const(it) => it.attrs(sema.db),
-        hir::ModuleDef::Static(it) => it.attrs(sema.db),
-        hir::ModuleDef::Trait(it) => it.attrs(sema.db),
-        hir::ModuleDef::TypeAlias(it) => it.attrs(sema.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;
     }
-    let def_name = def.name(sema.db).map(|it| it.to_string());
-    let test_id = def
-        .canonical_path(sema.db)
-        // This probably belongs to canonical path?
-        .map(|path| {
-            let assoc_def = match def {
-                hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db),
-                hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db),
-                hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db),
-                _ => None,
-            };
-            // FIXME: this also looks very wrong
-            if let Some(assoc_def) = assoc_def {
-                if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
-                    let ty = imp.self_ty(sema.db);
-                    if let Some(adt) = ty.as_adt() {
-                        let name = adt.name(sema.db);
-                        let idx = path.rfind(':').map_or(0, |idx| idx + 1);
-                        let (prefix, suffix) = path.split_at(idx);
-                        let mut ty_args = ty.type_arguments().peekable();
-                        let params = if ty_args.peek().is_some() {
-                            format!(
-                                "<{}>",
-                                ty_args.format_with(", ", |ty, cb| cb(&ty.display(sema.db)))
-                            )
-                        } else {
-                            String::new()
-                        };
-                        return format!("{}{}{}::{}", prefix, name, params, suffix);
+    let def_name = def.name(db)?;
+    let path = (|| {
+        let mut path = String::new();
+        def.canonical_module_path(db)?
+            .flat_map(|it| it.name(db))
+            .for_each(|name| format_to!(path, "{}::", name));
+        // This probably belongs to canonical_path?
+        if let Some(assoc_item) = def.as_assoc_item(db) {
+            if let hir::AssocItemContainer::Impl(imp) = assoc_item.container(db) {
+                let ty = imp.self_ty(db);
+                if let Some(adt) = ty.as_adt() {
+                    let name = adt.name(db);
+                    let mut ty_args = ty.type_arguments().peekable();
+                    format_to!(path, "{}", name);
+                    if ty_args.peek().is_some() {
+                        format_to!(
+                            path,
+                            "<{}>",
+                            ty_args.format_with(", ", |ty, cb| cb(&ty.display(db)))
+                        );
                     }
+                    format_to!(path, "::{}", def_name);
+                    return Some(path);
                 }
             }
-            path
-        })
-        .map(TestId::Path)
-        .or_else(|| def_name.clone().map(TestId::Name))?;
+        }
+        format_to!(path, "{}", def_name);
+        Some(path)
+    })();
 
-    let mut nav = def.try_to_nav(sema.db)?;
+    let test_id = path.map_or_else(|| TestId::Name(def_name.to_smol_str()), TestId::Path);
+
+    let mut nav = match def {
+        Definition::Module(def) => NavigationTarget::from_module_to_decl(db, def),
+        def => def.try_to_nav(db)?,
+    };
     nav.focus_range = None;
     nav.description = None;
     nav.docs = None;
     nav.kind = None;
-    let res = Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() };
+    let res = Runnable {
+        use_name_in_title: false,
+        nav,
+        kind: RunnableKind::DocTest { test_id },
+        cfg: attrs.cfg(),
+    };
     Some(res)
 }
 
@@ -443,12 +530,12 @@ mod tests {
 
     use crate::fixture;
 
-    use super::*;
+    use super::{RunnableTestKind::*, *};
 
     fn check(
         ra_fixture: &str,
         // FIXME: fold this into `expect` as well
-        actions: &[&RunnableAction],
+        actions: &[RunnableTestKind],
         expect: Expect,
     ) {
         let (analysis, position) = fixture::position(ra_fixture);
@@ -456,7 +543,7 @@ fn check(
         expect.assert_debug_eq(&runnables);
         assert_eq!(
             actions,
-            runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice()
+            runnables.into_iter().map(|it| it.test_kind()).collect::<Vec<_>>().as_slice()
         );
     }
 
@@ -488,10 +575,11 @@ mod not_a_root {
     fn main() {}
 }
 "#,
-            &[&BIN, &TEST, &TEST, &BENCH],
+            &[Bin, Test, Test, Bench, TestMod],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -505,6 +593,7 @@ fn main() {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -525,6 +614,7 @@ fn main() {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -545,6 +635,7 @@ fn main() {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -561,6 +652,21 @@ 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,
+                    },
                 ]
             "#]],
         );
@@ -662,10 +768,11 @@ fn test() -> usize {
 /// ```
 impl Test for StructWithRunnable {}
 "#,
-            &[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST],
+            &[Bin, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -679,6 +786,7 @@ impl Test for StructWithRunnable {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -694,6 +802,7 @@ impl Test for StructWithRunnable {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -709,6 +818,7 @@ impl Test for StructWithRunnable {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -724,6 +834,7 @@ impl Test for StructWithRunnable {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -739,6 +850,7 @@ impl Test for StructWithRunnable {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -754,6 +866,7 @@ impl Test for StructWithRunnable {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -769,6 +882,7 @@ impl Test for StructWithRunnable {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -786,6 +900,7 @@ impl Test for StructWithRunnable {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -823,10 +938,11 @@ impl Data {
     fn foo() {}
 }
 "#,
-            &[&BIN, &DOCTEST],
+            &[Bin, DocTest],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -840,6 +956,7 @@ fn foo() {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -870,10 +987,11 @@ mod test_mod {
     fn test_foo1() {}
 }
 "#,
-            &[&TEST_MOD, &TEST],
+            &[TestMod, Test],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -890,6 +1008,7 @@ fn test_foo1() {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -941,10 +1060,11 @@ mod nested_tests_3 {}
     mod nested_tests_4 {}
 }
 "#,
-            &[&TEST_MOD, &TEST_MOD, &TEST_MOD, &TEST, &TEST, &TEST],
+            &[TestMod, TestMod, TestMod, Test, Test, Test],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -961,6 +1081,7 @@ mod nested_tests_4 {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -977,6 +1098,7 @@ mod nested_tests_4 {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -993,6 +1115,7 @@ mod nested_tests_4 {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1013,6 +1136,7 @@ mod nested_tests_4 {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1033,6 +1157,7 @@ mod nested_tests_4 {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1067,10 +1192,11 @@ fn test_runnables_with_feature() {
 #[cfg(feature = "foo")]
 fn test_foo1() {}
 "#,
-            &[&TEST],
+            &[Test, TestMod],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1097,6 +1223,21 @@ 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,
+                    },
                 ]
             "#]],
         );
@@ -1112,10 +1253,11 @@ fn test_runnables_with_features() {
 #[cfg(all(feature = "foo", feature = "bar"))]
 fn test_foo1() {}
 "#,
-            &[&TEST],
+            &[Test, TestMod],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1152,6 +1294,21 @@ 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,
+                    },
                 ]
             "#]],
         );
@@ -1189,10 +1346,11 @@ impl Foo {
     fn foo() {}
 }
         "#,
-            &[&DOCTEST],
+            &[DocTest],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 1,
@@ -1226,9 +1384,9 @@ fn foo_test() {}
 }
 macro_rules! gen2 {
     () => {
-        mod m_tests {
+        mod tests2 {
             #[test]
-            fn foo_test() {}
+            fn foo_test2() {}
         }
     }
 }
@@ -1237,10 +1395,11 @@ mod tests {
 }
 gen2!();
 "#,
-            &[&TEST_MOD, &TEST_MOD, &TEST, &TEST],
+            &[TestMod, TestMod, TestMod, Test, Test],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1257,28 +1416,64 @@ mod tests {
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 0..237,
+                            name: "",
+                            kind: Module,
+                        },
+                        kind: TestMod {
+                            path: "",
+                        },
+                        cfg: None,
+                    },
+                    Runnable {
+                        use_name_in_title: true,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
                             ),
                             full_range: 228..236,
                             focus_range: 228..236,
-                            name: "m_tests",
+                            name: "tests2",
                             kind: Module,
-                            description: "mod m_tests",
+                            description: "mod tests2",
                         },
                         kind: TestMod {
-                            path: "m_tests",
+                            path: "tests2",
+                        },
+                        cfg: None,
+                    },
+                    Runnable {
+                        use_name_in_title: true,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 228..236,
+                            name: "foo_test2",
+                            kind: Function,
+                        },
+                        kind: Test {
+                            test_id: Path(
+                                "tests2::foo_test2",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
                         },
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
                             ),
                             full_range: 218..225,
-                            focus_range: 218..225,
                             name: "foo_test",
                             kind: Function,
                         },
@@ -1292,19 +1487,104 @@ mod tests {
                         },
                         cfg: None,
                     },
+                ]
+            "#]],
+        );
+    }
+
+    #[test]
+    fn big_mac() {
+        check(
+            r#"
+//- /lib.rs
+$0
+macro_rules! foo {
+    () => {
+        mod foo_tests {
+            #[test]
+            fn foo0() {}
+            #[test]
+            fn foo1() {}
+            #[test]
+            fn foo2() {}
+        }
+    };
+}
+foo!();
+"#,
+            &[TestMod, Test, Test, Test],
+            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 {
                             file_id: FileId(
                                 0,
                             ),
-                            full_range: 228..236,
-                            focus_range: 228..236,
-                            name: "foo_test",
+                            full_range: 210..217,
+                            name: "foo0",
+                            kind: Function,
+                        },
+                        kind: Test {
+                            test_id: Path(
+                                "foo_tests::foo0",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
+                        },
+                        cfg: None,
+                    },
+                    Runnable {
+                        use_name_in_title: true,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 210..217,
+                            name: "foo1",
+                            kind: Function,
+                        },
+                        kind: Test {
+                            test_id: Path(
+                                "foo_tests::foo1",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
+                        },
+                        cfg: None,
+                    },
+                    Runnable {
+                        use_name_in_title: true,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 210..217,
+                            name: "foo2",
                             kind: Function,
                         },
                         kind: Test {
                             test_id: Path(
-                                "m_tests::foo_test",
+                                "foo_tests::foo2",
                             ),
                             attr: TestAttr {
                                 ignore: false,
@@ -1350,10 +1630,11 @@ fn t0() {}
 #[test]
 fn t1() {}
 "#,
-            &[&TEST_MOD],
+            &[TestMod],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1387,10 +1668,11 @@ fn t0() {}
 #[test]
 fn t1() {}
 "#,
-            &[&TEST, &TEST],
+            &[Test, Test, TestMod],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 1,
@@ -1411,6 +1693,7 @@ fn t1() {}
                         cfg: None,
                     },
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 1,
@@ -1430,6 +1713,103 @@ 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,
+                    },
+                ]
+            "#]],
+        );
+    }
+
+    #[test]
+    fn attributed_module() {
+        check(
+            r#"
+//- proc_macros: identity
+//- /lib.rs
+$0
+#[proc_macros::identity]
+mod module {
+    #[test]
+    fn t0() {}
+    #[test]
+    fn t1() {}
+}
+"#,
+            &[TestMod, Test, Test],
+            expect![[r#"
+                [
+                    Runnable {
+                        use_name_in_title: true,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 26..94,
+                            focus_range: 30..36,
+                            name: "module",
+                            kind: Module,
+                            description: "mod module",
+                        },
+                        kind: TestMod {
+                            path: "module",
+                        },
+                        cfg: None,
+                    },
+                    Runnable {
+                        use_name_in_title: true,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 43..65,
+                            focus_range: 58..60,
+                            name: "t0",
+                            kind: Function,
+                        },
+                        kind: Test {
+                            test_id: Path(
+                                "module::t0",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
+                        },
+                        cfg: None,
+                    },
+                    Runnable {
+                        use_name_in_title: true,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 70..92,
+                            focus_range: 85..87,
+                            name: "t1",
+                            kind: Function,
+                        },
+                        kind: Test {
+                            test_id: Path(
+                                "module::t1",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
+                        },
+                        cfg: None,
+                    },
                 ]
             "#]],
         );
@@ -1465,6 +1845,7 @@ fn foo_test() {
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1507,28 +1888,29 @@ fn foo_test() {
 }
 "#,
             expect![[r#"
-            [
-                Runnable {
-                    nav: NavigationTarget {
-                        file_id: FileId(
-                            0,
-                        ),
-                        full_range: 71..122,
-                        focus_range: 86..94,
-                        name: "foo_test",
-                        kind: Function,
-                    },
-                    kind: Test {
-                        test_id: Path(
-                            "tests::foo_test",
-                        ),
-                        attr: TestAttr {
-                            ignore: false,
+                [
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 71..122,
+                            focus_range: 86..94,
+                            name: "foo_test",
+                            kind: Function,
                         },
+                        kind: Test {
+                            test_id: Path(
+                                "tests::foo_test",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
+                        },
+                        cfg: None,
                     },
-                    cfg: None,
-                },
-            ]
+                ]
             "#]],
         );
     }
@@ -1560,6 +1942,7 @@ fn foo_test() {
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,
@@ -1609,48 +1992,50 @@ fn foo2_test() {
 }
 "#,
             expect![[r#"
-            [
-                Runnable {
-                    nav: NavigationTarget {
-                        file_id: FileId(
-                            0,
-                        ),
-                        full_range: 52..115,
-                        focus_range: 67..75,
-                        name: "foo_test",
-                        kind: Function,
-                    },
-                    kind: Test {
-                        test_id: Path(
-                            "tests::foo_test",
-                        ),
-                        attr: TestAttr {
-                            ignore: false,
+                [
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 52..115,
+                            focus_range: 67..75,
+                            name: "foo_test",
+                            kind: Function,
                         },
+                        kind: Test {
+                            test_id: Path(
+                                "tests::foo_test",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
+                        },
+                        cfg: None,
                     },
-                    cfg: None,
-                },
-                Runnable {
-                    nav: NavigationTarget {
-                        file_id: FileId(
-                            0,
-                        ),
-                        full_range: 121..185,
-                        focus_range: 136..145,
-                        name: "foo2_test",
-                        kind: Function,
-                    },
-                    kind: Test {
-                        test_id: Path(
-                            "tests::foo2_test",
-                        ),
-                        attr: TestAttr {
-                            ignore: false,
+                    Runnable {
+                        use_name_in_title: false,
+                        nav: NavigationTarget {
+                            file_id: FileId(
+                                0,
+                            ),
+                            full_range: 121..185,
+                            focus_range: 136..145,
+                            name: "foo2_test",
+                            kind: Function,
                         },
+                        kind: Test {
+                            test_id: Path(
+                                "tests::foo2_test",
+                            ),
+                            attr: TestAttr {
+                                ignore: false,
+                            },
+                        },
+                        cfg: None,
                     },
-                    cfg: None,
-                },
-            ]
+                ]
             "#]],
         );
     }
@@ -1669,10 +2054,11 @@ impl<T, U> Foo<T, U> {
     fn t() {}
 }
 "#,
-            &[&DOCTEST],
+            &[DocTest],
             expect![[r#"
                 [
                     Runnable {
+                        use_name_in_title: false,
                         nav: NavigationTarget {
                             file_id: FileId(
                                 0,