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	/// 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 the unified scoping (symbol table + scope tree) from semantic.
138	// OXC 0.127 collapsed the separate `SymbolTable` + `ScopeTree` accessors
139	// into a single `Scoping` value returned by `into_scoping()`. The older
140	// `into_symbol_table_and_scope_tree()` (0.48-era API) no longer exists,
141	// and `Transformer::build_with_symbols_and_scopes` has likewise been
142	// replaced by `build_with_scoping` below.
143	let scoping = semantic_ret.semantic.into_scoping();
144	trace!(
145		"[Transform #{transform_id}] Extracted scoping: {} symbols, {} scopes",
146		scoping.symbols_len(),
147		scoping.scopes_len()
148	);
149
150	// Configure TypeScript transformation
151	// Set only_remove_type_imports to true to preserve all value exports
152	// This ensures modules with runtime code (like profiling.ts) emit JavaScript
153	let mut typescript_options = TypeScriptOptions::default();
154	typescript_options.only_remove_type_imports = true;
155	trace!("[Transform #{transform_id}] TypeScript options configured (only_remove_type_imports=true)");
156
157	// Configure JSX transformation if enabled
158	let jsx_options = if config.jsx {
159		JsxOptions { runtime:JsxRuntime::Automatic, ..JsxOptions::default() }
160	} else {
161		// Disable JSX by setting a dummy runtime
162		JsxOptions { runtime:JsxRuntime::Classic, ..JsxOptions::default() }
163	};
164	trace!("[Transform #{transform_id}] JSX options configured");
165
166	// Configure environment options based on target
167	let env_options_start = std::time::Instant::now();
168	let env_options = EnvOptions::from_target(&config.target).unwrap_or_default();
169	trace!(
170		"[Transform #{transform_id}] Env options from target '{}' in {:?}",
171		config.target,
172		env_options_start.elapsed()
173	);
174
175	// Configure compiler assumptions for VSCode compatibility.
176	// The `use_define_for_class_fields` flag from TypeScript:
177	// - false => loose mode (direct assignment) => set_public_class_fields = true
178	// - true => strict mode (defineProperty) => set_public_class_fields = false
179	let mut assumptions = CompilerAssumptions::default();
180	assumptions.set_public_class_fields = !config.use_define_for_class_fields;
181	trace!(
182		"[Transform #{transform_id}] Compiler assumptions configured (set_public_class_fields={})",
183		assumptions.set_public_class_fields
184	);
185
186	// Create transform options with all VSCode compatibility settings
187	let transform_options = TransformOptions {
188		typescript:typescript_options,
189		jsx:jsx_options,
190		env:env_options,
191		assumptions,
192		..TransformOptions::default()
193	};
194	trace!("[Transform #{transform_id}] TransformOptions configured with plugins");
195	trace!("[Transform #{transform_id}] TransformOptions created");
196
197	// Create transformer and apply transformation using OXC 0.127 API.
198	let transformer_start = std::time::Instant::now();
199	let transformer = Transformer::new(allocator, Path::new(source_path), &transform_options);
200	info!(
201		"[Transform #{transform_id}] Transformer created in {:?}",
202		transformer_start.elapsed()
203	);
204	trace!("[Transform #{transform_id}] Transformer allocator address: {:p}", allocator);
205
206	let build_start = std::time::Instant::now();
207	let transform_ret = transformer.build_with_scoping(scoping, program);
208	info!(
209		"[Transform #{transform_id}] build_with_scoping completed in {:?}",
210		build_start.elapsed()
211	);
212	trace!(
213		"[Transform #{transform_id}] Program body ptr after: {:p}, len: {}",
214		program.body.as_ptr(),
215		program.body.len()
216	);
217
218	if !transform_ret.errors.is_empty() {
219		let errors:Vec<String> = transform_ret.errors.iter().map(|e| e.to_string()).collect();
220		warn!("[Transform #{transform_id}] Transformation errors: {:?}", errors);
221		return Err(errors);
222	}
223
224	info!(
225		"[Transform #{transform_id}] SUCCESS: Transformation completed for {}",
226		source_path
227	);
228	Ok(())
229}