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