use std::fmt::Write as FmtWrite;
use crate::ts_syn::abi::{MacroContextIR, MacroResult, TargetIR};
#[cfg(not(target_arch = "wasm32"))]
mod fs_log {
use std::io::Write;
use std::path::PathBuf;
use std::sync::LazyLock;
static LOG_PATH: LazyLock<PathBuf> = LazyLock::new(|| {
let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
let mut dir = cwd.as_path();
loop {
let candidate = dir.join(".macroforge");
if candidate.is_dir() {
return candidate.join("debug.log");
}
match dir.parent() {
Some(parent) => dir = parent,
None => break,
}
}
let fallback = cwd.join(".macroforge");
let _ = std::fs::create_dir_all(&fallback);
fallback.join("debug.log")
});
pub fn write(line: &str) {
let _ = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(LOG_PATH.as_path())
.and_then(|mut f| f.write_all(line.as_bytes()));
}
pub fn clear() {
let _ = std::fs::write(LOG_PATH.as_path(), "");
}
}
fn timestamp() -> String {
#[cfg(not(target_arch = "wasm32"))]
{
let now = std::time::SystemTime::now();
let dur = now
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
let secs = dur.as_secs();
let millis = dur.subsec_millis();
format!("{secs}.{millis:03}")
}
#[cfg(target_arch = "wasm32")]
{
"wasm".to_string()
}
}
pub fn log(tag: &str, msg: &str) {
let line = format!("[{}] [{}] {}\n", timestamp(), tag, msg);
#[cfg(not(target_arch = "wasm32"))]
{
fs_log::write(&line);
}
#[cfg(target_arch = "wasm32")]
{
eprintln!("{}", line.trim_end());
}
}
pub fn log_ctx(tag: &str, ctx: &MacroContextIR) {
let target_kind = match &ctx.target {
TargetIR::Class(_) => "class",
TargetIR::Interface(_) => "interface",
TargetIR::Enum(_) => "enum",
TargetIR::TypeAlias(_) => "type_alias",
_ => "other",
};
let field_count = match &ctx.target {
TargetIR::Class(c) => c.fields.len(),
TargetIR::Interface(i) => i.fields.len(),
TargetIR::Enum(e) => e.variants.len(),
_ => 0,
};
let mut buf = String::new();
let _ = write!(
buf,
"ctx {{ macro: {}::{}, file: {}, target: {} ({} fields), span: {}-{} }}",
ctx.module_path,
ctx.macro_name,
ctx.file_name,
target_kind,
field_count,
ctx.decorator_span.start,
ctx.decorator_span.end,
);
log(tag, &buf);
}
pub fn log_result(tag: &str, result: &MacroResult) {
let mut buf = String::new();
let _ = write!(
buf,
"result {{ runtime_patches: {}, type_patches: {}, tokens: {}, diagnostics: {} }}",
result.runtime_patches.len(),
result.type_patches.len(),
result
.tokens
.as_ref()
.map(|t| t.len().to_string())
.unwrap_or_else(|| "None".to_string()),
result.diagnostics.len(),
);
for diag in &result.diagnostics {
let _ = write!(buf, "\n [{:?}] {}", diag.level, diag.message);
if let Some(help) = &diag.help {
let _ = write!(buf, " (help: {help})");
}
}
log(tag, &buf);
}
pub fn clear() {
#[cfg(not(target_arch = "wasm32"))]
{
fs_log::clear();
}
}
#[macro_export]
macro_rules! debug_log {
($tag:expr, $($arg:tt)*) => {
$crate::debug::log($tag, &format!($($arg)*))
};
}