use std::cell::RefCell;
use std::hash::{Hash, Hasher};
use std::ops::AddAssign;
use rustc_hash::{FxHashSet, FxHasher};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct ResolveWork {
pub specifier_calls: u64,
pub unique_specifiers: u64,
pub oxc_resolve_calls: u64,
pub canonicalize_calls: u64,
}
impl AddAssign for ResolveWork {
fn add_assign(&mut self, other: Self) {
self.specifier_calls += other.specifier_calls;
self.unique_specifiers += other.unique_specifiers;
self.oxc_resolve_calls += other.oxc_resolve_calls;
self.canonicalize_calls += other.canonicalize_calls;
}
}
struct ModuleScope {
work: ResolveWork,
seen_specifiers: FxHashSet<u64>,
}
thread_local! {
static SCOPE: RefCell<Option<ModuleScope>> = const { RefCell::new(None) };
static SPARE_SET: RefCell<FxHashSet<u64>> = RefCell::new(FxHashSet::default());
}
struct ScopeGuard {
previous: Option<ModuleScope>,
restored: bool,
}
impl ScopeGuard {
fn finish(mut self) -> ResolveWork {
self.restored = true;
let previous = self.previous.take();
let Some(closed) = SCOPE.with(|scope| scope.replace(previous)) else {
return ResolveWork::default();
};
let mut seen = closed.seen_specifiers;
seen.clear();
SPARE_SET.with(|spare| *spare.borrow_mut() = seen);
closed.work
}
}
impl Drop for ScopeGuard {
fn drop(&mut self) {
if !self.restored {
let previous = self.previous.take();
SCOPE.with(|scope| scope.replace(previous));
}
}
}
pub(super) fn in_module_scope<T>(resolve: impl FnOnce() -> T) -> (T, ResolveWork) {
let fresh = ModuleScope {
work: ResolveWork::default(),
seen_specifiers: SPARE_SET.with(|spare| std::mem::take(&mut *spare.borrow_mut())),
};
let guard = ScopeGuard {
previous: SCOPE.with(|scope| scope.replace(Some(fresh))),
restored: false,
};
let output = resolve();
(output, guard.finish())
}
fn with_scope(record: impl FnOnce(&mut ModuleScope)) {
SCOPE.with(|scope| {
if let Some(scope) = scope.borrow_mut().as_mut() {
record(scope);
}
});
}
pub(super) fn note_specifier(specifier: &str, from_style: bool) {
with_scope(|scope| {
scope.work.specifier_calls += 1;
let mut hasher = FxHasher::default();
specifier.hash(&mut hasher);
from_style.hash(&mut hasher);
if scope.seen_specifiers.insert(hasher.finish()) {
scope.work.unique_specifiers += 1;
}
});
}
pub(super) fn note_oxc_resolve() {
with_scope(|scope| scope.work.oxc_resolve_calls += 1);
}
pub(super) fn note_canonicalize(count: u64) {
with_scope(|scope| scope.work.canonicalize_calls += count);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_module_scope_counts_calls_and_distinct_specifiers() {
let ((), work) = in_module_scope(|| {
note_specifier("./a", false);
note_specifier("./a", false);
note_specifier("./a", true);
note_oxc_resolve();
note_canonicalize(2);
});
assert_eq!(
work,
ResolveWork {
specifier_calls: 3,
unique_specifiers: 2,
oxc_resolve_calls: 1,
canonicalize_calls: 2,
}
);
}
#[test]
fn notes_outside_a_scope_are_dropped() {
note_specifier("./a", false);
let ((), work) = in_module_scope(|| note_specifier("./b", false));
assert_eq!(work.specifier_calls, 1);
}
#[test]
fn each_scope_starts_with_an_empty_specifier_set() {
let ((), first) = in_module_scope(|| note_specifier("./a", false));
let ((), second) = in_module_scope(|| note_specifier("./a", false));
assert_eq!(first.unique_specifiers, 1);
assert_eq!(second.unique_specifiers, 1);
}
#[test]
fn a_nested_scope_keeps_its_counts_apart_from_the_outer_scope() {
let (inner, outer) = in_module_scope(|| {
note_specifier("./outer", false);
let ((), inner) = in_module_scope(|| note_specifier("./inner", false));
note_specifier("./outer-2", false);
inner
});
assert_eq!(inner.specifier_calls, 1);
assert_eq!(outer.specifier_calls, 2);
}
}