Skip to main content

Library/Fn/OXC/
Parser.rs

1//! OXC TypeScript Parser module
2//!
3//! This module provides TypeScript source code parsing using the OXC parser.
4//!
5//! DIAGNOSTIC LOGGING:
6//! - All operations log with tracing::debug! for memory lifecycle tracking
7//! - Use RUST_LOG=debug to see detailed parser operations
8
9use std::sync::atomic::{AtomicUsize, Ordering};
10
11use oxc_allocator::Allocator;
12use oxc_parser::{Parser, ParserReturn};
13use oxc_span::SourceType;
14use tracing::{debug, info, trace, warn};
15
16/// Result of parsing a TypeScript source file
17///
18/// CRITICAL FIX FOR SEGFAULT:
19/// The Program has 'static lifetime via safe transmute. ParseResult owns BOTH
20/// the Program AND the Allocator, ensuring they're dropped together.
21///
22/// The ORIGINAL segfault occurred when code did:
23///   let mut program = parse_result.program;  // Moves Program out!
24///   Transformer::transform(&parse_result.allocator, ...);
25///   // parse_result dropped here -> allocator freed -> program is dangling!
26///
27/// THE FIX in Compiler.rs - NEVER move Program out:
28///   let program = &mut parse_result.program;  // Borrow mutably
29///   Transformer::transform(&parse_result.allocator, program, ...);
30///   // parse_result stays in scope -> allocator stays alive -> no segfault!
31pub struct ParseResult {
32	/// The parsed AST program with 'static lifetime (safe transmute)
33	pub program:oxc_ast::ast::Program<'static>,
34
35	/// The allocator used for the AST - owns the memory for the Program
36	/// CRITICAL: Must not be dropped separately from program
37	pub allocator:Allocator,
38
39	/// Any parsing errors encountered
40	pub errors:Vec<String>,
41
42	/// File path for debugging
43	#[allow(dead_code)]
44	pub file_path:String,
45}
46
47/// Parser configuration options
48#[derive(Debug, Clone)]
49pub struct ParserConfig {
50	/// Target ECMAScript version (e.g., "es2024")
51	pub target:String,
52
53	/// Whether to support JSX syntax
54	pub jsx:bool,
55
56	/// Whether to support decorators
57	pub decorators:bool,
58
59	/// Whether to support TypeScript
60	pub typescript:bool,
61}
62
63impl Default for ParserConfig {
64	fn default() -> Self { Self { target:"es2024".to_string(), jsx:false, decorators:true, typescript:true } }
65}
66
67impl ParserConfig {
68	/// Create a new parser configuration
69	pub fn new(target:String, jsx:bool, decorators:bool, typescript:bool) -> Self {
70		Self { target, jsx, decorators, typescript }
71	}
72}
73
74/// Parse TypeScript source code into an AST
75///
76/// # Arguments
77/// * `source_text` - The TypeScript source code to parse
78/// * `file_path` - The path to the source file (used for determining file type)
79/// * `config` - Parser configuration options
80///
81/// # Returns
82/// A ParseResult containing the parsed AST and any errors
83static PARSE_COUNT:AtomicUsize = AtomicUsize::new(0);
84
85#[tracing::instrument(skip(source_text, config))]
86pub fn parse(source_text:&str, file_path:&str, config:&ParserConfig) -> Result<ParseResult, Vec<String>> {
87	let parse_id = PARSE_COUNT.fetch_add(1, Ordering::SeqCst);
88
89	info!("[Parser #{parse_id}] Starting parse of: {}", file_path);
90
91	trace!("[Parser #{parse_id}] Source text length: {} bytes", source_text.len());
92
93	let allocator = Allocator::default();
94
95	trace!("[Parser #{parse_id}] Allocator created at: {:p}", &allocator);
96
97	// Determine source type based on file extension and config
98	let source_type = determine_source_type(file_path, config);
99
100	info!("[Parser #{parse_id}] Parsing {} as {:?}", file_path, source_type);
101
102	let parse_start = std::time::Instant::now();
103
104	// Parse the source code
105	let parser_return:ParserReturn = Parser::new(&allocator, source_text, source_type).parse();
106
107	info!("[Parser #{parse_id}] Parse completed in {:?}", parse_start.elapsed());
108
109	let errors:Vec<String> = parser_return.errors.iter().map(|e| e.to_string()).collect();
110
111	if !errors.is_empty() {
112		warn!("[Parser #{parse_id}] Parsing errors for {}: {:?}", file_path, errors);
113
114		return Err(errors);
115	}
116
117	// SAFETY: Transmute program lifetime to 'static.
118	// This is SAFE because:
119	// 1. ParseResult owns BOTH the Program AND the Allocator
120	// 2. The Program's memory comes from the Allocator
121	// 3. Both are dropped together when ParseResult is dropped
122	// 4. The Program NEVER outlives the Allocator
123	//
124	// CRITICAL BUG FIX: The original segfault happened because code did:
125	//   let mut program = parse_result.program;  // Moves Program out!
126	//   Transformer::transform(&parse_result.allocator, ...);
127	//   // parse_result dropped -> allocator freed -> program is dangling!
128	//
129	// THE FIX in Compiler.rs:
130	//   let program = &mut parse_result.program;  // Borrow mutably, don't move!
131	//   Transformer::transform(&parse_result.allocator, program, ...);
132	//   // parse_result stays in scope -> allocator stays alive
133	let program = unsafe {
134		std::mem::transmute::<oxc_ast::ast::Program<'_>, oxc_ast::ast::Program<'static>>(parser_return.program)
135	};
136
137	debug!("[Parser #{parse_id}] Program transmute complete at: {:p}", &program);
138
139	trace!(
140		"[Parser #{parse_id}] AST body pointer: {:p}, len: {}",
141		program.body.as_ptr(),
142		program.body.len()
143	);
144
145	info!(
146		"[Parser #{parse_id}] SUCCESS: ParseResult<'static> created (allocator={:p})",
147		&allocator
148	);
149
150	Ok(ParseResult { program, allocator, errors:vec![], file_path:file_path.to_string() })
151}
152
153/// Determine the source type based on file path and configuration
154fn determine_source_type(file_path:&str, _config:&ParserConfig) -> SourceType {
155	let path = std::path::Path::new(file_path);
156
157	let extension = path.extension().and_then(|e| e.to_str()).unwrap_or("");
158
159	match extension {
160		"ts" | "mts" => SourceType::ts(),
161
162		"tsx" => SourceType::tsx(),
163
164		"mjs" => SourceType::mjs(),
165
166		"cjs" => SourceType::cjs(),
167
168		"js" => SourceType::unambiguous(),
169
170		"jsx" => SourceType::jsx(),
171
172		_ => SourceType::unambiguous(),
173	}
174}