use crate::{SymbolId, symbol_flags};
use rustc_hash::FxHashMap;
use std::fmt::Write;
use std::sync::atomic::{AtomicBool, Ordering};
use tracing::debug;
static DEBUG_ENABLED: AtomicBool = AtomicBool::new(false);
pub fn set_debug_enabled(enabled: bool) {
DEBUG_ENABLED.store(enabled, Ordering::SeqCst);
}
pub fn is_debug_enabled() -> bool {
DEBUG_ENABLED.load(Ordering::Relaxed)
}
#[derive(Debug, Clone)]
pub struct SymbolDeclarationEvent {
pub name: String,
pub symbol_id: SymbolId,
pub flags_description: String,
pub file_name: String,
pub is_merge: bool,
pub declaration_count: usize,
}
#[derive(Debug, Clone)]
pub struct SymbolLookupEvent {
pub name: String,
pub scope_path: Vec<String>,
pub found: bool,
pub symbol_id: Option<SymbolId>,
pub found_in_file: Option<String>,
}
#[derive(Debug, Clone)]
pub struct SymbolMergeEvent {
pub name: String,
pub symbol_id: SymbolId,
pub existing_flags: String,
pub new_flags: String,
pub combined_flags: String,
pub contributing_file: String,
}
#[derive(Debug, Default)]
pub struct ModuleResolutionDebugger {
pub declaration_events: Vec<SymbolDeclarationEvent>,
pub lookup_events: Vec<SymbolLookupEvent>,
pub merge_events: Vec<SymbolMergeEvent>,
pub symbol_origins: FxHashMap<SymbolId, String>,
pub current_file: String,
}
impl ModuleResolutionDebugger {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn set_current_file(&mut self, file_name: &str) {
self.current_file = file_name.to_string();
}
pub fn record_declaration(
&mut self,
name: &str,
symbol_id: SymbolId,
flags: u32,
declaration_count: usize,
is_merge: bool,
) {
if !is_debug_enabled() {
return;
}
let event = SymbolDeclarationEvent {
name: name.to_string(),
symbol_id,
flags_description: flags_to_string(flags),
file_name: self.current_file.clone(),
is_merge,
declaration_count,
};
if !is_merge && !self.symbol_origins.contains_key(&symbol_id) {
self.symbol_origins
.insert(symbol_id, self.current_file.clone());
}
if is_debug_enabled() {
debug!(
"[MODULE_DEBUG] {} symbol '{}' (id={}) with flags [{}] in {} (decls={})",
if is_merge { "MERGED" } else { "DECLARED" },
event.name,
symbol_id.0,
event.flags_description,
event.file_name,
event.declaration_count
);
}
self.declaration_events.push(event);
}
pub fn record_merge(
&mut self,
name: &str,
symbol_id: SymbolId,
existing_flags: u32,
new_flags: u32,
combined_flags: u32,
) {
if !is_debug_enabled() {
return;
}
let event = SymbolMergeEvent {
name: name.to_string(),
symbol_id,
existing_flags: flags_to_string(existing_flags),
new_flags: flags_to_string(new_flags),
combined_flags: flags_to_string(combined_flags),
contributing_file: self.current_file.clone(),
};
debug!(
"[MODULE_DEBUG] MERGE '{}' (id={}): [{}] + [{}] = [{}] (from {})",
event.name,
symbol_id.0,
event.existing_flags,
event.new_flags,
event.combined_flags,
event.contributing_file
);
self.merge_events.push(event);
}
pub fn record_lookup(&mut self, name: &str, scope_path: &[String], result: Option<SymbolId>) {
if !is_debug_enabled() {
return;
}
let lookup_message = match result {
Some(id) => format!("FOUND (id={})", id.0),
None => "NOT FOUND".to_string(),
};
let found_in_file = result.and_then(|id| self.symbol_origins.get(&id).cloned());
let event = SymbolLookupEvent {
name: name.to_string(),
scope_path: scope_path.to_owned(),
found: result.is_some(),
symbol_id: result,
found_in_file: found_in_file.clone(),
};
debug!(
"[MODULE_DEBUG] LOOKUP '{}': scopes=[{}] -> {} (file: {})",
event.name,
scope_path.join(" -> "),
lookup_message,
found_in_file.unwrap_or_else(|| "unknown".to_string())
);
self.lookup_events.push(event);
}
#[must_use]
pub fn get_summary(&self) -> String {
let mut summary = String::new();
summary.push_str("=== Module Resolution Debug Summary ===\n\n");
let _ = writeln!(
summary,
"Total declarations: {}",
self.declaration_events.len()
);
let _ = writeln!(summary, "Total merges: {}", self.merge_events.len());
let _ = writeln!(summary, "Total lookups: {}\n", self.lookup_events.len());
summary.push_str("Symbol Origins by File:\n");
let mut by_file: FxHashMap<String, Vec<SymbolId>> = FxHashMap::default();
for (sym_id, file) in &self.symbol_origins {
by_file.entry(file.clone()).or_default().push(*sym_id);
}
for (file, symbols) in &by_file {
let _ = writeln!(summary, " {}: {} symbols", file, symbols.len());
}
if !self.merge_events.is_empty() {
summary.push_str("\nMerge Operations:\n");
for event in &self.merge_events {
let _ = writeln!(
summary,
" {} (id={}): [{}] + [{}] = [{}] from {}",
event.name,
event.symbol_id.0,
event.existing_flags,
event.new_flags,
event.combined_flags,
event.contributing_file
);
}
}
let failed_lookups: Vec<_> = self.lookup_events.iter().filter(|e| !e.found).collect();
if !failed_lookups.is_empty() {
summary.push_str("\nFailed Lookups:\n");
for event in failed_lookups {
let _ = writeln!(
summary,
" '{}': searched [{}]",
event.name,
event.scope_path.join(" -> ")
);
}
}
summary
}
pub fn clear(&mut self) {
self.declaration_events.clear();
self.lookup_events.clear();
self.merge_events.clear();
self.symbol_origins.clear();
}
}
#[must_use]
pub fn flags_to_string(flags: u32) -> String {
let mut parts = Vec::new();
if flags & symbol_flags::FUNCTION_SCOPED_VARIABLE != 0 {
parts.push("VAR");
}
if flags & symbol_flags::BLOCK_SCOPED_VARIABLE != 0 {
parts.push("LET/CONST");
}
if flags & symbol_flags::PROPERTY != 0 {
parts.push("PROPERTY");
}
if flags & symbol_flags::ENUM_MEMBER != 0 {
parts.push("ENUM_MEMBER");
}
if flags & symbol_flags::FUNCTION != 0 {
parts.push("FUNCTION");
}
if flags & symbol_flags::CLASS != 0 {
parts.push("CLASS");
}
if flags & symbol_flags::INTERFACE != 0 {
parts.push("INTERFACE");
}
if flags & symbol_flags::CONST_ENUM != 0 {
parts.push("CONST_ENUM");
}
if flags & symbol_flags::REGULAR_ENUM != 0 {
parts.push("ENUM");
}
if flags & symbol_flags::VALUE_MODULE != 0 {
parts.push("VALUE_MODULE");
}
if flags & symbol_flags::NAMESPACE_MODULE != 0 {
parts.push("NAMESPACE");
}
if flags & symbol_flags::TYPE_LITERAL != 0 {
parts.push("TYPE_LITERAL");
}
if flags & symbol_flags::OBJECT_LITERAL != 0 {
parts.push("OBJECT_LITERAL");
}
if flags & symbol_flags::METHOD != 0 {
parts.push("METHOD");
}
if flags & symbol_flags::CONSTRUCTOR != 0 {
parts.push("CONSTRUCTOR");
}
if flags & symbol_flags::GET_ACCESSOR != 0 {
parts.push("GETTER");
}
if flags & symbol_flags::SET_ACCESSOR != 0 {
parts.push("SETTER");
}
if flags & symbol_flags::TYPE_PARAMETER != 0 {
parts.push("TYPE_PARAM");
}
if flags & symbol_flags::TYPE_ALIAS != 0 {
parts.push("TYPE_ALIAS");
}
if flags & symbol_flags::ALIAS != 0 {
parts.push("ALIAS");
}
if flags & symbol_flags::EXPORT_VALUE != 0 {
parts.push("EXPORT");
}
if parts.is_empty() {
"NONE".to_string()
} else {
parts.join("|")
}
}
#[cfg(test)]
#[path = "../tests/module_resolution_debug.rs"]
mod tests;