Skip to main content

intlayer_swc_plugin/
logger.rs

1//! Build-time reporting.
2//!
3//! Mirrors what the Babel/Vite optimize pipeline prints on the JavaScript side
4//! so a Next.js build driven by this plugin can be inspected the same way.
5//! Output is opt-in through the `logLevel` plugin option and goes to stdout,
6//! which both the Wasm host and a native embedder capture.
7
8use crate::config::LogLevel;
9use swc_core::ecma::ast::Program;
10
11/// Prefix every line carries so plugin output is greppable in a build log.
12const LOG_PREFIX: &str = "[intlayer/swc]";
13
14/// Forces the plugin to report at [`LogLevel::Debug`] whatever the `logLevel`
15/// option says, so the emitted code of every transformed file can be inspected
16/// without threading a config change through the JavaScript side.
17///
18/// Development aid only — set back to `false` before publishing: it prints the
19/// full output of every file the transform touches.
20pub static DEBUG_LOG: bool = false;
21
22/// Reports what the transform did, at the verbosity the user asked for.
23#[derive(Debug, Clone, Copy)]
24pub struct Logger {
25    level: LogLevel,
26}
27
28impl Logger {
29    /// Builds a logger for the given level.
30    pub fn new(level: LogLevel) -> Self {
31        Self { level }
32    }
33
34    /// Whether anything at all will be printed.
35    pub fn is_enabled(&self) -> bool {
36        self.level > LogLevel::Off
37    }
38
39    /// Whether the emitted code and per-file skip decisions are printed.
40    pub fn is_debug(&self) -> bool {
41        self.level >= LogLevel::Debug
42    }
43
44    /// Prints a line at `info` verbosity.
45    pub fn info(&self, message: impl AsRef<str>) {
46        if self.level >= LogLevel::Info {
47            println!("{} {}", LOG_PREFIX, message.as_ref());
48        }
49    }
50
51    /// Prints a line at `debug` verbosity.
52    pub fn debug(&self, message: impl AsRef<str>) {
53        if self.is_debug() {
54            println!("{} {}", LOG_PREFIX, message.as_ref());
55        }
56    }
57
58    /// Reports the outcome of a transformed file: how many dictionary imports
59    /// were injected, how many call sites were rewritten, and how many content
60    /// field accesses were renamed. At `debug` verbosity the emitted code
61    /// follows.
62    pub fn report_file(&self, file_path: &str, summary: &TransformSummary, program: &Program) {
63        if !self.is_enabled() {
64            return;
65        }
66
67        if !summary.changed_anything() {
68            self.debug(format!("{}: unchanged", file_path));
69            return;
70        }
71
72        self.info(format!(
73            "{}: {} static import(s), {} dynamic import(s), {} field rename(s)",
74            file_path, summary.static_imports, summary.dynamic_imports, summary.renamed_fields
75        ));
76
77        if self.is_debug() {
78            self.debug(format!(
79                "{}: output\n{}",
80                file_path,
81                program_to_code(program)
82            ));
83        }
84    }
85}
86
87/// Counters describing what the transform changed in a single file.
88#[derive(Debug, Default, Clone, Copy)]
89pub struct TransformSummary {
90    /// Number of injected static dictionary imports.
91    pub static_imports: usize,
92    /// Number of injected dynamic / fetch loader imports.
93    pub dynamic_imports: usize,
94    /// Number of content field accesses rewritten to their short alias.
95    pub renamed_fields: usize,
96}
97
98impl TransformSummary {
99    /// Whether the transform touched the file at all.
100    pub fn changed_anything(&self) -> bool {
101        self.static_imports > 0 || self.dynamic_imports > 0 || self.renamed_fields > 0
102    }
103}
104
105/// Emits a `Program` AST back to JavaScript/TypeScript source code as a `String`.
106/// Used exclusively for debug logging.
107#[cfg(any(test, feature = "debug-output"))]
108pub fn program_to_code(program: &Program) -> String {
109    use swc_core::{
110        common::{sync::Lrc, SourceMap},
111        ecma::codegen::{text_writer::JsWriter, Config as CodegenConfig, Emitter},
112    };
113
114    let source_map = Lrc::new(SourceMap::default());
115    let mut buffer = vec![];
116    {
117        let writer = JsWriter::new(source_map.clone(), "\n", &mut buffer, None);
118        let mut emitter = Emitter {
119            cfg: CodegenConfig::default(),
120            cm: source_map.clone(),
121            comments: None,
122            wr: writer,
123        };
124        let _ = emitter.emit_program(program);
125    }
126    String::from_utf8_lossy(&buffer).into_owned()
127}
128
129/// Stand-in used when the code generator is not compiled in (see the
130/// `debug-output` Cargo feature), so debug logs explain how to get the output.
131#[cfg(not(any(test, feature = "debug-output")))]
132pub fn program_to_code(_program: &Program) -> String {
133    String::from("(emitted code not available: build with `--features plugin,debug-output`)")
134}