fallow_graph/resolve/
work.rs1use std::cell::RefCell;
14use std::hash::{Hash, Hasher};
15use std::ops::AddAssign;
16
17use rustc_hash::{FxHashSet, FxHasher};
18
19#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
21pub struct ResolveWork {
22 pub specifier_calls: u64,
26 pub unique_specifiers: u64,
32 pub oxc_resolve_calls: u64,
34 pub canonicalize_calls: u64,
37}
38
39impl AddAssign for ResolveWork {
40 fn add_assign(&mut self, other: Self) {
41 self.specifier_calls += other.specifier_calls;
42 self.unique_specifiers += other.unique_specifiers;
43 self.oxc_resolve_calls += other.oxc_resolve_calls;
44 self.canonicalize_calls += other.canonicalize_calls;
45 }
46}
47
48struct ModuleScope {
49 work: ResolveWork,
50 seen_specifiers: FxHashSet<u64>,
51}
52
53thread_local! {
54 static SCOPE: RefCell<Option<ModuleScope>> = const { RefCell::new(None) };
55 static SPARE_SET: RefCell<FxHashSet<u64>> = RefCell::new(FxHashSet::default());
58}
59
60struct ScopeGuard {
62 previous: Option<ModuleScope>,
63 restored: bool,
64}
65
66impl ScopeGuard {
67 fn finish(mut self) -> ResolveWork {
68 self.restored = true;
69 let previous = self.previous.take();
70 let Some(closed) = SCOPE.with(|scope| scope.replace(previous)) else {
71 return ResolveWork::default();
72 };
73 let mut seen = closed.seen_specifiers;
74 seen.clear();
75 SPARE_SET.with(|spare| *spare.borrow_mut() = seen);
76 closed.work
77 }
78}
79
80impl Drop for ScopeGuard {
81 fn drop(&mut self) {
82 if !self.restored {
83 let previous = self.previous.take();
84 SCOPE.with(|scope| scope.replace(previous));
85 }
86 }
87}
88
89pub(super) fn in_module_scope<T>(resolve: impl FnOnce() -> T) -> (T, ResolveWork) {
91 let fresh = ModuleScope {
92 work: ResolveWork::default(),
93 seen_specifiers: SPARE_SET.with(|spare| std::mem::take(&mut *spare.borrow_mut())),
94 };
95 let guard = ScopeGuard {
96 previous: SCOPE.with(|scope| scope.replace(Some(fresh))),
97 restored: false,
98 };
99 let output = resolve();
100 (output, guard.finish())
101}
102
103fn with_scope(record: impl FnOnce(&mut ModuleScope)) {
104 SCOPE.with(|scope| {
105 if let Some(scope) = scope.borrow_mut().as_mut() {
106 record(scope);
107 }
108 });
109}
110
111pub(super) fn note_specifier(specifier: &str, from_style: bool) {
113 with_scope(|scope| {
114 scope.work.specifier_calls += 1;
115 let mut hasher = FxHasher::default();
116 specifier.hash(&mut hasher);
117 from_style.hash(&mut hasher);
118 if scope.seen_specifiers.insert(hasher.finish()) {
119 scope.work.unique_specifiers += 1;
120 }
121 });
122}
123
124pub(super) fn note_oxc_resolve() {
126 with_scope(|scope| scope.work.oxc_resolve_calls += 1);
127}
128
129pub(super) fn note_canonicalize(count: u64) {
131 with_scope(|scope| scope.work.canonicalize_calls += count);
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn a_module_scope_counts_calls_and_distinct_specifiers() {
140 let ((), work) = in_module_scope(|| {
141 note_specifier("./a", false);
142 note_specifier("./a", false);
143 note_specifier("./a", true);
144 note_oxc_resolve();
145 note_canonicalize(2);
146 });
147 assert_eq!(
148 work,
149 ResolveWork {
150 specifier_calls: 3,
151 unique_specifiers: 2,
152 oxc_resolve_calls: 1,
153 canonicalize_calls: 2,
154 }
155 );
156 }
157
158 #[test]
159 fn notes_outside_a_scope_are_dropped() {
160 note_specifier("./a", false);
161 let ((), work) = in_module_scope(|| note_specifier("./b", false));
162 assert_eq!(work.specifier_calls, 1);
163 }
164
165 #[test]
166 fn each_scope_starts_with_an_empty_specifier_set() {
167 let ((), first) = in_module_scope(|| note_specifier("./a", false));
168 let ((), second) = in_module_scope(|| note_specifier("./a", false));
169 assert_eq!(first.unique_specifiers, 1);
170 assert_eq!(second.unique_specifiers, 1);
171 }
172
173 #[test]
174 fn a_nested_scope_keeps_its_counts_apart_from_the_outer_scope() {
175 let (inner, outer) = in_module_scope(|| {
176 note_specifier("./outer", false);
177 let ((), inner) = in_module_scope(|| note_specifier("./inner", false));
178 note_specifier("./outer-2", false);
179 inner
180 });
181 assert_eq!(inner.specifier_calls, 1);
182 assert_eq!(outer.specifier_calls, 2);
183 }
184}