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_ast::CommentKind;
10use oxc_diagnostics::OxcDiagnostic;
11use oxc_parser::{ParseOptions, Parser, ParserReturn};
12use oxc_span::{GetSpan, SourceType};
13
14use self_cell::self_cell;
15
16struct ProgramOwner {
17 source: Box<str>,
18 allocator: Allocator,
19 source_type: SourceType,
20 options: ParseOptions,
21}
22
23self_cell! {
24 struct ParsedProgramCell {
25 owner: ProgramOwner,
26
27 #[covariant]
28 dependent: ParserReturn,
29 }
30}
31
32struct ExpressionOwner {
33 source: Box<str>,
34 allocator: Allocator,
35 source_type: SourceType,
36}
37
38struct ParsedExpressionData<'a> {
39 expression: Expression<'a>,
40}
41
42self_cell! {
43 struct ParsedExpressionCell {
44 owner: ExpressionOwner,
45
46 #[covariant]
47 dependent: ParsedExpressionData,
48 }
49}
50
51pub struct JsProgram {
57 cell: ParsedProgramCell,
58}
59
60impl std::fmt::Debug for JsProgram {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 f.debug_struct("JsProgram")
63 .field("source", &self.source())
64 .field("source_type", &self.source_type())
65 .field("panicked", &self.panicked())
66 .field("error_count", &self.errors().len())
67 .finish()
68 }
69}
70
71impl PartialEq for JsProgram {
72 fn eq(&self, other: &Self) -> bool {
73 self.source() == other.source() && self.source_type() == other.source_type()
74 }
75}
76
77impl Eq for JsProgram {}
78
79unsafe impl Send for JsProgram {}
83unsafe impl Sync for JsProgram {}
84
85impl JsProgram {
86 #[must_use]
88 pub fn parse(source: impl Into<Box<str>>, source_type: SourceType) -> Self {
89 Self::parse_with_options(source, source_type, ParseOptions::default())
90 }
91
92 #[must_use]
94 pub fn parse_with_options(
95 source: impl Into<Box<str>>,
96 source_type: SourceType,
97 options: ParseOptions,
98 ) -> Self {
99 let owner = ProgramOwner {
100 source: source.into(),
101 allocator: Allocator::default(),
102 source_type,
103 options,
104 };
105 let cell = ParsedProgramCell::new(owner, |owner| {
106 Parser::new(&owner.allocator, owner.source.as_ref(), owner.source_type)
107 .with_options(owner.options)
108 .parse()
109 });
110 Self { cell }
111 }
112
113 #[must_use]
115 pub fn source(&self) -> &str {
116 self.cell.borrow_owner().source.as_ref()
117 }
118
119 #[must_use]
121 pub fn source_type(&self) -> SourceType {
122 self.cell.borrow_owner().source_type
123 }
124
125 #[must_use]
127 pub fn program(&self) -> &Program<'_> {
128 &self.cell.borrow_dependent().program
129 }
130
131 pub fn errors(&self) -> &[OxcDiagnostic] {
133 &self.cell.borrow_dependent().errors
134 }
135
136 #[must_use]
138 pub fn panicked(&self) -> bool {
139 self.cell.borrow_dependent().panicked
140 }
141
142 #[must_use]
144 pub fn is_flow_language(&self) -> bool {
145 self.cell.borrow_dependent().is_flow_language
146 }
147
148 #[must_use]
151 pub fn parser_return(&self) -> &ParserReturn<'_> {
152 self.cell.borrow_dependent()
153 }
154
155 #[must_use]
163 pub fn to_estree_json(&self, full_source: &str, offset: usize, script_tag_end: usize) -> String {
164 use oxc_estree::ESTree;
165
166 let program = self.program();
167 let raw_json = if self.source_type().is_typescript() {
168 let mut ser = oxc_estree::CompactTSSerializer::new(false);
169 program.serialize(&mut ser);
170 ser.into_string()
171 } else {
172 let mut ser = oxc_estree::CompactJSSerializer::new(false);
173 program.serialize(&mut ser);
174 ser.into_string()
175 };
176
177 let Ok(mut value) = serde_json::from_str::<serde_json::Value>(&raw_json) else {
178 return raw_json;
179 };
180
181 if self.source_type().is_typescript() {
183 fix_template_element_spans(&mut value);
184 if let serde_json::Value::Object(ref mut map) = value {
187 if crate::estree::node_type(map) == "Program" {
188 map.insert("start".to_string(), serde_json::json!(0));
189 }
190 }
191 }
192
193 let start_line = line_at_offset(full_source, offset);
197
198 let content = self.source();
199 adjust_program_json(&mut value, content, offset, start_line);
200
201 if let serde_json::Value::Object(map) = &mut value
204 && crate::estree::node_type(map) == "Program"
205 {
206 if let Some(serde_json::Value::Object(loc)) = map.get_mut("loc") {
207 let (end_line, end_col) = line_column_at_offset(full_source, script_tag_end, 1);
208 loc.insert("end".to_string(), serde_json::json!({
209 "line": end_line,
210 "column": end_col
211 }));
212 }
213
214 let comment_entries: Vec<(u32, u32, serde_json::Value)> = program
216 .comments
217 .iter()
218 .map(|comment| {
219 let kind_str = match comment.kind {
220 CommentKind::Line => "Line",
221 CommentKind::SingleLineBlock | CommentKind::MultiLineBlock => "Block",
222 };
223 let content_span = comment.content_span();
224 let value_text =
225 &content[content_span.start as usize..content_span.end as usize];
226 let abs_start = comment.span.start as usize + offset;
227 let abs_end = comment.span.end as usize + offset;
228 (
229 comment.span.start + offset as u32,
230 comment.span.end + offset as u32,
231 serde_json::json!({
232 "type": kind_str,
233 "value": value_text,
234 "start": abs_start,
235 "end": abs_end
236 }),
237 )
238 })
239 .collect();
240
241 attach_comments_to_json_tree(&mut value, &comment_entries, full_source);
243 }
244
245 serde_json::to_string(&value).unwrap_or(raw_json)
246 }
247}
248
249pub(crate) fn attach_comments_to_json_tree(
253 root: &mut serde_json::Value,
254 comments: &[(u32, u32, serde_json::Value)],
255 content: &str,
256) {
257 if comments.is_empty() {
258 return;
259 }
260
261 fn walk(value: &mut serde_json::Value, comments: &[(u32, u32, serde_json::Value)], content: &str) {
263 let serde_json::Value::Object(map) = value else {
264 return;
265 };
266
267 let node_type = map
269 .get("type")
270 .and_then(|v| v.as_str())
271 .unwrap_or("");
272 let container_keys: &[&str] = match node_type {
273 "Program" | "BlockStatement" | "StaticBlock" | "ClassBody" => &["body"],
274 "SwitchCase" => &["consequent"],
275 "ArrayExpression" => &["elements"],
276 "ObjectExpression" => &["properties"],
277 _ => &[],
278 };
279
280 if node_type == "Program" {
282 let body_empty = map
283 .get("body")
284 .and_then(|v| v.as_array())
285 .is_none_or(|a| a.is_empty());
286 if body_empty {
287 let node_start = map.get("start").and_then(|v| v.as_u64()).unwrap_or(0);
288 let node_end = map.get("end").and_then(|v| v.as_u64()).unwrap_or(u64::MAX);
289 let trailing: Vec<serde_json::Value> = comments
290 .iter()
291 .filter(|(cs, ce, _)| *cs as u64 >= node_start && (*ce as u64) <= node_end)
292 .map(|(_, _, e)| e.clone())
293 .collect();
294 if !trailing.is_empty() {
295 map.insert(
296 "trailingComments".to_string(),
297 serde_json::Value::Array(trailing),
298 );
299 }
300 return;
301 }
302 }
303
304 let node_start = map.get("start").and_then(|v| v.as_u64()).unwrap_or(0) as u32;
306 let node_end = map.get("end").and_then(|v| v.as_u64()).unwrap_or(u32::MAX as u64) as u32;
307
308 for key in container_keys {
309 if let Some(serde_json::Value::Array(children)) = map.get_mut(*key) {
310 attach_comments_to_siblings(children, comments, content, node_start, node_end);
311 }
312 }
313
314 for v in map.values_mut() {
316 match v {
317 serde_json::Value::Object(_) => walk(v, comments, content),
318 serde_json::Value::Array(arr) => {
319 for item in arr.iter_mut() {
320 walk(item, comments, content);
321 }
322 }
323 _ => {}
324 }
325 }
326 }
327
328 walk(root, comments, content);
329}
330
331fn attach_comments_to_siblings(
334 children: &mut [serde_json::Value],
335 comments: &[(u32, u32, serde_json::Value)],
336 content: &str,
337 container_start: u32,
338 container_end: u32,
339) {
340 if children.is_empty() || comments.is_empty() {
341 return;
342 }
343
344 let positions: Vec<(u64, u64)> = children
346 .iter()
347 .map(|c| {
348 let s = c.get("start").and_then(|v| v.as_u64()).unwrap_or(0);
349 let e = c.get("end").and_then(|v| v.as_u64()).unwrap_or(0);
350 (s, e)
351 })
352 .collect();
353
354 let n = children.len();
356 let mut child_leading: Vec<Vec<serde_json::Value>> = vec![Vec::new(); n];
357 let mut child_trailing: Vec<Vec<serde_json::Value>> = vec![Vec::new(); n];
358
359 for &(c_start, c_end, ref entry) in comments {
360 if c_start < container_start || c_end > container_end {
362 continue;
363 }
364
365 let c_start_u64 = c_start as u64;
366 let c_end_u64 = c_end as u64;
367
368 if c_end_u64 <= positions[0].0 {
371 child_leading[0].push(entry.clone());
372 continue;
373 }
374 if c_start_u64 >= positions[n - 1].1 {
376 child_trailing[n - 1].push(entry.clone());
377 continue;
378 }
379 for i in 0..n - 1 {
381 if c_start_u64 >= positions[i].1 && c_end_u64 <= positions[i + 1].0 {
382 let child_end_pos = positions[i].1 as usize;
386 let comment_start_pos = c_start as usize;
387 let same_line = child_end_pos <= content.len()
388 && comment_start_pos <= content.len()
389 && !content[child_end_pos..comment_start_pos].contains('\n');
390 if same_line {
391 child_trailing[i].push(entry.clone());
392 } else {
393 child_leading[i + 1].push(entry.clone());
394 }
395 break;
396 }
397 }
398 }
400
401 for (i, child) in children.iter_mut().enumerate() {
403 if let serde_json::Value::Object(map) = child {
404 if !child_leading[i].is_empty() {
405 map.insert(
406 "leadingComments".to_string(),
407 serde_json::Value::Array(child_leading[i].clone()),
408 );
409 }
410 if !child_trailing[i].is_empty() {
411 map.insert(
412 "trailingComments".to_string(),
413 serde_json::Value::Array(child_trailing[i].clone()),
414 );
415 }
416 }
417 }
418}
419
420use crate::estree::{SpanAdjustConfig, adjust_spans_and_loc, line_at_offset, line_column_at_offset};
421
422fn adjust_program_json(
426 value: &mut serde_json::Value,
427 content_source: &str,
428 offset: usize,
429 start_line: usize,
430) {
431 adjust_spans_and_loc(value, &SpanAdjustConfig {
432 offset: offset as i64,
433 source: content_source,
434 base_line: start_line,
435 column_offset: 0,
436 with_character: false,
437 program_mode: true,
438 });
439}
440
441pub(crate) fn adjust_expression_json_with_column_offset(
446 value: &mut serde_json::Value,
447 full_source: &str,
448 offset: i64,
449 column_offset: usize,
450) {
451 adjust_expression_json_inner(value, full_source, offset, column_offset, false);
452}
453
454pub(crate) fn adjust_expression_json_with_character(
456 value: &mut serde_json::Value,
457 full_source: &str,
458 offset: i64,
459) {
460 adjust_expression_json_inner(value, full_source, offset, 0, true);
461}
462
463fn adjust_expression_json_inner(
464 value: &mut serde_json::Value,
465 full_source: &str,
466 offset: i64,
467 column_offset: usize,
468 with_character: bool,
469) {
470 adjust_spans_and_loc(value, &SpanAdjustConfig {
471 offset,
472 source: full_source,
473 base_line: 1,
474 column_offset,
475 with_character,
476 program_mode: false,
477 });
478}
479
480pub(crate) use crate::estree::fix_template_element_spans;
482
483pub(crate) use crate::estree::unwrap_parenthesized_expression;
485
486pub struct JsExpression {
492 cell: ParsedExpressionCell,
493}
494
495impl std::fmt::Debug for JsExpression {
496 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
497 f.debug_struct("JsExpression")
498 .field("source", &self.source())
499 .field("source_type", &self.source_type())
500 .finish()
501 }
502}
503
504impl PartialEq for JsExpression {
505 fn eq(&self, other: &Self) -> bool {
506 self.source() == other.source() && self.source_type() == other.source_type()
507 }
508}
509
510impl Eq for JsExpression {}
511
512unsafe impl Send for JsExpression {}
516unsafe impl Sync for JsExpression {}
517
518impl JsExpression {
519 pub fn parse(
521 source: impl Into<Box<str>>,
522 source_type: SourceType,
523 ) -> Result<Self, Box<[OxcDiagnostic]>> {
524 let owner = ExpressionOwner {
525 source: source.into(),
526 allocator: Allocator::default(),
527 source_type,
528 };
529 let cell = ParsedExpressionCell::try_new(owner, |owner| {
530 Parser::new(&owner.allocator, owner.source.as_ref(), owner.source_type)
531 .parse_expression()
532 .map(|expression| ParsedExpressionData { expression })
533 .map_err(|errors| errors.into_boxed_slice())
534 })?;
535 Ok(Self { cell })
536 }
537
538 #[must_use]
540 pub fn source(&self) -> &str {
541 self.cell.borrow_owner().source.as_ref()
542 }
543
544 #[must_use]
546 pub fn source_type(&self) -> SourceType {
547 self.cell.borrow_owner().source_type
548 }
549
550 #[must_use]
552 pub fn expression(&self) -> &Expression<'_> {
553 &self.cell.borrow_dependent().expression
554 }
555}
556
557pub struct JsPattern {
563 source: Box<str>,
564 wrapper: Arc<JsExpression>,
565}
566
567impl std::fmt::Debug for JsPattern {
568 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
569 f.debug_struct("JsPattern")
570 .field("source", &self.source())
571 .finish()
572 }
573}
574
575pub struct JsParameters {
580 source: Box<str>,
581 wrapper: Arc<JsExpression>,
582}
583
584impl std::fmt::Debug for JsParameters {
585 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586 f.debug_struct("JsParameters")
587 .field("source", &self.source())
588 .field("parameter_count", &self.parameters().items.len())
589 .field("has_rest", &self.parameters().rest.is_some())
590 .finish()
591 }
592}
593
594impl PartialEq for JsParameters {
595 fn eq(&self, other: &Self) -> bool {
596 self.source() == other.source()
597 }
598}
599
600impl Eq for JsParameters {}
601
602impl JsParameters {
603 pub fn parse(source: impl Into<Box<str>>) -> Result<Self, Box<[OxcDiagnostic]>> {
604 let source = source.into();
605 let wrapper_source = format!("({})=>{{}}", source);
606 let wrapper = Arc::new(JsExpression::parse(
607 wrapper_source,
608 SourceType::ts().with_module(true),
609 )?);
610 let _ = Self::parameters_from_wrapper(&wrapper).ok_or_else(|| {
611 vec![OxcDiagnostic::error(
612 "failed to recover formal parameters from wrapper",
613 )]
614 .into_boxed_slice()
615 })?;
616 Ok(Self { source, wrapper })
617 }
618
619 #[must_use]
620 pub fn source(&self) -> &str {
621 self.source.as_ref()
622 }
623
624 #[must_use]
625 pub fn parameters(&self) -> &FormalParameters<'_> {
626 Self::parameters_from_wrapper(&self.wrapper).expect("validated parsed parameters")
627 }
628
629 #[must_use]
630 pub fn parameter(&self, index: usize) -> Option<&FormalParameter<'_>> {
631 self.parameters().items.get(index)
632 }
633
634 #[must_use]
635 pub fn rest_parameter(&self) -> Option<&FormalParameterRest<'_>> {
636 self.parameters().rest.as_deref()
637 }
638
639 fn parameters_from_wrapper(wrapper: &JsExpression) -> Option<&FormalParameters<'_>> {
640 match wrapper.expression() {
641 Expression::ArrowFunctionExpression(function) => Some(&function.params),
642 _ => None,
643 }
644 }
645}
646
647impl PartialEq for JsPattern {
648 fn eq(&self, other: &Self) -> bool {
649 self.source() == other.source()
650 }
651}
652
653impl Eq for JsPattern {}
654
655unsafe impl Send for JsPattern {}
658unsafe impl Sync for JsPattern {}
659
660impl JsPattern {
661 pub fn parse(source: impl Into<Box<str>>) -> Result<Self, Box<[OxcDiagnostic]>> {
662 let source = source.into();
663 let wrapper_source = format!("({})=>{{}}", source);
664 let wrapper = Arc::new(JsExpression::parse(
665 wrapper_source,
666 SourceType::ts().with_module(true),
667 )?);
668
669 let _ = Self::pattern_from_wrapper(&wrapper).ok_or_else(|| {
670 vec![OxcDiagnostic::error(
671 "failed to recover binding pattern from wrapper",
672 )]
673 .into_boxed_slice()
674 })?;
675
676 Ok(Self { source, wrapper })
677 }
678
679 #[must_use]
680 pub fn source(&self) -> &str {
681 self.source.as_ref()
682 }
683
684 #[must_use]
685 pub fn pattern(&self) -> &BindingPattern<'_> {
686 Self::pattern_from_wrapper(&self.wrapper).expect("validated parsed pattern")
687 }
688
689 fn pattern_from_wrapper(wrapper: &JsExpression) -> Option<&BindingPattern<'_>> {
690 match wrapper.expression() {
691 Expression::ArrowFunctionExpression(function) => function
692 .params
693 .items
694 .first()
695 .map(|parameter| ¶meter.pattern),
696 _ => None,
697 }
698 }
699}
700
701impl JsProgram {
702 #[must_use]
703 pub fn statement(&self, index: usize) -> Option<&Statement<'_>> {
704 self.program().body.get(index)
705 }
706
707 #[must_use]
708 pub fn statement_source(&self, index: usize) -> Option<&str> {
709 let statement = self.statement(index)?;
710 let span = statement.span();
711 self.source()
712 .get(span.start as usize..span.end as usize)
713 }
714
715 #[must_use]
716 pub fn variable_declaration(&self, index: usize) -> Option<&VariableDeclaration<'_>> {
717 match self.statement(index)? {
718 Statement::VariableDeclaration(declaration) => Some(declaration),
719 Statement::ExportNamedDeclaration(declaration) => match declaration.declaration.as_ref() {
720 Some(oxc_ast::ast::Declaration::VariableDeclaration(declaration)) => Some(declaration),
721 _ => None,
722 },
723 _ => None,
724 }
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use oxc_ast::ast::{BindingPattern, Expression, Statement};
731 use oxc_span::SourceType;
732
733 use super::{JsExpression, JsPattern, JsProgram};
734
735 #[test]
736 fn parsed_js_program_exposes_reusable_oxc_program() {
737 let parsed = JsProgram::parse("export const answer = 42;", SourceType::mjs());
738
739 assert_eq!(parsed.source(), "export const answer = 42;");
740 assert!(parsed.errors().is_empty());
741 assert!(!parsed.panicked());
742 assert!(matches!(
743 parsed.program().body.first(),
744 Some(Statement::ExportNamedDeclaration(_))
745 ));
746 }
747
748 #[test]
749 fn parsed_js_expression_exposes_reusable_oxc_expression() {
750 let parsed = JsExpression::parse("count + 1", SourceType::ts().with_module(true))
751 .expect("expression should parse");
752
753 assert_eq!(parsed.source(), "count + 1");
754 assert!(matches!(
755 parsed.expression(),
756 Expression::BinaryExpression(_)
757 ));
758 }
759
760 #[test]
761 fn parsed_js_expression_returns_oxc_errors_on_invalid_input() {
762 let errors = JsExpression::parse("foo(", SourceType::ts().with_module(true))
763 .err()
764 .expect("expression should fail");
765
766 assert!(!errors.is_empty());
767 }
768
769 #[test]
770 fn parsed_js_pattern_exposes_reusable_oxc_pattern() {
771 let parsed =
772 JsPattern::parse("{ count, items: [item] }").expect("pattern should parse");
773
774 assert!(matches!(parsed.pattern(), BindingPattern::ObjectPattern(_)));
775 }
776}