Skip to main content

Library/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
37	/// Module format (commonjs, esmodule, etc.)
38	pub module_format:String,
39
40	/// Whether to emit decorator metadata
41	pub emit_decorator_metadata:bool,
42
43	/// Whether to use define for class fields (VSCode compatibility)
44	pub use_define_for_class_fields:bool,
45
46	/// Whether to support JSX
47	pub jsx:bool,
48
49	/// Whether to enable tree-shaking
50	pub tree_shaking:bool,
51
52	/// Whether to enable minification
53	pub minify:bool,
54}
55
56impl Default for TransformerConfig {
57	fn default() -> Self {
58		Self {
59			target:"es2024".to_string(),
60
61			module_format:"commonjs".to_string(),
62
63			emit_decorator_metadata:true,
64
65			use_define_for_class_fields:false,
66
67			jsx:false,
68
69			tree_shaking:false,
70
71			minify:false,
72		}
73	}
74}
75
76impl TransformerConfig {
77	/// Create a new transformer configuration
78	pub fn new(
79		target:String,
80
81		_module_format:String,
82
83		_emit_decorator_metadata:bool,
84
85		use_define_for_class_fields:bool,
86
87		jsx:bool,
88
89		_tree_shaking:bool,
90
91		_minify:bool,
92	) -> Self {
93		Self {
94			target,
95
96			module_format:_module_format,
97
98			emit_decorator_metadata:_emit_decorator_metadata,
99
100			use_define_for_class_fields,
101
102			jsx,
103
104			tree_shaking:_tree_shaking,
105
106			minify:_minify,
107		}
108	}
109}
110
111/// Transform a parsed AST from TypeScript to JavaScript
112///
113/// # Arguments
114/// * `allocator` - The allocator used for the AST
115/// * `program` - The parsed program AST (mutable)
116/// * `source_path` - The source file path
117/// * `source_type` - The source type (TypeScript, JSX, etc.)
118/// * `config` - Transformer configuration options
119///
120/// # Returns
121/// Result containing transformation errors if any
122static TRANSFORM_COUNT:AtomicUsize = AtomicUsize::new(0);
123
124#[tracing::instrument(skip(allocator, program, config))]
125pub fn transform<'a>(
126	allocator:&'a Allocator,
127
128	program:&mut Program<'a>,
129
130	source_path:&str,
131
132	_source_type:SourceType,
133
134	config:&TransformerConfig,
135) -> Result<(), Vec<String>> {
136	let transform_id = TRANSFORM_COUNT.fetch_add(1, Ordering::SeqCst);
137
138	info!("[Transform #{transform_id}] Starting transformation of: {}", source_path);
139
140	trace!("[Transform #{transform_id}] Allocator address: {:p}", allocator);
141
142	trace!("[Transform #{transform_id}] Program address: {:p}", program);
143
144	trace!(
145		"[Transform #{transform_id}] Program body ptr before: {:p}, len: {}",
146		program.body.as_ptr(),
147		program.body.len()
148	);
149
150	debug!(
151		"[Transform #{transform_id}] Config: target={}, module={}, use_define={}",
152		config.target, config.module_format, config.use_define_for_class_fields
153	);
154
155	// Build semantic information required for transformations
156	let semantic_start = std::time::Instant::now();
157
158	let semantic_ret = SemanticBuilder::new().build(program);
159
160	info!(
161		"[Transform #{transform_id}] Semantic build completed in {:?}",
162		semantic_start.elapsed()
163	);
164
165	if !semantic_ret.errors.is_empty() {
166		let errors:Vec<String> = semantic_ret.errors.iter().map(|e| e.to_string()).collect();
167
168		warn!("[Transform #{transform_id}] Semantic errors: {:?}", errors);
169
170		return Err(errors);
171	}
172
173	// Extract the unified scoping (symbol table + scope tree) from semantic.
174	// OXC 0.127 collapsed the separate `SymbolTable` + `ScopeTree` accessors
175	// into a single `Scoping` value returned by `into_scoping()`. The older
176	// `into_symbol_table_and_scope_tree()` (0.48-era API) no longer exists,
177	// and `Transformer::build_with_symbols_and_scopes` has likewise been
178	// replaced by `build_with_scoping` below.
179	let scoping = semantic_ret.semantic.into_scoping();
180
181	trace!(
182		"[Transform #{transform_id}] Extracted scoping: {} symbols, {} scopes",
183		scoping.symbols_len(),
184		scoping.scopes_len()
185	);
186
187	// Configure TypeScript transformation
188	// Set only_remove_type_imports to true to preserve all value exports
189	// This ensures modules with runtime code (like profiling.ts) emit JavaScript
190	let mut typescript_options = TypeScriptOptions::default();
191
192	typescript_options.only_remove_type_imports = true;
193
194	trace!("[Transform #{transform_id}] TypeScript options configured (only_remove_type_imports=true)");
195
196	// Configure JSX transformation if enabled
197	let jsx_options = if config.jsx {
198		JsxOptions { runtime:JsxRuntime::Automatic, ..JsxOptions::default() }
199	} else {
200		// Disable JSX by setting a dummy runtime
201		JsxOptions { runtime:JsxRuntime::Classic, ..JsxOptions::default() }
202	};
203
204	trace!("[Transform #{transform_id}] JSX options configured");
205
206	// Configure environment options based on target
207	let env_options_start = std::time::Instant::now();
208
209	let env_options = EnvOptions::from_target(&config.target).unwrap_or_default();
210
211	trace!(
212		"[Transform #{transform_id}] Env options from target '{}' in {:?}",
213		config.target,
214		env_options_start.elapsed()
215	);
216
217	// Configure compiler assumptions for VSCode compatibility.
218	// The `use_define_for_class_fields` flag from TypeScript:
219	// - false => loose mode (direct assignment) => set_public_class_fields = true
220	// - true => strict mode (defineProperty) => set_public_class_fields = false
221	let mut assumptions = CompilerAssumptions::default();
222
223	assumptions.set_public_class_fields = !config.use_define_for_class_fields;
224
225	trace!(
226		"[Transform #{transform_id}] Compiler assumptions configured (set_public_class_fields={})",
227		assumptions.set_public_class_fields
228	);
229
230	// Create transform options with all VSCode compatibility settings
231	let transform_options = TransformOptions {
232		typescript:typescript_options,
233
234		jsx:jsx_options,
235
236		env:env_options,
237
238		assumptions,
239		..TransformOptions::default()
240	};
241
242	trace!("[Transform #{transform_id}] TransformOptions configured with plugins");
243
244	trace!("[Transform #{transform_id}] TransformOptions created");
245
246	// Create transformer and apply transformation using OXC 0.127 API.
247	let transformer_start = std::time::Instant::now();
248
249	let transformer = Transformer::new(allocator, Path::new(source_path), &transform_options);
250
251	info!(
252		"[Transform #{transform_id}] Transformer created in {:?}",
253		transformer_start.elapsed()
254	);
255
256	trace!("[Transform #{transform_id}] Transformer allocator address: {:p}", allocator);
257
258	let build_start = std::time::Instant::now();
259
260	let transform_ret = transformer.build_with_scoping(scoping, program);
261
262	info!(
263		"[Transform #{transform_id}] build_with_scoping completed in {:?}",
264		build_start.elapsed()
265	);
266
267	trace!(
268		"[Transform #{transform_id}] Program body ptr after: {:p}, len: {}",
269		program.body.as_ptr(),
270		program.body.len()
271	);
272
273	if !transform_ret.errors.is_empty() {
274		let errors:Vec<String> = transform_ret.errors.iter().map(|e| e.to_string()).collect();
275
276		warn!("[Transform #{transform_id}] Transformation errors: {:?}", errors);
277
278		return Err(errors);
279	}
280
281	info!(
282		"[Transform #{transform_id}] SUCCESS: Transformation completed for {}",
283		source_path
284	);
285
286	Ok(())
287}