Skip to main content

Rest/Fn/OXC/
Transformer.rs

1//! OXC TypeScript Transformer module
2//!
3//! This module provides TypeScript to JavaScript transformation using the OXC
4//! transformer. It handles TypeScript type stripping, decorator transformation,
5//! and ECMAScript version transpilation.
6//!
7//! DIAGNOSTIC LOGGING:
8//! - Tracks transformer lifecycle and memory access
9//! - Logs allocator addresses to detect use-after-free
10
11use std::{
12	path::Path,
13	sync::atomic::{AtomicUsize, Ordering},
14};
15
16use oxc_allocator::Allocator;
17use oxc_ast::ast::Program;
18use oxc_semantic::SemanticBuilder;
19use oxc_span::SourceType;
20use oxc_transformer::{
21	CompilerAssumptions,
22	EnvOptions,
23	JsxOptions,
24	JsxRuntime,
25	TransformOptions,
26	Transformer,
27	TypeScriptOptions,
28};
29use tracing::{debug, info, trace, warn};
30
31/// Transformer configuration options
32#[derive(Debug, Clone)]
33pub struct TransformerConfig {
34	/// Target ECMAScript version (e.g., "es2024")
35	pub target:String,
36	/// Module format (commonjs, esmodule, etc.)
37	pub module_format:String,
38	/// Whether to emit decorator metadata
39	pub emit_decorator_metadata:bool,
40	/// Whether to use define for class fields (VSCode compatibility)
41	pub use_define_for_class_fields:bool,
42	/// Whether to support JSX
43	pub jsx:bool,
44	/// Whether to enable tree-shaking
45	pub tree_shaking:bool,
46	/// Whether to enable minification
47	pub minify:bool,
48}
49
50impl Default for TransformerConfig {
51	fn default() -> Self {
52		Self {
53			target:"es2024".to_string(),
54			module_format:"commonjs".to_string(),
55			emit_decorator_metadata:true,
56			use_define_for_class_fields:false,
57			jsx:false,
58			tree_shaking:false,
59			minify:false,
60		}
61	}
62}
63
64impl TransformerConfig {
65	/// Create a new transformer configuration
66	pub fn new(
67		target:String,
68		_module_format:String,
69		_emit_decorator_metadata:bool,
70		use_define_for_class_fields:bool,
71		jsx:bool,
72		_tree_shaking:bool,
73		_minify:bool,
74	) -> Self {
75		Self {
76			target,
77			module_format:_module_format,
78			emit_decorator_metadata:_emit_decorator_metadata,
79			use_define_for_class_fields,
80			jsx,
81			tree_shaking:_tree_shaking,
82			minify:_minify,
83		}
84	}
85}
86
87/// Transform a parsed AST from TypeScript to JavaScript
88///
89/// # Arguments
90/// * `allocator` - The allocator used for the AST
91/// * `program` - The parsed program AST (mutable)
92/// * `source_path` - The source file path
93/// * `source_type` - The source type (TypeScript, JSX, etc.)
94/// * `config` - Transformer configuration options
95///
96/// # Returns
97/// Result containing transformation errors if any
98static TRANSFORM_COUNT:AtomicUsize = AtomicUsize::new(0);
99
100#[tracing::instrument(skip(allocator, program, config))]
101pub fn transform<'a>(
102	allocator:&'a Allocator,
103	program:&mut Program<'a>,
104	source_path:&str,
105	_source_type:SourceType,
106	config:&TransformerConfig,
107) -> Result<(), Vec<String>> {
108	let transform_id = TRANSFORM_COUNT.fetch_add(1, Ordering::SeqCst);
109
110	info!("[Transform #{transform_id}] Starting transformation of: {}", source_path);
111	trace!("[Transform #{transform_id}] Allocator address: {:p}", allocator);
112	trace!("[Transform #{transform_id}] Program address: {:p}", program);
113	trace!(
114		"[Transform #{transform_id}] Program body ptr before: {:p}, len: {}",
115		program.body.as_ptr(),
116		program.body.len()
117	);
118	debug!(
119		"[Transform #{transform_id}] Config: target={}, module={}, use_define={}",
120		config.target, config.module_format, config.use_define_for_class_fields
121	);
122
123	// Build semantic information required for transformations
124	let semantic_start = std::time::Instant::now();
125	let semantic_ret = SemanticBuilder::new().build(program);
126	info!(
127		"[Transform #{transform_id}] Semantic build completed in {:?}",
128		semantic_start.elapsed()
129	);
130
131	if !semantic_ret.errors.is_empty() {
132		let errors:Vec<String> = semantic_ret.errors.iter().map(|e| e.to_string()).collect();
133		warn!("[Transform #{transform_id}] Semantic errors: {:?}", errors);
134		return Err(errors);
135	}
136
137	// Extract symbols and scopes from semantic using OXC 0.48 API
138	let (symbols, scopes) = semantic_ret.semantic.into_symbol_table_and_scope_tree();
139	trace!(
140		"[Transform #{transform_id}] Extracted {} symbols and {} scopes",
141		symbols.len(),
142		scopes.len()
143	);
144
145	// Configure TypeScript transformation
146	// Set only_remove_type_imports to true to preserve all value exports
147	// This ensures modules with runtime code (like profiling.ts) emit JavaScript
148	let mut typescript_options = TypeScriptOptions::default();
149	typescript_options.only_remove_type_imports = true;
150	trace!("[Transform #{transform_id}] TypeScript options configured (only_remove_type_imports=true)");
151
152	// Configure JSX transformation if enabled
153	let jsx_options = if config.jsx {
154		JsxOptions { runtime:JsxRuntime::Automatic, ..JsxOptions::default() }
155	} else {
156		// Disable JSX by setting a dummy runtime
157		JsxOptions { runtime:JsxRuntime::Classic, ..JsxOptions::default() }
158	};
159	trace!("[Transform #{transform_id}] JSX options configured");
160
161	// Configure environment options based on target
162	let env_options_start = std::time::Instant::now();
163	let env_options = EnvOptions::from_target(&config.target).unwrap_or_default();
164	trace!(
165		"[Transform #{transform_id}] Env options from target '{}' in {:?}",
166		config.target,
167		env_options_start.elapsed()
168	);
169
170	// Configure compiler assumptions for VSCode compatibility.
171	// The `use_define_for_class_fields` flag from TypeScript:
172	// - false => loose mode (direct assignment) => set_public_class_fields = true
173	// - true => strict mode (defineProperty) => set_public_class_fields = false
174	let mut assumptions = CompilerAssumptions::default();
175	assumptions.set_public_class_fields = !config.use_define_for_class_fields;
176	trace!(
177		"[Transform #{transform_id}] Compiler assumptions configured (set_public_class_fields={})",
178		assumptions.set_public_class_fields
179	);
180
181	// Create transform options with all VSCode compatibility settings
182	let transform_options = TransformOptions {
183		typescript:typescript_options,
184		jsx:jsx_options,
185		env:env_options,
186		assumptions,
187		..TransformOptions::default()
188	};
189	trace!("[Transform #{transform_id}] TransformOptions configured with plugins");
190	trace!("[Transform #{transform_id}] TransformOptions created");
191
192	// Create transformer and apply transformation using OXC 0.48 API
193	let transformer_start = std::time::Instant::now();
194	let transformer = Transformer::new(allocator, Path::new(source_path), &transform_options);
195	info!(
196		"[Transform #{transform_id}] Transformer created in {:?}",
197		transformer_start.elapsed()
198	);
199	trace!("[Transform #{transform_id}] Transformer allocator address: {:p}", allocator);
200
201	let build_start = std::time::Instant::now();
202	let transform_ret = transformer.build_with_symbols_and_scopes(symbols, scopes, program);
203	info!(
204		"[Transform #{transform_id}] build_with_symbols_and_scopes completed in {:?}",
205		build_start.elapsed()
206	);
207	trace!(
208		"[Transform #{transform_id}] Program body ptr after: {:p}, len: {}",
209		program.body.as_ptr(),
210		program.body.len()
211	);
212
213	if !transform_ret.errors.is_empty() {
214		let errors:Vec<String> = transform_ret.errors.iter().map(|e| e.to_string()).collect();
215		warn!("[Transform #{transform_id}] Transformation errors: {:?}", errors);
216		return Err(errors);
217	}
218
219	info!(
220		"[Transform #{transform_id}] SUCCESS: Transformation completed for {}",
221		source_path
222	);
223	Ok(())
224}