1use crate::{SymbolId, symbol_flags};
9use rustc_hash::FxHashMap;
10use std::fmt::Write;
11use std::sync::atomic::{AtomicBool, Ordering};
12use tracing::debug;
13
14static DEBUG_ENABLED: AtomicBool = AtomicBool::new(false);
17
18pub fn set_debug_enabled(enabled: bool) {
20 DEBUG_ENABLED.store(enabled, Ordering::SeqCst);
21}
22
23pub fn is_debug_enabled() -> bool {
25 DEBUG_ENABLED.load(Ordering::Relaxed)
26}
27
28#[derive(Debug, Clone)]
30pub struct SymbolDeclarationEvent {
31 pub name: String,
33 pub symbol_id: SymbolId,
35 pub flags_description: String,
37 pub file_name: String,
39 pub is_merge: bool,
41 pub declaration_count: usize,
43}
44
45#[derive(Debug, Clone)]
47pub struct SymbolLookupEvent {
48 pub name: String,
50 pub scope_path: Vec<String>,
52 pub found: bool,
54 pub symbol_id: Option<SymbolId>,
56 pub found_in_file: Option<String>,
58}
59
60#[derive(Debug, Clone)]
62pub struct SymbolMergeEvent {
63 pub name: String,
65 pub symbol_id: SymbolId,
67 pub existing_flags: String,
69 pub new_flags: String,
71 pub combined_flags: String,
73 pub contributing_file: String,
75}
76
77#[derive(Debug, Default)]
79pub struct ModuleResolutionDebugger {
80 pub declaration_events: Vec<SymbolDeclarationEvent>,
82 pub lookup_events: Vec<SymbolLookupEvent>,
84 pub merge_events: Vec<SymbolMergeEvent>,
86 pub symbol_origins: FxHashMap<SymbolId, String>,
88 pub current_file: String,
90}
91
92impl ModuleResolutionDebugger {
93 #[must_use]
95 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn set_current_file(&mut self, file_name: &str) {
101 self.current_file = file_name.to_string();
102 }
103
104 pub fn record_declaration(
106 &mut self,
107 name: &str,
108 symbol_id: SymbolId,
109 flags: u32,
110 declaration_count: usize,
111 is_merge: bool,
112 ) {
113 if !is_debug_enabled() {
114 return;
115 }
116
117 let event = SymbolDeclarationEvent {
118 name: name.to_string(),
119 symbol_id,
120 flags_description: flags_to_string(flags),
121 file_name: self.current_file.clone(),
122 is_merge,
123 declaration_count,
124 };
125
126 if !is_merge && !self.symbol_origins.contains_key(&symbol_id) {
128 self.symbol_origins
129 .insert(symbol_id, self.current_file.clone());
130 }
131
132 if is_debug_enabled() {
133 debug!(
134 "[MODULE_DEBUG] {} symbol '{}' (id={}) with flags [{}] in {} (decls={})",
135 if is_merge { "MERGED" } else { "DECLARED" },
136 event.name,
137 symbol_id.0,
138 event.flags_description,
139 event.file_name,
140 event.declaration_count
141 );
142 }
143
144 self.declaration_events.push(event);
145 }
146
147 pub fn record_merge(
149 &mut self,
150 name: &str,
151 symbol_id: SymbolId,
152 existing_flags: u32,
153 new_flags: u32,
154 combined_flags: u32,
155 ) {
156 if !is_debug_enabled() {
157 return;
158 }
159
160 let event = SymbolMergeEvent {
161 name: name.to_string(),
162 symbol_id,
163 existing_flags: flags_to_string(existing_flags),
164 new_flags: flags_to_string(new_flags),
165 combined_flags: flags_to_string(combined_flags),
166 contributing_file: self.current_file.clone(),
167 };
168
169 debug!(
170 "[MODULE_DEBUG] MERGE '{}' (id={}): [{}] + [{}] = [{}] (from {})",
171 event.name,
172 symbol_id.0,
173 event.existing_flags,
174 event.new_flags,
175 event.combined_flags,
176 event.contributing_file
177 );
178
179 self.merge_events.push(event);
180 }
181
182 pub fn record_lookup(&mut self, name: &str, scope_path: &[String], result: Option<SymbolId>) {
184 if !is_debug_enabled() {
185 return;
186 }
187
188 let lookup_message = match result {
189 Some(id) => format!("FOUND (id={})", id.0),
190 None => "NOT FOUND".to_string(),
191 };
192 let found_in_file = result.and_then(|id| self.symbol_origins.get(&id).cloned());
193
194 let event = SymbolLookupEvent {
195 name: name.to_string(),
196 scope_path: scope_path.to_owned(),
197 found: result.is_some(),
198 symbol_id: result,
199 found_in_file: found_in_file.clone(),
200 };
201
202 debug!(
203 "[MODULE_DEBUG] LOOKUP '{}': scopes=[{}] -> {} (file: {})",
204 event.name,
205 scope_path.join(" -> "),
206 lookup_message,
207 found_in_file.unwrap_or_else(|| "unknown".to_string())
208 );
209
210 self.lookup_events.push(event);
211 }
212
213 #[must_use]
215 pub fn get_summary(&self) -> String {
216 let mut summary = String::new();
217 summary.push_str("=== Module Resolution Debug Summary ===\n\n");
218
219 let _ = writeln!(
220 summary,
221 "Total declarations: {}",
222 self.declaration_events.len()
223 );
224 let _ = writeln!(summary, "Total merges: {}", self.merge_events.len());
225 let _ = writeln!(summary, "Total lookups: {}\n", self.lookup_events.len());
226
227 summary.push_str("Symbol Origins by File:\n");
229 let mut by_file: FxHashMap<String, Vec<SymbolId>> = FxHashMap::default();
230 for (sym_id, file) in &self.symbol_origins {
231 by_file.entry(file.clone()).or_default().push(*sym_id);
232 }
233 for (file, symbols) in &by_file {
234 let _ = writeln!(summary, " {}: {} symbols", file, symbols.len());
235 }
236
237 if !self.merge_events.is_empty() {
239 summary.push_str("\nMerge Operations:\n");
240 for event in &self.merge_events {
241 let _ = writeln!(
242 summary,
243 " {} (id={}): [{}] + [{}] = [{}] from {}",
244 event.name,
245 event.symbol_id.0,
246 event.existing_flags,
247 event.new_flags,
248 event.combined_flags,
249 event.contributing_file
250 );
251 }
252 }
253
254 let failed_lookups: Vec<_> = self.lookup_events.iter().filter(|e| !e.found).collect();
256 if !failed_lookups.is_empty() {
257 summary.push_str("\nFailed Lookups:\n");
258 for event in failed_lookups {
259 let _ = writeln!(
260 summary,
261 " '{}': searched [{}]",
262 event.name,
263 event.scope_path.join(" -> ")
264 );
265 }
266 }
267
268 summary
269 }
270
271 pub fn clear(&mut self) {
273 self.declaration_events.clear();
274 self.lookup_events.clear();
275 self.merge_events.clear();
276 self.symbol_origins.clear();
277 }
278}
279
280#[must_use]
282pub fn flags_to_string(flags: u32) -> String {
283 let mut parts = Vec::new();
284
285 if flags & symbol_flags::FUNCTION_SCOPED_VARIABLE != 0 {
286 parts.push("VAR");
287 }
288 if flags & symbol_flags::BLOCK_SCOPED_VARIABLE != 0 {
289 parts.push("LET/CONST");
290 }
291 if flags & symbol_flags::PROPERTY != 0 {
292 parts.push("PROPERTY");
293 }
294 if flags & symbol_flags::ENUM_MEMBER != 0 {
295 parts.push("ENUM_MEMBER");
296 }
297 if flags & symbol_flags::FUNCTION != 0 {
298 parts.push("FUNCTION");
299 }
300 if flags & symbol_flags::CLASS != 0 {
301 parts.push("CLASS");
302 }
303 if flags & symbol_flags::INTERFACE != 0 {
304 parts.push("INTERFACE");
305 }
306 if flags & symbol_flags::CONST_ENUM != 0 {
307 parts.push("CONST_ENUM");
308 }
309 if flags & symbol_flags::REGULAR_ENUM != 0 {
310 parts.push("ENUM");
311 }
312 if flags & symbol_flags::VALUE_MODULE != 0 {
313 parts.push("VALUE_MODULE");
314 }
315 if flags & symbol_flags::NAMESPACE_MODULE != 0 {
316 parts.push("NAMESPACE");
317 }
318 if flags & symbol_flags::TYPE_LITERAL != 0 {
319 parts.push("TYPE_LITERAL");
320 }
321 if flags & symbol_flags::OBJECT_LITERAL != 0 {
322 parts.push("OBJECT_LITERAL");
323 }
324 if flags & symbol_flags::METHOD != 0 {
325 parts.push("METHOD");
326 }
327 if flags & symbol_flags::CONSTRUCTOR != 0 {
328 parts.push("CONSTRUCTOR");
329 }
330 if flags & symbol_flags::GET_ACCESSOR != 0 {
331 parts.push("GETTER");
332 }
333 if flags & symbol_flags::SET_ACCESSOR != 0 {
334 parts.push("SETTER");
335 }
336 if flags & symbol_flags::TYPE_PARAMETER != 0 {
337 parts.push("TYPE_PARAM");
338 }
339 if flags & symbol_flags::TYPE_ALIAS != 0 {
340 parts.push("TYPE_ALIAS");
341 }
342 if flags & symbol_flags::ALIAS != 0 {
343 parts.push("ALIAS");
344 }
345 if flags & symbol_flags::EXPORT_VALUE != 0 {
346 parts.push("EXPORT");
347 }
348
349 if parts.is_empty() {
350 "NONE".to_string()
351 } else {
352 parts.join("|")
353 }
354}
355
356#[cfg(test)]
357#[path = "../../tests/module_resolution_debug.rs"]
358mod tests;