Skip to main content

Library/Fn/OXC/
Compiler.rs

1//! OXC TypeScript Compiler
2//!
3//! This module provides the main compiler orchestration for TypeScript to
4//! JavaScript compilation using the OXC parser, transformer, and codegen.
5//!
6//! DIAGNOSTIC LOGGING:
7//! - Full lifecycle tracking from parse through codegen
8//! - Memory allocation/deallocation timestamps
9//! - Pointer addresses for debugging use-after-free
10
11use std::{
12	path::Path,
13	sync::{
14		Arc,
15		Mutex,
16		atomic::{AtomicUsize, Ordering},
17	},
18	time::{Duration, Instant},
19};
20
21use tracing::{debug, info, trace, warn};
22
23use super::{
24	Codegen::{self, CodegenConfig},
25	Parser::{self, ParserConfig},
26	Transformer::{self, TransformerConfig},
27};
28
29static COMPILE_ID:AtomicUsize = AtomicUsize::new(0);
30
31/// Compiler metrics
32#[derive(Debug, Default)]
33pub struct CompilerMetrics {
34	/// Number of files compiled
35	pub count:usize,
36
37	/// Total elapsed time
38	pub elapsed:Duration,
39
40	/// Number of errors
41	pub error:usize,
42}
43
44/// OXC-based TypeScript compiler
45pub struct Compiler {
46	/// Compiler configuration
47	pub config:crate::Struct::SWC::CompilerConfig,
48
49	/// Compiler metrics
50	pub outlook:Arc<Mutex<CompilerMetrics>>,
51}
52
53impl Compiler {
54	/// Create a new OXC compiler
55	pub fn new(config:crate::Struct::SWC::CompilerConfig) -> Self {
56		Self { config, outlook:Arc::new(Mutex::new(CompilerMetrics::default())) }
57	}
58
59	/// Compile a TypeScript file and return the output
60	///
61	/// # Arguments
62	/// * `file_path` - Path to the source file
63	/// * `input` - TypeScript source code
64	///
65	/// # Returns
66	/// Result containing the output file path
67	#[tracing::instrument(skip(self, input))]
68	pub fn compile_file(&self, file_path:&str, input:String) -> anyhow::Result<String> {
69		let compile_id = COMPILE_ID.fetch_add(1, Ordering::SeqCst);
70
71		let begin = Instant::now();
72
73		info!("[Compile #{compile_id}] Starting compilation of: {}", file_path);
74
75		trace!("[Compile #{compile_id}] Input size: {} bytes", input.len());
76
77		// All compilation steps happen in one scope to ensure allocator stays alive
78		let (codegen_result, output_path) = {
79			info!("[Compile #{compile_id}] Step 1: Parsing TypeScript source");
80
81			let parser_config = self.get_parser_config();
82
83			let mut parse_result = Parser::parse(&input, file_path, &parser_config)
84				.map_err(|errors| anyhow::anyhow!("Parse errors: {:?}", errors))?;
85
86			info!("[Compile #{compile_id}] Step 1 complete: Parsed {} successfully", file_path);
87
88			debug!(
89				"[Compile #{compile_id}] ParseResult.allocator address: {:p}",
90				&parse_result.allocator
91			);
92
93			debug!(
94				"[Compile #{compile_id}] ParseResult.program address: {:p}",
95				&parse_result.program
96			);
97
98			trace!(
99				"[Compile #{compile_id}] AST body pointer: {:p}",
100				parse_result.program.body.as_ptr()
101			);
102
103			// Transform - borrow program mutably from parse_result
104			info!("[Compile #{compile_id}] Step 2: Transforming AST");
105
106			let transformer_config = self.get_transformer_config();
107
108			// SAFETY: We borrow program with 'static lifetime from parse_result.
109			// This is safe because:
110			// 1. parse_result stays in scope for the entire inner block
111			// 2. The program and allocator are dropped together when parse_result is
112			//    dropped
113			// 3. We never use program after parse_result is dropped
114			let program = unsafe {
115				std::mem::transmute::<&mut oxc_ast::ast::Program<'static>, &mut oxc_ast::ast::Program<'_>>(
116					&mut parse_result.program,
117				)
118			};
119
120			let source_type = oxc_span::SourceType::from_path(file_path).unwrap_or(oxc_span::SourceType::ts());
121
122			debug!(
123				"[Compile #{compile_id}] Transformer config: target={}, module={}",
124				transformer_config.target, transformer_config.module_format
125			);
126
127			Transformer::transform(&parse_result.allocator, program, file_path, source_type, &transformer_config)
128				.map_err(|errors| anyhow::anyhow!("Transform errors: {:?}", errors))?;
129
130			info!(
131				"[Compile #{compile_id}] Step 2 complete: Transformed {} successfully",
132				file_path
133			);
134
135			trace!(
136				"[Compile #{compile_id}] Program after transform - body pointer: {:p}",
137				program.body.as_ptr()
138			);
139
140			// Generate code
141			info!("[Compile #{compile_id}] Step 3: Generating code");
142
143			let codegen_config = self.get_codegen_config();
144
145			let codegen_result = Codegen::codegen(&parse_result.allocator, program, source_type, &codegen_config)
146				.map_err(|e| anyhow::anyhow!("Codegen error: {}", e))?;
147
148			info!(
149				"[Compile #{compile_id}] Step 3 complete: Generated {} bytes",
150				codegen_result.code.len()
151			);
152
153			let output_path = Path::new(file_path).with_extension("js");
154
155			(codegen_result, output_path)
156		}; // parse_result dropped here - allocator and AST freed together
157
158		// Write output to file (outside the scope)
159		if let Some(parent) = output_path.parent() {
160			std::fs::create_dir_all(parent)?;
161		}
162
163		let write_start = Instant::now();
164
165		std::fs::write(&output_path, &codegen_result.code)?;
166
167		trace!("[Compile #{compile_id}] File write completed in {:?}", write_start.elapsed());
168
169		let elapsed = begin.elapsed();
170
171		{
172			let mut outlook = self.outlook.lock().unwrap();
173
174			outlook.count += 1;
175
176			outlook.elapsed += elapsed;
177		}
178
179		info!("[Compile #{compile_id}] COMPLETE: Compiled {} in {:?}", file_path, elapsed);
180
181		Ok(output_path.to_string_lossy().to_string())
182	}
183
184	/// Compile a TypeScript file and write output to a specific path
185	///
186	/// # Arguments
187	/// * `file_path` - Path to the source file
188	/// * `input` - TypeScript source code
189	/// * `output_path` - Path for the output file
190	/// * `use_define_for_class_fields` - VSCode compatibility setting
191	///
192	/// # Returns
193	/// Result containing the output file path
194	#[tracing::instrument(skip(self, input))]
195	pub fn compile_file_to(
196		&self,
197
198		file_path:&str,
199
200		input:String,
201
202		output_path:&Path,
203
204		use_define_for_class_fields:bool,
205	) -> anyhow::Result<String> {
206		let compile_id = COMPILE_ID.fetch_add(1, Ordering::SeqCst);
207
208		let begin = Instant::now();
209
210		info!(
211			"[Compile #{compile_id}] START compile_file_to: {} -> {}",
212			file_path,
213			output_path.display()
214		);
215
216		trace!(
217			"[Compile #{compile_id}] Input size: {} bytes, use_define_for_class_fields={}",
218			input.len(),
219			use_define_for_class_fields
220		);
221
222		// CRITICAL FIX: Wrap entire compilation in its own scope to ensure
223		// the allocator is dropped before the next file is processed.
224		// This prevents OXC internal state corruption when processing
225		// multiple files sequentially.
226		let codegen_result = {
227			// Parse the TypeScript source
228			info!("[Compile #{compile_id}] Step 1/4: Parsing {}", file_path);
229
230			let parse_start = Instant::now();
231
232			let parser_config = self.get_parser_config();
233
234			let mut parse_result = Parser::parse(&input, file_path, &parser_config)
235				.map_err(|errors| anyhow::anyhow!("Parse errors: {:?}", errors))?;
236
237			info!(
238				"[Compile #{compile_id}] Step 1/4 complete: Parse in {:?}",
239				parse_start.elapsed()
240			);
241
242			debug!(
243				"[Compile #{compile_id}] Memory addresses: allocator={:p}, program={:p}",
244				&parse_result.allocator, &parse_result.program
245			);
246
247			trace!(
248				"[Compile #{compile_id}] AST body slice: ptr={:p}, len={}",
249				parse_result.program.body.as_ptr(),
250				parse_result.program.body.len()
251			);
252
253			// Transform the AST - borrow program mutably, don't move it out
254			info!("[Compile #{compile_id}] Step 2/4: Transforming");
255
256			let transform_start = Instant::now();
257
258			let mut transformer_config = self.get_transformer_config();
259
260			transformer_config.use_define_for_class_fields = use_define_for_class_fields;
261
262			// SAFETY: We borrow program with 'static lifetime from parse_result.
263			// This is safe because:
264			// 1. parse_result stays in scope for the entire inner block
265			// 2. The program and allocator are dropped together when parse_result is
266			// dropped
267			// 3. We never use program after parse_result is dropped
268			let program = unsafe {
269				std::mem::transmute::<&mut oxc_ast::ast::Program<'static>, &mut oxc_ast::ast::Program<'_>>(
270					&mut parse_result.program,
271				)
272			};
273
274			let source_type = oxc_span::SourceType::from_path(file_path).unwrap_or(oxc_span::SourceType::ts());
275
276			Transformer::transform(&parse_result.allocator, program, file_path, source_type, &transformer_config)
277				.map_err(|errors| anyhow::anyhow!("Transform errors: {:?}", errors))?;
278
279			info!(
280				"[Compile #{compile_id}] Step 2/4 complete: Transform in {:?}",
281				transform_start.elapsed()
282			);
283
284			trace!(
285				"[Compile #{compile_id}] Program after transform - body ptr: {:p}",
286				program.body.as_ptr()
287			);
288
289			// Generate code - parse_result still alive
290			info!("[Compile #{compile_id}] Step 3/4: Codegen");
291
292			let codegen_start = Instant::now();
293
294			let codegen_config = self.get_codegen_config();
295
296			let codegen_result = Codegen::codegen(&parse_result.allocator, program, source_type, &codegen_config)
297				.map_err(|e| anyhow::anyhow!("Codegen error: {}", e))?;
298
299			info!(
300				"[Compile #{compile_id}] Step 3/4 complete: Codegen in {:?}, output={}(bytes)",
301				codegen_start.elapsed(),
302				codegen_result.code.len()
303			);
304
305			// Force drop of parse_result to ensure allocator is freed
306			// before returning from this scope
307			let _ = program;
308
309			// parse_result will be dropped at end of scope
310			codegen_result
311		}; // parse_result and allocator dropped here - critical for preventing segfault
312
313		// Create parent directories if they don't exist
314		if let Some(parent) = output_path.parent() {
315			std::fs::create_dir_all(parent)?;
316		}
317
318		// Write to the specified output path
319		let write_start = Instant::now();
320
321		std::fs::write(output_path, &codegen_result.code)?;
322
323		trace!("[Compile #{compile_id}] Step 4/4: File write in {:?}", write_start.elapsed());
324
325		let elapsed = begin.elapsed();
326
327		{
328			let mut outlook = self.outlook.lock().unwrap();
329
330			outlook.count += 1;
331
332			outlook.elapsed += elapsed;
333		}
334
335		info!(
336			"[Compile #{compile_id}] COMPLETE: {} -> {} in {:?}",
337			file_path,
338			output_path.display(),
339			elapsed
340		);
341
342		Ok(output_path.to_string_lossy().to_string())
343	}
344
345	/// Get parser configuration from compiler config
346	fn get_parser_config(&self) -> ParserConfig {
347		ParserConfig::new(
348			self.config.Target.clone(),
349			self.config.jsx(),
350			true, // decorators always enabled for TypeScript
351			true, // typescript
352		)
353	}
354
355	/// Get transformer configuration from compiler config
356	fn get_transformer_config(&self) -> TransformerConfig {
357		TransformerConfig::new(
358			self.config.Target.clone(),
359			self.config.module_format(),
360			self.config.EmitDecoratorsMetadata,
361			false, // default: VSCode compatible
362			self.config.jsx(),
363			self.config.TreeShaking,
364			self.config.Minify,
365		)
366	}
367
368	/// Get codegen configuration from compiler config
369	fn get_codegen_config(&self) -> CodegenConfig {
370		CodegenConfig::new(
371			self.config.Minify,
372			false, // source maps disabled by default
373			String::new(),
374			false, // comments disabled by default
375		)
376	}
377}