1use std::sync::Arc;
2
3use oxc_allocator::Allocator;
4
5use oxc_ast::ast::{
6 BindingPattern, Expression, FormalParameter, FormalParameterRest, FormalParameters, Program,
7 Statement, VariableDeclaration,
8};
9use oxc_diagnostics::OxcDiagnostic;
10use oxc_parser::{ParseOptions, Parser, ParserReturn};
11use oxc_span::{GetSpan, SourceType};
12
13use self_cell::self_cell;
14
15struct ProgramOwner {
16 source: Box<str>,
17 allocator: Allocator,
18 source_type: SourceType,
19 options: ParseOptions,
20}
21
22self_cell! {
23 struct ParsedProgramCell {
24 owner: ProgramOwner,
25
26 #[covariant]
27 dependent: ParserReturn,
28 }
29}
30
31struct ExpressionOwner {
32 source: Box<str>,
33 allocator: Allocator,
34 source_type: SourceType,
35}
36
37struct ParsedExpressionData<'a> {
38 expression: Expression<'a>,
39}
40
41self_cell! {
42 struct ParsedExpressionCell {
43 owner: ExpressionOwner,
44
45 #[covariant]
46 dependent: ParsedExpressionData,
47 }
48}
49
50pub struct ParsedJsProgram {
56 cell: ParsedProgramCell,
57}
58
59impl std::fmt::Debug for ParsedJsProgram {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.debug_struct("ParsedJsProgram")
62 .field("source", &self.source())
63 .field("source_type", &self.source_type())
64 .field("panicked", &self.panicked())
65 .field("error_count", &self.errors().len())
66 .finish()
67 }
68}
69
70impl PartialEq for ParsedJsProgram {
71 fn eq(&self, other: &Self) -> bool {
72 self.source() == other.source() && self.source_type() == other.source_type()
73 }
74}
75
76impl Eq for ParsedJsProgram {}
77
78impl ParsedJsProgram {
79 #[must_use]
81 pub fn parse(source: impl Into<Box<str>>, source_type: SourceType) -> Self {
82 Self::parse_with_options(source, source_type, ParseOptions::default())
83 }
84
85 #[must_use]
87 pub fn parse_with_options(
88 source: impl Into<Box<str>>,
89 source_type: SourceType,
90 options: ParseOptions,
91 ) -> Self {
92 let owner = ProgramOwner {
93 source: source.into(),
94 allocator: Allocator::default(),
95 source_type,
96 options,
97 };
98 let cell = ParsedProgramCell::new(owner, |owner| {
99 Parser::new(&owner.allocator, owner.source.as_ref(), owner.source_type)
100 .with_options(owner.options)
101 .parse()
102 });
103 Self { cell }
104 }
105
106 #[must_use]
108 pub fn source(&self) -> &str {
109 self.cell.borrow_owner().source.as_ref()
110 }
111
112 #[must_use]
114 pub fn source_type(&self) -> SourceType {
115 self.cell.borrow_owner().source_type
116 }
117
118 #[must_use]
120 pub fn program(&self) -> &Program<'_> {
121 &self.cell.borrow_dependent().program
122 }
123
124 pub fn errors(&self) -> &[OxcDiagnostic] {
126 &self.cell.borrow_dependent().errors
127 }
128
129 #[must_use]
131 pub fn panicked(&self) -> bool {
132 self.cell.borrow_dependent().panicked
133 }
134
135 #[must_use]
137 pub fn is_flow_language(&self) -> bool {
138 self.cell.borrow_dependent().is_flow_language
139 }
140
141 #[must_use]
144 pub fn parser_return(&self) -> &ParserReturn<'_> {
145 self.cell.borrow_dependent()
146 }
147}
148
149pub struct ParsedJsExpression {
155 cell: ParsedExpressionCell,
156}
157
158impl std::fmt::Debug for ParsedJsExpression {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 f.debug_struct("ParsedJsExpression")
161 .field("source", &self.source())
162 .field("source_type", &self.source_type())
163 .finish()
164 }
165}
166
167impl PartialEq for ParsedJsExpression {
168 fn eq(&self, other: &Self) -> bool {
169 self.source() == other.source() && self.source_type() == other.source_type()
170 }
171}
172
173impl Eq for ParsedJsExpression {}
174
175impl ParsedJsExpression {
176 pub fn parse(
178 source: impl Into<Box<str>>,
179 source_type: SourceType,
180 ) -> Result<Self, Box<[OxcDiagnostic]>> {
181 let owner = ExpressionOwner {
182 source: source.into(),
183 allocator: Allocator::default(),
184 source_type,
185 };
186 let cell = ParsedExpressionCell::try_new(owner, |owner| {
187 Parser::new(&owner.allocator, owner.source.as_ref(), owner.source_type)
188 .parse_expression()
189 .map(|expression| ParsedExpressionData { expression })
190 .map_err(|errors| errors.into_boxed_slice())
191 })?;
192 Ok(Self { cell })
193 }
194
195 #[must_use]
197 pub fn source(&self) -> &str {
198 self.cell.borrow_owner().source.as_ref()
199 }
200
201 #[must_use]
203 pub fn source_type(&self) -> SourceType {
204 self.cell.borrow_owner().source_type
205 }
206
207 #[must_use]
209 pub fn expression(&self) -> &Expression<'_> {
210 &self.cell.borrow_dependent().expression
211 }
212}
213
214pub struct ParsedJsPattern {
220 source: Box<str>,
221 wrapper: Arc<ParsedJsExpression>,
222}
223
224impl std::fmt::Debug for ParsedJsPattern {
225 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226 f.debug_struct("ParsedJsPattern")
227 .field("source", &self.source())
228 .finish()
229 }
230}
231
232pub struct ParsedJsParameters {
237 source: Box<str>,
238 wrapper: Arc<ParsedJsExpression>,
239}
240
241impl std::fmt::Debug for ParsedJsParameters {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 f.debug_struct("ParsedJsParameters")
244 .field("source", &self.source())
245 .field("parameter_count", &self.parameters().items.len())
246 .field("has_rest", &self.parameters().rest.is_some())
247 .finish()
248 }
249}
250
251impl PartialEq for ParsedJsParameters {
252 fn eq(&self, other: &Self) -> bool {
253 self.source() == other.source()
254 }
255}
256
257impl Eq for ParsedJsParameters {}
258
259impl ParsedJsParameters {
260 pub fn parse(source: impl Into<Box<str>>) -> Result<Self, Box<[OxcDiagnostic]>> {
261 let source = source.into();
262 let wrapper_source = format!("({})=>{{}}", source);
263 let wrapper = Arc::new(ParsedJsExpression::parse(
264 wrapper_source,
265 SourceType::ts().with_module(true),
266 )?);
267 let _ = Self::parameters_from_wrapper(&wrapper).ok_or_else(|| {
268 vec![OxcDiagnostic::error(
269 "failed to recover formal parameters from wrapper",
270 )]
271 .into_boxed_slice()
272 })?;
273 Ok(Self { source, wrapper })
274 }
275
276 #[must_use]
277 pub fn source(&self) -> &str {
278 self.source.as_ref()
279 }
280
281 #[must_use]
282 pub fn parameters(&self) -> &FormalParameters<'_> {
283 Self::parameters_from_wrapper(&self.wrapper).expect("validated parsed parameters")
284 }
285
286 #[must_use]
287 pub fn parameter(&self, index: usize) -> Option<&FormalParameter<'_>> {
288 self.parameters().items.get(index)
289 }
290
291 #[must_use]
292 pub fn rest_parameter(&self) -> Option<&FormalParameterRest<'_>> {
293 self.parameters().rest.as_deref()
294 }
295
296 fn parameters_from_wrapper(wrapper: &ParsedJsExpression) -> Option<&FormalParameters<'_>> {
297 match wrapper.expression() {
298 Expression::ArrowFunctionExpression(function) => Some(&function.params),
299 _ => None,
300 }
301 }
302}
303
304impl PartialEq for ParsedJsPattern {
305 fn eq(&self, other: &Self) -> bool {
306 self.source() == other.source()
307 }
308}
309
310impl Eq for ParsedJsPattern {}
311
312impl ParsedJsPattern {
313 pub fn parse(source: impl Into<Box<str>>) -> Result<Self, Box<[OxcDiagnostic]>> {
314 let source = source.into();
315 let wrapper_source = format!("({})=>{{}}", source);
316 let wrapper = Arc::new(ParsedJsExpression::parse(
317 wrapper_source,
318 SourceType::ts().with_module(true),
319 )?);
320
321 let _ = Self::pattern_from_wrapper(&wrapper).ok_or_else(|| {
322 vec![OxcDiagnostic::error(
323 "failed to recover binding pattern from wrapper",
324 )]
325 .into_boxed_slice()
326 })?;
327
328 Ok(Self { source, wrapper })
329 }
330
331 #[must_use]
332 pub fn source(&self) -> &str {
333 self.source.as_ref()
334 }
335
336 #[must_use]
337 pub fn pattern(&self) -> &BindingPattern<'_> {
338 Self::pattern_from_wrapper(&self.wrapper).expect("validated parsed pattern")
339 }
340
341 fn pattern_from_wrapper(wrapper: &ParsedJsExpression) -> Option<&BindingPattern<'_>> {
342 match wrapper.expression() {
343 Expression::ArrowFunctionExpression(function) => function
344 .params
345 .items
346 .first()
347 .map(|parameter| ¶meter.pattern),
348 _ => None,
349 }
350 }
351}
352
353impl ParsedJsProgram {
354 #[must_use]
355 pub fn statement(&self, index: usize) -> Option<&Statement<'_>> {
356 self.program().body.get(index)
357 }
358
359 #[must_use]
360 pub fn statement_source(&self, index: usize) -> Option<&str> {
361 let statement = self.statement(index)?;
362 let span = statement.span();
363 self.source()
364 .get(span.start as usize..span.end as usize)
365 }
366
367 #[must_use]
368 pub fn variable_declaration(&self, index: usize) -> Option<&VariableDeclaration<'_>> {
369 match self.statement(index)? {
370 Statement::VariableDeclaration(declaration) => Some(declaration),
371 Statement::ExportNamedDeclaration(declaration) => match declaration.declaration.as_ref() {
372 Some(oxc_ast::ast::Declaration::VariableDeclaration(declaration)) => Some(declaration),
373 _ => None,
374 },
375 _ => None,
376 }
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use oxc_ast::ast::{BindingPattern, Expression, Statement};
383 use oxc_span::SourceType;
384
385 use super::{ParsedJsExpression, ParsedJsPattern, ParsedJsProgram};
386
387 #[test]
388 fn parsed_js_program_exposes_reusable_oxc_program() {
389 let parsed = ParsedJsProgram::parse("export const answer = 42;", SourceType::mjs());
390
391 assert_eq!(parsed.source(), "export const answer = 42;");
392 assert!(parsed.errors().is_empty());
393 assert!(!parsed.panicked());
394 assert!(matches!(
395 parsed.program().body.first(),
396 Some(Statement::ExportNamedDeclaration(_))
397 ));
398 }
399
400 #[test]
401 fn parsed_js_expression_exposes_reusable_oxc_expression() {
402 let parsed = ParsedJsExpression::parse("count + 1", SourceType::ts().with_module(true))
403 .expect("expression should parse");
404
405 assert_eq!(parsed.source(), "count + 1");
406 assert!(matches!(
407 parsed.expression(),
408 Expression::BinaryExpression(_)
409 ));
410 }
411
412 #[test]
413 fn parsed_js_expression_returns_oxc_errors_on_invalid_input() {
414 let errors = ParsedJsExpression::parse("foo(", SourceType::ts().with_module(true))
415 .err()
416 .expect("expression should fail");
417
418 assert!(!errors.is_empty());
419 }
420
421 #[test]
422 fn parsed_js_pattern_exposes_reusable_oxc_pattern() {
423 let parsed =
424 ParsedJsPattern::parse("{ count, items: [item] }").expect("pattern should parse");
425
426 assert!(matches!(parsed.pattern(), BindingPattern::ObjectPattern(_)));
427 }
428}