Skip to main content

Library/Fn/OXC/
Codegen.rs

1//! OXC Code Generation module
2//!
3//! This module provides code generation from the transformed AST to JavaScript
4//! source code.
5//!
6//! DIAGNOSTIC LOGGING:
7//! - Tracks codegen lifecycle and memory access patterns
8
9use std::sync::atomic::{AtomicUsize, Ordering};
10
11use oxc_allocator::Allocator;
12use oxc_ast::ast::Program;
13use oxc_codegen::{Codegen, CodegenOptions, CodegenReturn, CommentOptions};
14use oxc_span::SourceType;
15use tracing::{debug, error, info, trace, warn};
16
17/// Codegen configuration options
18#[derive(Debug, Clone)]
19pub struct CodegenConfig {
20	/// Whether to generate minified output
21	pub minify:bool,
22
23	/// Whether to generate source maps
24	pub source_map:bool,
25
26	/// Source map file name (without extension)
27	pub source_map_name:String,
28
29	/// Whether to preserve comments
30	pub comments:bool,
31}
32
33impl Default for CodegenConfig {
34	fn default() -> Self { Self { minify:false, source_map:false, source_map_name:String::new(), comments:false } }
35}
36
37impl CodegenConfig {
38	/// Create a new codegen configuration
39	pub fn new(minify:bool, _source_map:bool, _source_map_name:String, comments:bool) -> Self {
40		Self { minify, source_map:_source_map, source_map_name:_source_map_name, comments }
41	}
42}
43
44/// Result of code generation
45pub struct CodegenResult {
46	/// The generated JavaScript source code
47	pub code:String,
48
49	/// The length of the generated code
50	pub code_len:usize,
51}
52
53/// Post-process generated JavaScript to match VSCode's static class property
54/// format. Converts `static x = expr;` into `static { this.x = expr; }`.
55/// This is needed because OXC 0.48's class properties plugin does not emit
56/// legacy static initializer blocks by default.
57fn transform_static_class_properties(code:&str) -> String {
58	// This regex matches: static <name> = <expression>;
59	// Captures the property name and the initializer expression.
60	let re = match regex::Regex::new(r"(?m)^\s*static\s+([a-zA-Z_$][\w$]*)\s*=\s*([^;]+);") {
61		Ok(re) => re,
62
63		Err(e) => {
64			// If regex compilation fails, return original code and log.
65			error!("transform_static_class_properties: regex compile error: {}", e);
66
67			return code.to_string();
68		},
69	};
70
71	re.replace_all(code, "static { this.$1 = $2; }").into_owned()
72}
73
74/// Generate JavaScript source code from a transformed AST
75///
76/// # Arguments
77/// * `allocator` - The allocator used for the AST
78/// * `program` - The transformed program AST
79/// * `_source_type` - The source type (JavaScript, JSX, etc.)
80/// * `config` - Codegen configuration options
81///
82/// # Returns
83/// A CodegenResult containing the generated source code
84static CODEGEN_COUNT:AtomicUsize = AtomicUsize::new(0);
85
86#[tracing::instrument(skip(_allocator, program, config))]
87pub fn codegen<'a>(
88	_allocator:&Allocator,
89
90	program:&Program<'a>,
91
92	_source_type:SourceType,
93
94	config:&CodegenConfig,
95) -> Result<CodegenResult, String> {
96	let codegen_id = CODEGEN_COUNT.fetch_add(1, Ordering::SeqCst);
97
98	info!("[Codegen #{codegen_id}] Starting code generation");
99
100	trace!("[Codegen #{codegen_id}] Program address: {:p}", program);
101
102	trace!(
103		"[Codegen #{codegen_id}] Program body ptr: {:p}, len: {}",
104		program.body.as_ptr(),
105		program.body.len()
106	);
107
108	debug!(
109		"[Codegen #{codegen_id}] Config: minify={}, comments={}",
110		config.minify, config.comments
111	);
112
113	// Configure codegen options. OXC 0.127 replaced the flat `comments: bool`
114	// field with a structured `CommentOptions` covering normal / jsdoc /
115	// annotation / legal comment categories. Map our legacy boolean onto the
116	// two extremes: `true` ⇒ defaults (keep everything), `false` ⇒ disabled
117	// (strip everything). Finer-grained gates can layer on later by exposing
118	// dedicated `CodegenConfig` fields.
119	let comment_options = if config.comments {
120		CommentOptions::default()
121	} else {
122		CommentOptions::disabled()
123	};
124
125	let options = CodegenOptions { minify:config.minify, comments:comment_options, ..Default::default() };
126
127	trace!("[Codegen #{codegen_id}] CodegenOptions configured");
128
129	// Create codegen instance and generate code
130	let codegen_start = std::time::Instant::now();
131
132	let CodegenReturn { code, .. } = Codegen::new().with_options(options).build(program);
133
134	info!(
135		"[Codegen #{codegen_id}] Code generation completed in {:?}",
136		codegen_start.elapsed()
137	);
138
139	let code_len = code.len();
140
141	debug!("[Codegen #{codegen_id}] Generated {} bytes of code", code_len);
142
143	trace!(
144		"[Codegen #{codegen_id}] First 100 chars of output: {:?}",
145		code.chars().take(100).collect::<String>()
146	);
147
148	info!("[Codegen #{codegen_id}] SUCCESS: Generated {} bytes", code_len);
149
150	// Transform OXC output to match VSCode static class property format
151	let transformed_code = transform_static_class_properties(&code);
152
153	Ok(CodegenResult { code:transformed_code, code_len })
154}
155
156/// Write the generated code to a file
157///
158/// # Arguments
159/// * `output_path` - The path to write the output file
160/// * `result` - The codegen result containing source text
161pub fn write_output(output_path:&std::path::Path, result:&CodegenResult) -> Result<(), std::io::Error> {
162	// Create parent directories if they don't exist
163	if let Some(parent) = output_path.parent() {
164		std::fs::create_dir_all(parent)?;
165	}
166
167	// Write the source code
168	std::fs::write(output_path, &result.code)?;
169
170	debug!("Written output to {}", output_path.display());
171
172	Ok(())
173}