Skip to content

Commit 7b3dffd

Browse files
committed
refactor snapshot-tests detection in runnables
1 parent 2983ce8 commit 7b3dffd

File tree

1 file changed

+104
-92
lines changed

1 file changed

+104
-92
lines changed

crates/ide/src/runnables.rs

Lines changed: 104 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
use std::fmt;
1+
use std::{fmt, sync::OnceLock};
22

3+
use arrayvec::ArrayVec;
34
use ast::HasName;
45
use cfg::{CfgAtom, CfgExpr};
56
use hir::{
67
db::HirDatabase, sym, AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate, HasSource, HirFileIdExt,
7-
Semantics,
8+
ModPath, Name, PathKind, Semantics, Symbol,
89
};
910
use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn};
1011
use ide_db::{
@@ -336,7 +337,7 @@ pub(crate) fn runnable_fn(
336337
}
337338
};
338339

339-
let fn_source = def.source(sema.db)?;
340+
let fn_source = sema.source(def)?;
340341
let nav = NavigationTarget::from_named(
341342
sema.db,
342343
fn_source.as_ref().map(|it| it as &dyn ast::HasName),
@@ -345,7 +346,8 @@ pub(crate) fn runnable_fn(
345346
.call_site();
346347

347348
let file_range = fn_source.syntax().original_file_range_with_macro_call_body(sema.db);
348-
let update_test = TestDefs::new(sema, def.krate(sema.db), file_range).update_test();
349+
let update_test =
350+
UpdateTest::find_snapshot_macro(sema, &fn_source.file_syntax(sema.db), file_range);
349351

350352
let cfg = def.attrs(sema.db).cfg();
351353
Some(Runnable { use_name_in_title: false, nav, kind, cfg, update_test })
@@ -374,13 +376,13 @@ pub(crate) fn runnable_mod(
374376
let cfg = attrs.cfg();
375377
let nav = NavigationTarget::from_module_to_decl(sema.db, def).call_site();
376378

377-
let file_range = {
378-
let src = def.definition_source(sema.db);
379-
let file_id = src.file_id.original_file(sema.db);
380-
let range = src.file_syntax(sema.db).text_range();
381-
hir::FileRange { file_id, range }
379+
let module_source = sema.module_definition_node(def);
380+
let module_syntax = module_source.file_syntax(sema.db);
381+
let file_range = hir::FileRange {
382+
file_id: module_source.file_id.original_file(sema.db),
383+
range: module_syntax.text_range(),
382384
};
383-
let update_test = TestDefs::new(sema, def.krate(), file_range).update_test();
385+
let update_test = UpdateTest::find_snapshot_macro(sema, &module_syntax, file_range);
384386

385387
Some(Runnable {
386388
use_name_in_title: false,
@@ -414,9 +416,11 @@ pub(crate) fn runnable_impl(
414416
test_id.retain(|c| c != ' ');
415417
let test_id = TestId::Path(test_id);
416418

417-
let impl_source =
418-
def.source(sema.db)?.syntax().original_file_range_with_macro_call_body(sema.db);
419-
let update_test = TestDefs::new(sema, def.krate(sema.db), impl_source).update_test();
419+
let impl_source = sema.source(*def)?;
420+
let impl_syntax = impl_source.syntax();
421+
let file_range = impl_syntax.original_file_range_with_macro_call_body(sema.db);
422+
let update_test =
423+
UpdateTest::find_snapshot_macro(sema, &impl_syntax.file_syntax(sema.db), file_range);
420424

421425
Some(Runnable {
422426
use_name_in_title: false,
@@ -456,13 +460,13 @@ fn runnable_mod_outline_definition(
456460
let attrs = def.attrs(sema.db);
457461
let cfg = attrs.cfg();
458462

459-
let file_range = {
460-
let src = def.definition_source(sema.db);
461-
let file_id = src.file_id.original_file(sema.db);
462-
let range = src.file_syntax(sema.db).text_range();
463-
hir::FileRange { file_id, range }
463+
let mod_source = sema.module_definition_node(def);
464+
let mod_syntax = mod_source.file_syntax(sema.db);
465+
let file_range = hir::FileRange {
466+
file_id: mod_source.file_id.original_file(sema.db),
467+
range: mod_syntax.text_range(),
464468
};
465-
let update_test = TestDefs::new(sema, def.krate(), file_range).update_test();
469+
let update_test = UpdateTest::find_snapshot_macro(sema, &mod_syntax, file_range);
466470

467471
Some(Runnable {
468472
use_name_in_title: false,
@@ -616,16 +620,93 @@ fn has_test_function_or_multiple_test_submodules(
616620
number_of_test_submodules > 1
617621
}
618622

619-
struct TestDefs<'a, 'b>(&'a Semantics<'b, RootDatabase>, hir::Crate, hir::FileRange);
620-
621623
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
622624
pub struct UpdateTest {
623625
pub expect_test: bool,
624626
pub insta: bool,
625627
pub snapbox: bool,
626628
}
627629

630+
static SNAPSHOT_TEST_MACROS: OnceLock<FxHashMap<&str, Vec<ModPath>>> = OnceLock::new();
631+
628632
impl UpdateTest {
633+
const EXPECT_CRATE: &str = "expect_test";
634+
const EXPECT_MACROS: &[&str] = &["expect", "expect_file"];
635+
636+
const INSTA_CRATE: &str = "insta";
637+
const INSTA_MACROS: &[&str] = &[
638+
"assert_snapshot",
639+
"assert_debug_snapshot",
640+
"assert_display_snapshot",
641+
"assert_json_snapshot",
642+
"assert_yaml_snapshot",
643+
"assert_ron_snapshot",
644+
"assert_toml_snapshot",
645+
"assert_csv_snapshot",
646+
"assert_compact_json_snapshot",
647+
"assert_compact_debug_snapshot",
648+
"assert_binary_snapshot",
649+
];
650+
651+
const SNAPBOX_CRATE: &str = "snapbox";
652+
const SNAPBOX_MACROS: &[&str] = &["assert_data_eq", "file", "str"];
653+
654+
fn find_snapshot_macro(
655+
sema: &Semantics<'_, RootDatabase>,
656+
scope: &SyntaxNode,
657+
file_range: hir::FileRange,
658+
) -> Self {
659+
fn init<'a>(
660+
krate_name: &'a str,
661+
paths: &[&str],
662+
map: &mut FxHashMap<&'a str, Vec<ModPath>>,
663+
) {
664+
let mut res = Vec::with_capacity(paths.len());
665+
let krate = Name::new_symbol_root(Symbol::intern(krate_name));
666+
for path in paths {
667+
let segments = [krate.clone(), Name::new_symbol_root(Symbol::intern(path))];
668+
let mod_path = ModPath::from_segments(PathKind::Abs, segments);
669+
res.push(mod_path);
670+
}
671+
map.insert(krate_name, res);
672+
}
673+
674+
let mod_paths = SNAPSHOT_TEST_MACROS.get_or_init(|| {
675+
let mut map = FxHashMap::default();
676+
init(Self::EXPECT_CRATE, Self::EXPECT_MACROS, &mut map);
677+
init(Self::INSTA_CRATE, Self::INSTA_MACROS, &mut map);
678+
init(Self::SNAPBOX_CRATE, Self::SNAPBOX_MACROS, &mut map);
679+
map
680+
});
681+
682+
let search_scope = SearchScope::file_range(file_range);
683+
let find_macro = |paths: &[ModPath]| {
684+
for path in paths {
685+
let Some(items) = sema.resolve_mod_path(scope, path) else {
686+
continue;
687+
};
688+
for item in items {
689+
if let hir::ItemInNs::Macros(makro) = item {
690+
if Definition::Macro(makro)
691+
.usages(sema)
692+
.in_scope(&search_scope)
693+
.at_least_one()
694+
{
695+
return true;
696+
}
697+
}
698+
}
699+
}
700+
false
701+
};
702+
703+
UpdateTest {
704+
expect_test: find_macro(mod_paths.get(Self::EXPECT_CRATE).unwrap()),
705+
insta: find_macro(mod_paths.get(Self::INSTA_CRATE).unwrap()),
706+
snapbox: find_macro(mod_paths.get(Self::SNAPBOX_CRATE).unwrap()),
707+
}
708+
}
709+
629710
pub fn label(&self) -> Option<SmolStr> {
630711
let mut builder: SmallVec<[_; 3]> = SmallVec::new();
631712
if self.expect_test {
@@ -646,8 +727,8 @@ impl UpdateTest {
646727
}
647728
}
648729

649-
pub fn env(&self) -> SmallVec<[(&str, &str); 3]> {
650-
let mut env = SmallVec::new();
730+
pub fn env(&self) -> ArrayVec<(&str, &str), 3> {
731+
let mut env = ArrayVec::new();
651732
if self.expect_test {
652733
env.push(("UPDATE_EXPECT", "1"));
653734
}
@@ -661,75 +742,6 @@ impl UpdateTest {
661742
}
662743
}
663744

664-
impl<'a, 'b> TestDefs<'a, 'b> {
665-
fn new(
666-
sema: &'a Semantics<'b, RootDatabase>,
667-
current_krate: hir::Crate,
668-
file_range: hir::FileRange,
669-
) -> Self {
670-
Self(sema, current_krate, file_range)
671-
}
672-
673-
fn update_test(&self) -> UpdateTest {
674-
UpdateTest { expect_test: self.expect_test(), insta: self.insta(), snapbox: self.snapbox() }
675-
}
676-
677-
fn expect_test(&self) -> bool {
678-
self.find_macro("expect_test", &["expect", "expect_file"])
679-
}
680-
681-
fn insta(&self) -> bool {
682-
self.find_macro(
683-
"insta",
684-
&[
685-
"assert_snapshot",
686-
"assert_debug_snapshot",
687-
"assert_display_snapshot",
688-
"assert_json_snapshot",
689-
"assert_yaml_snapshot",
690-
"assert_ron_snapshot",
691-
"assert_toml_snapshot",
692-
"assert_csv_snapshot",
693-
"assert_compact_json_snapshot",
694-
"assert_compact_debug_snapshot",
695-
"assert_binary_snapshot",
696-
],
697-
)
698-
}
699-
700-
fn snapbox(&self) -> bool {
701-
self.find_macro("snapbox", &["assert_data_eq", "file", "str"])
702-
}
703-
704-
fn find_macro(&self, crate_name: &str, paths: &[&str]) -> bool {
705-
let db = self.0.db;
706-
707-
let Some(dep) =
708-
self.1.dependencies(db).into_iter().find(|dep| dep.name.eq_ident(crate_name))
709-
else {
710-
return false;
711-
};
712-
let module = dep.krate.root_module();
713-
let scope = module.scope(db, None);
714-
715-
paths
716-
.iter()
717-
.filter_map(|path| {
718-
let (_, def) = scope.iter().find(|(name, _)| name.eq_ident(path))?;
719-
match def {
720-
hir::ScopeDef::ModuleDef(hir::ModuleDef::Macro(it)) => Some(it),
721-
_ => None,
722-
}
723-
})
724-
.any(|makro| {
725-
Definition::Macro(*makro)
726-
.usages(self.0)
727-
.in_scope(&SearchScope::file_range(self.2))
728-
.at_least_one()
729-
})
730-
}
731-
}
732-
733745
#[cfg(test)]
734746
mod tests {
735747
use expect_test::{expect, Expect};

0 commit comments

Comments
 (0)