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