1//! implements ts partial parsing helpers
2use crate::{adjust_err, err, marko_ast::LexState};
3
4use oxc_allocator::Allocator;
5use oxc_ast::ast::{Expression, Statement};
6use oxc_diagnostics::OxcDiagnostic;
7use oxc_span::{GetSpan, SourceType};
8
9pub fn scan_first_statement_forbid_trailing(source: &str) -> Result<u32, OxcDiagnostic> {
10 let mut allocator = Allocator::new();
11 let (stmt, parse_errors) = parse_stmt_extra(source, 0, &mut allocator)?;
12 let span = stmt.span();
13 let len = span.end - span.start;
14
15 // If OXC reported parse errors while recovering the statement, surface the
16 // first one directly — it points at the actual invalid token.
17 if let Some(first) = parse_errors.into_iter().next() {
18 return Err(first);
19 }
20
21 if let Some(trailing) = source[span.end as usize..].lines().next() {
22 if !trailing.trim().is_empty() {
23 return Err(err(
24 "Trailing content not allowed here",
25 span.end + (trailing.len() - trailing.trim_start().len()) as u32,
26 trailing.trim().len(),
27 ));
28 }
29 }
30 Ok(len)
31}
32
33fn parse_stmt_extra<'alloc, 'src: 'alloc>(
34 source: &'src str,
35 offset: isize,
36 allocator: &'alloc mut Allocator,
37) -> Result<(Statement<'alloc>, Vec<OxcDiagnostic>), OxcDiagnostic> {
38 if source.is_empty() {
39 return Err(err(
40 "Expected expression, found end of file",
41 offset.max(0).cast_unsigned() as u32,
42 1,
43 ));
44 }
45
46 let source_type = SourceType::default()
47 .with_module(true)
48 .with_typescript(true)
49 .with_jsx(false);
50
51 let mut result = oxc_parser::Parser::new(allocator, source, source_type).parse();
52 // Capture errors from the initial full-source parse. These are the only errors
53 // relevant to the caller — errors from truncated-candidate loop iterations below
54 // are artifacts of parsing incomplete source and must not be surfaced.
55 let initial_errors = std::mem::take(&mut result.errors);
56
57 if !initial_errors.is_empty() && result.program.body.is_empty() {
58 let first_err = initial_errors
59 .into_iter()
60 .next()
61 .expect("no errors but no result!");
62 let first_err_offset = first_err
63 .labels
64 .as_ref()
65 .ok_or_else(|| {
66 err(
67 "Expected statement",
68 offset.max(0).cast_unsigned() as u32,
69 1,
70 )
71 })?
72 .first()
73 .expect("labels, but no labels!")
74 .offset();
75
76 let mut candidate = source[0..first_err_offset].trim_end();
77 let mut whitespace_groups = 0;
78 loop {
79 if candidate.is_empty() {
80 return Err(adjust_err(first_err, offset));
81 }
82 result = oxc_parser::Parser::new(allocator, candidate, source_type).parse();
83 if (!result.errors.is_empty() || result.panicked) && result.program.body.is_empty() {
84 let before_trim = &candidate[..candidate.len() - 1];
85 let after_trim = before_trim.trim_end();
86 if after_trim.len() < before_trim.len() {
87 whitespace_groups += 1;
88 if whitespace_groups > 1 {
89 return Err(adjust_err(first_err, offset));
90 }
91 }
92 candidate = after_trim;
93 } else {
94 break;
95 }
96 }
97
98 let stmt = result.program.body.into_iter().next().ok_or_else(|| {
99 err(
100 "Expected statement",
101 offset.max(0).cast_unsigned() as u32,
102 1,
103 )
104 })?;
105 // Surface the OXC error only if it falls within the same line as the
106 // recovered statement (i.e. no newline between statement end and error).
107 // If there's a newline before the error, it's trailing content (markdown
108 // or another statement) that the caller will handle.
109 let stmt_end = stmt.span().end as usize;
110 let errors = if !source[stmt_end..first_err_offset].contains('\n') {
111 vec![adjust_err(first_err, offset)]
112 } else {
113 vec![]
114 };
115 return Ok((stmt, errors));
116 } else {
117 assert!(!result.panicked);
118 }
119
120 // Full-source parse succeeded; surface only errors that fall within the statement's
121 // span. Errors beyond the span are about trailing content (markdown, other statements)
122 // and should be handled by the trailing-content check in the caller, not here.
123 let stmt = result.program.body.into_iter().next().ok_or_else(|| {
124 err(
125 "Expected statement",
126 offset.max(0).cast_unsigned() as u32,
127 1,
128 )
129 })?;
130 let stmt_end = stmt.span().end as usize;
131 let within_stmt_errors: Vec<OxcDiagnostic> = initial_errors
132 .into_iter()
133 .filter(|e| {
134 e.labels
135 .as_deref()
136 .and_then(|l| l.first())
137 .map_or(false, |l| l.offset() < stmt_end)
138 })
139 .collect();
140 Ok((stmt, within_stmt_errors))
141}
142
143pub fn parse_expr_extra<'alloc, 'src: 'alloc>(
144 source: &'src str,
145 offset: i32,
146 allocator: &'alloc mut Allocator,
147) -> Result<Expression<'alloc>, OxcDiagnostic> {
148 if source.is_empty() {
149 return Err(err(
150 "Expected expression, found end of file",
151 offset.max(0).cast_unsigned(),
152 1,
153 ));
154 }
155
156 let source_type = SourceType::default()
157 .with_module(true)
158 .with_typescript(true)
159 .with_jsx(false);
160
161 let expr = match oxc_parser::Parser::new(allocator, source, source_type).parse_expression() {
162 Ok(expr) => expr,
163 Err(errors) => {
164 let first_err = errors.into_iter().next().expect("no errors but no result!");
165 let first_label = first_err
166 .labels
167 .as_ref()
168 .ok_or_else(|| err("Expected expression", offset.max(0).cast_unsigned(), 1))?
169 .first()
170 .expect("labels, but no labels!");
171 let first_err_offset = first_label.offset();
172 let first_err_len = first_label.len();
173
174 // Use end of error span when error is at start (e.g. "Cannot assign to this expression")
175 let initial_end = if first_err_offset == 0 {
176 (first_err_offset + first_err_len).min(source.len())
177 } else {
178 first_err_offset
179 };
180 let mut candidate = source[0..initial_end].trim_end();
181 let mut whitespace_groups = 0;
182 loop {
183 if candidate.is_empty() {
184 return Err(adjust_err(first_err, offset as isize));
185 }
186 match oxc_parser::Parser::new(allocator, candidate, source_type).parse_expression()
187 {
188 Ok(expr) => break expr,
189 Err(_) => {
190 let before_trim = &candidate[..candidate.len() - 1];
191 let after_trim = before_trim.trim_end();
192 if after_trim.len() < before_trim.len() {
193 whitespace_groups += 1;
194 if whitespace_groups > 1 {
195 return Err(adjust_err(first_err, offset as isize));
196 }
197 }
198 candidate = after_trim;
199 }
200 }
201 }
202 }
203 };
204
205 let mut expr = expr;
206 while let Expression::SequenceExpression(seq) = expr {
207 expr = seq
208 .unbox()
209 .expressions
210 .into_iter()
211 .next()
212 .expect("no items");
213 }
214 Ok(expr)
215}
216
217/// parse ts expression, stopping at garbage data / comma
218pub fn parse_expr(l: &mut LexState) -> Result<u32, OxcDiagnostic> {
219 let mut allocator = Allocator::new();
220 let expr = parse_expr_extra(l.peek_rest(), l.offset().cast_signed(), &mut allocator)?;
221 let span = expr.span();
222 l.advance(span.end - span.start);
223 Ok(span.end - span.start)
224}
225
226/// parse ts expression but stop at the first > because of ambiguity with HTML tag end
227pub fn parse_expr_without_gt(l: &mut LexState) -> Result<u32, OxcDiagnostic> {
228 use oxc_ast::ast::{BinaryOperator, Expression};
229
230 let rest = l.peek_rest();
231 let mut allocator = Allocator::new();
232
233 // First, try parsing the full source and using AST walking to find the earliest `>`.
234 // This handles balanced cases like `arr[a > 0]` and `fn(a > b)` correctly.
235 // We walk down the left spine of binary expressions, looking for any `>` operator.
236 // When we find one, we record the span of its left operand. We keep walking to find
237 // the innermost `>` (which appears earliest in the source).
238 if let Ok(expr) = parse_expr_extra(rest, l.offset().cast_signed(), &mut allocator) {
239 let mut e = &expr;
240 let mut result_end = expr.span().end; // default: whole expression
241
242 while let Expression::BinaryExpression(bin) = e {
243 if bin.operator == BinaryOperator::GreaterThan
244 || bin.operator == BinaryOperator::GreaterEqualThan
245 || bin.operator == BinaryOperator::ShiftRight
246 || bin.operator == BinaryOperator::ShiftRightZeroFill
247 {
248 // Found a `>`, update result to be left side's span
249 result_end = bin.left.span().end;
250 }
251 // Continue walking left to find any nested `>` operators
252 e = &bin.left;
253 }
254
255 l.advance(result_end);
256 return Ok(result_end);
257 }
258
259 // If full parse failed (e.g., due to JSX-like content after the expression),
260 // try parsing prefixes ending at each `>` position.
261 let gt_positions: Vec<usize> = rest
262 .char_indices()
263 .filter(|(_, c)| *c == '>')
264 .map(|(i, _)| i)
265 .collect();
266
267 // Try from longest to shortest prefix, return first success
268 for &pos in gt_positions.iter().rev() {
269 let candidate = &rest[..pos];
270 allocator.reset();
271 if let Ok(expr) = parse_expr_extra(candidate, l.offset().cast_signed(), &mut allocator) {
272 // Check that the parse consumed most of the candidate (not just a prefix)
273 let length = expr.span().end;
274 // If the expression spans close to the full candidate, use it
275 if length >= (pos.saturating_sub(1) as u32) {
276 l.advance(length);
277 return Ok(length);
278 }
279 }
280 }
281
282 // Last resort: return error from the original parse attempt
283 allocator.reset();
284 let expr = parse_expr_extra(rest, l.offset().cast_signed(), &mut allocator)?;
285 let length = expr.span().end;
286 l.advance(length);
287 Ok(length)
288}
289
290/// parse call arguments including the parentheses: `(a, b, ...c)`
291pub fn parse_call_arguments(l: &mut LexState) -> Result<u32, OxcDiagnostic> {
292 let source = l.peek_rest();
293 if !source.starts_with("(") {
294 return Err(err("Expected `(`", l.offset(), 1));
295 }
296
297 // Prepend `f` to make it a call expression: "f(a, b, c)"
298 let wrapped = format!("f{source}");
299
300 let mut allocator = Allocator::default();
301 let expr = parse_expr_extra(wrapped.as_str(), l.offset().cast_signed(), &mut allocator)?;
302
303 // Walk down the left side of the AST to find the CallExpression
304 let call_span =
305 find_leftmost_call(&expr).ok_or_else(|| err("Expected call expression", l.offset(), 1))?;
306
307 // Subtract the `f` prefix we added
308 let length = call_span.end - 1;
309 l.advance(length);
310 Ok(length)
311}
312
313/// parse variable binding. identifier or destructuring pattern
314/// also parses optional `: Type`
315pub fn parse_var_binding(l: &mut LexState) -> Result<u32, OxcDiagnostic> {
316 let offset = l.offset();
317 let source = l.peek_rest();
318 if source.is_empty() {
319 return Err(err("Expected variable binding", l.offset(), 1));
320 }
321
322 let mut allocator = Allocator::default();
323 let end = {
324 let expr = parse_expr_extra(source, l.offset().cast_signed(), &mut allocator)?;
325 let span = find_leftmost(&expr, LeftmostSearch::Assignment).unwrap();
326 span.end
327 };
328 allocator.reset();
329
330 {
331 let prefix = "function f(";
332 let source = format!("{prefix}{}) {{}}", &source[0..end as usize]);
333 parse_expr_extra(
334 source.as_str(),
335 l.offset().cast_signed() - (prefix.len().cast_signed() as i32),
336 &mut allocator,
337 )?;
338 }
339
340 l.advance(end);
341
342 let snapshot = l.offset();
343 l.skip_whitespace();
344 if l.expect(":").is_ok() {
345 l.skip_whitespace();
346 parse_type(l)?;
347 Ok(l.offset() - offset)
348 } else {
349 l.offset = snapshot;
350 Ok(snapshot - offset)
351 }
352}
353
354/// parses a type
355pub fn parse_type(l: &mut LexState) -> Result<u32, OxcDiagnostic> {
356 let mut allocator = Allocator::default();
357 let source = format!("T as {}", l.peek_rest());
358 let expr = parse_expr_extra(source.as_str(), l.offset().cast_signed(), &mut allocator)?;
359
360 let span = find_leftmost(&expr, LeftmostSearch::As).ok_or_else(|| {
361 err(
362 "Expected type expression",
363 l.offset(),
364 expr.span().end as usize,
365 )
366 })?;
367 let len = span.end - span.start;
368 l.advance(len);
369 Ok(len)
370}
371
372/// parses `(params): ReturnType { body }`
373pub fn parse_fn_params_and_body(l: &mut LexState) -> Result<u32, OxcDiagnostic> {
374 let source = l.peek_rest();
375 if !source.starts_with("(") {
376 return Err(err("Expected `(`", l.offset(), 1));
377 }
378
379 // Prepend `function f` to make it a function expression
380 let prefix = "function f";
381 let wrapped = format!("{prefix}{source}");
382
383 let mut allocator = Allocator::default();
384
385 let expr = parse_expr_extra(&wrapped, l.offset() as i32, &mut allocator)?;
386
387 // Find the function expression (walk left side of any binary/etc expressions)
388 let func_span = find_leftmost(&expr, LeftmostSearch::Function)
389 .ok_or_else(|| err("Expected function expression", l.offset(), 1))?;
390
391 // Subtract the prefix we added
392 let length = func_span.end - (prefix.len() as u32);
393 l.advance(length);
394 Ok(length)
395}
396
397/// Walk down the left/object side of an expression to find a CallExpression.
398fn find_leftmost_call(expr: &oxc_ast::ast::Expression) -> Option<oxc_span::Span> {
399 use oxc_ast::ast::Expression;
400
401 match expr {
402 Expression::CallExpression(call) => Some(call.span),
403 Expression::BinaryExpression(bin) => find_leftmost_call(&bin.left),
404 Expression::LogicalExpression(log) => find_leftmost_call(&log.left),
405 Expression::AssignmentExpression(assign) => find_leftmost_call(&assign.right),
406 Expression::ConditionalExpression(cond) => find_leftmost_call(&cond.test),
407 Expression::TaggedTemplateExpression(tag) => find_leftmost_call(&tag.tag),
408 Expression::ComputedMemberExpression(mem) => find_leftmost_call(&mem.object),
409 Expression::StaticMemberExpression(mem) => find_leftmost_call(&mem.object),
410 Expression::PrivateFieldExpression(mem) => find_leftmost_call(&mem.object),
411 _ => None,
412 }
413}
414
415#[derive(Debug, Clone, Copy, PartialEq)]
416enum LeftmostSearch {
417 As,
418 Function,
419 Assignment,
420}
421
422/// Walk down the left/object side of an expression to find a FunctionExpression.
423fn find_leftmost(expr: &oxc_ast::ast::Expression, kind: LeftmostSearch) -> Option<oxc_span::Span> {
424 use oxc_ast::ast::Expression::*;
425
426 match expr {
427 FunctionExpression(func) if kind == LeftmostSearch::Function => Some(func.span),
428 TSAsExpression(expr) if kind == LeftmostSearch::As => {
429 let mut expr = expr;
430 loop {
431 match &expr.expression {
432 TSAsExpression(nested) => expr = nested,
433 _ => return Some(expr.type_annotation.span()),
434 }
435 }
436 }
437 Identifier(_) | ObjectExpression(_) | ArrayExpression(_)
438 if kind == LeftmostSearch::Assignment =>
439 {
440 Some(expr.span())
441 }
442
443 AssignmentExpression(assign) => find_leftmost_assign(&assign.left, kind),
444 UpdateExpression(seq) => Some(seq.argument.span()),
445
446 TSAsExpression(expr) => find_leftmost(&expr.expression, kind),
447 TSNonNullExpression(expr) => find_leftmost(&expr.expression, kind),
448 TSSatisfiesExpression(expr) => find_leftmost(&expr.expression, kind),
449 TSInstantiationExpression(expr) => find_leftmost(&expr.expression, kind),
450 BinaryExpression(bin) => find_leftmost(&bin.left, kind),
451 LogicalExpression(log) => find_leftmost(&log.left, kind),
452 ConditionalExpression(cond) => find_leftmost(&cond.test, kind),
453 TaggedTemplateExpression(tag) => find_leftmost(&tag.tag, kind),
454 ComputedMemberExpression(mem) => find_leftmost(&mem.object, kind),
455 StaticMemberExpression(mem) => find_leftmost(&mem.object, kind),
456 PrivateFieldExpression(mem) => find_leftmost(&mem.object, kind),
457 CallExpression(call) => find_leftmost(&call.callee, kind),
458 SequenceExpression(seq) => find_leftmost(seq.expressions.first().unwrap(), kind),
459
460 BooleanLiteral(_)
461 | NullLiteral(_)
462 | NumericLiteral(_)
463 | BigIntLiteral(_)
464 | RegExpLiteral(_)
465 | StringLiteral(_)
466 | TemplateLiteral(_)
467 | Identifier(_)
468 | MetaProperty(_)
469 | Super(_)
470 | ArrayExpression(_)
471 | ArrowFunctionExpression(_)
472 | AwaitExpression(_)
473 | ChainExpression(_)
474 | ClassExpression(_)
475 | FunctionExpression(_)
476 | ImportExpression(_)
477 | NewExpression(_)
478 | ObjectExpression(_)
479 | ParenthesizedExpression(_)
480 | ThisExpression(_)
481 | UnaryExpression(_)
482 | YieldExpression(_)
483 | PrivateInExpression(_)
484 | JSXElement(_)
485 | JSXFragment(_)
486 | TSTypeAssertion(_) => None,
487 V8IntrinsicExpression(_) => unreachable!(),
488 }
489}
490
491fn find_leftmost_assign(
492 expr: &oxc_ast::ast::AssignmentTarget,
493 kind: LeftmostSearch,
494) -> Option<oxc_span::Span> {
495 use oxc_ast::ast::AssignmentTarget::*;
496
497 match expr {
498 TSAsExpression(expr) if kind == LeftmostSearch::As => Some(expr.type_annotation.span()),
499
500 AssignmentTargetIdentifier(_) | ArrayAssignmentTarget(_) | ObjectAssignmentTarget(_)
501 if kind == LeftmostSearch::Assignment =>
502 {
503 Some(expr.span())
504 }
505
506 AssignmentTargetIdentifier(_)
507 | TSAsExpression(_)
508 | TSSatisfiesExpression(_)
509 | TSNonNullExpression(_)
510 | TSTypeAssertion(_)
511 | ComputedMemberExpression(_)
512 | StaticMemberExpression(_)
513 | ArrayAssignmentTarget(_)
514 | ObjectAssignmentTarget(_)
515 | PrivateFieldExpression(_) => None,
516 }
517}
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522
523 fn parse_expr(source: &str) -> Result<u32, OxcDiagnostic> {
524 let mut l = LexState::new(source);
525 super::parse_expr(&mut l)
526 }
527
528 #[test]
529 fn test_simple_identifier() {
530 assert_eq!(parse_expr("foo"), Ok(3));
531 assert_eq!(parse_expr("bar123"), Ok(6));
532 }
533
534 #[test]
535 fn test_number_literals() {
536 assert_eq!(parse_expr("42"), Ok(2));
537 assert_eq!(parse_expr("3.14"), Ok(4));
538 assert_eq!(parse_expr("0xff"), Ok(4));
539 }
540
541 #[test]
542 fn test_string_literals() {
543 assert_eq!(parse_expr("\"hello\""), Ok(7));
544 assert_eq!(parse_expr("'world'"), Ok(7));
545 assert_eq!(parse_expr("`template`"), Ok(10));
546 }
547
548 #[test]
549 fn test_binary_expressions() {
550 assert_eq!(parse_expr("a + b"), Ok(5));
551 assert_eq!(parse_expr("x * y / z"), Ok(9));
552 }
553
554 #[test]
555 fn test_call_expression() {
556 assert_eq!(parse_expr("foo()"), Ok(5));
557 assert_eq!(parse_expr("foo(a, b)"), Ok(9));
558 assert_eq!(parse_expr("foo.bar()"), Ok(9));
559 }
560
561 #[test]
562 fn test_with_trailing_garbage() {
563 assert_eq!(parse_expr("foo > more garbage"), Ok(10));
564 assert_eq!(parse_expr("42 > stuff"), Ok(10));
565 assert_eq!(parse_expr("foo @@@"), Ok(3));
566 assert_eq!(parse_expr("foo 🩵"), Ok(3));
567 assert_eq!(parse_expr("foo, bar, @@@"), Ok(3));
568 }
569
570 #[test]
571 fn test_member_expression() {
572 assert_eq!(parse_expr("obj.prop"), Ok(8));
573 assert_eq!(parse_expr("a.b.c"), Ok(5));
574 assert_eq!(parse_expr("arr[0]"), Ok(6));
575 }
576
577 #[test]
578 fn test_arrow_function() {
579 assert_eq!(parse_expr("() => 42"), Ok(8));
580 assert_eq!(parse_expr("x => x + 1"), Ok(10));
581 assert_eq!(parse_expr("(a, b) => a + b"), Ok(15));
582 }
583
584 #[test]
585 fn test_object_literal() {
586 assert_eq!(parse_expr("{ a: 1 }"), Ok(8));
587 assert_eq!(parse_expr("{ a: 1, b: 2 }"), Ok(14));
588 }
589
590 #[test]
591 fn test_array_literal() {
592 assert_eq!(parse_expr("[1, 2, 3]"), Ok(9));
593 assert_eq!(parse_expr("[]"), Ok(2));
594 }
595
596 #[test]
597 fn test_empty_input() {
598 let result = parse_expr("");
599 assert!(result.is_err());
600 }
601
602 #[test]
603 fn test_invalid_syntax() {
604 let result = parse_expr("@@@");
605 assert!(result.is_err());
606 }
607
608 fn parse_expr_no_gt(source: &str) -> Result<u32, OxcDiagnostic> {
609 let mut l = LexState::new(source);
610 parse_expr_without_gt(&mut l)
611 }
612
613 #[test]
614 fn test_no_gt_simple() {
615 assert_eq!(parse_expr_no_gt("foo"), Ok(3));
616 assert_eq!(parse_expr_no_gt("42"), Ok(2));
617 assert_eq!(parse_expr_no_gt("a + b"), Ok(5));
618 }
619
620 #[test]
621 fn test_no_gt_stops_at_gt() {
622 assert_eq!(parse_expr_no_gt("a > b"), Ok(1)); // just "a"
623 assert_eq!(parse_expr_no_gt("foo > bar"), Ok(3)); // just "foo"
624 assert_eq!(parse_expr_no_gt("x + 1 > y"), Ok(5)); // "x + 1"
625 assert_eq!(parse_expr_no_gt("x + 1 > y > z > y"), Ok(5)); // "x + 1"
626 }
627
628 #[test]
629 fn test_no_gt_parens_protect() {
630 // Parentheses protect the `>`
631 assert_eq!(parse_expr_no_gt("(a > b)"), Ok(7));
632 assert_eq!(parse_expr_no_gt("(x > y) + z"), Ok(11));
633 }
634
635 #[test]
636 fn test_no_gt_brackets_protect() {
637 // Array access brackets protect the `>`
638 assert_eq!(parse_expr_no_gt("arr[a > b]"), Ok(10));
639 assert_eq!(parse_expr_no_gt("obj[x > 0]"), Ok(10));
640 }
641
642 #[test]
643 fn test_no_gt_call_parens_protect() {
644 // Function call parens protect the `>`
645 assert_eq!(parse_expr_no_gt("fn(a > b)"), Ok(9));
646 assert_eq!(parse_expr_no_gt("Math.max(x, y > 0 ? 1 : 0)"), Ok(26));
647 }
648
649 #[test]
650 fn test_no_gt_gte_ok() {
651 assert_eq!(parse_expr_no_gt("a >= b"), Ok(1));
652 assert_eq!(parse_expr_no_gt("x >= 0"), Ok(1));
653 }
654
655 #[test]
656 fn test_no_gt_shift_ok() {
657 assert_eq!(parse_expr_no_gt("a >> b"), Ok(1));
658 assert_eq!(parse_expr_no_gt("x >>> 2"), Ok(1));
659 }
660
661 fn parse_call_args(source: &str) -> Result<u32, OxcDiagnostic> {
662 let mut l = LexState::new(source);
663 parse_call_arguments(&mut l)
664 }
665
666 #[test]
667 fn test_call_args_empty() {
668 assert_eq!(parse_call_args("()"), Ok(2));
669 assert_eq!(parse_call_args("() garbage"), Ok(2));
670 }
671
672 #[test]
673 fn test_call_args_simple() {
674 assert_eq!(parse_call_args("(a)"), Ok(3));
675 assert_eq!(parse_call_args("(a, b)"), Ok(6));
676 assert_eq!(parse_call_args("(a, b, c)"), Ok(9));
677 }
678
679 #[test]
680 fn test_call_args_expressions() {
681 assert_eq!(parse_call_args("(1 + 2)"), Ok(7));
682 assert_eq!(parse_call_args("(a + b, c * d)"), Ok(14));
683 assert_eq!(parse_call_args("(foo())"), Ok(7));
684 }
685
686 #[test]
687 fn test_call_args_spread() {
688 assert_eq!(parse_call_args("(...args)"), Ok(9));
689 assert_eq!(parse_call_args("(a, ...rest)"), Ok(12));
690 }
691
692 #[test]
693 fn test_call_args_trailing_garbage() {
694 assert_eq!(parse_call_args("(a, b) + more"), Ok(6));
695 assert_eq!(parse_call_args("(x).foo"), Ok(3));
696 assert_eq!(parse_call_args("(a, b)[0]"), Ok(6));
697 }
698
699 #[test]
700 fn test_call_args_nested() {
701 assert_eq!(parse_call_args("(fn(a, b))"), Ok(10));
702 assert_eq!(parse_call_args("((a + b))"), Ok(9));
703 }
704
705 #[test]
706 fn test_call_args_no_paren() {
707 let result = parse_call_args("a, b)");
708 assert!(result.is_err());
709 }
710
711 fn parse_fn_params_body(source: &str) -> Result<u32, OxcDiagnostic> {
712 let mut l = LexState::new(source);
713 parse_fn_params_and_body(&mut l)
714 }
715
716 #[test]
717 fn test_fn_simple() {
718 assert_eq!(parse_fn_params_body("() {}"), Ok(5));
719 assert_eq!(parse_fn_params_body("() { }"), Ok(6));
720 }
721
722 #[test]
723 fn test_fn_with_params() {
724 assert_eq!(parse_fn_params_body("(a) {}"), Ok(6));
725 assert_eq!(parse_fn_params_body("(a, b) {}"), Ok(9));
726 assert_eq!(parse_fn_params_body("(a, b, c) {}"), Ok(12));
727 }
728
729 #[test]
730 fn test_fn_with_body() {
731 assert_eq!(parse_fn_params_body("() { return 42; }"), Ok(17));
732 assert_eq!(parse_fn_params_body("(x) { return x + 1; }"), Ok(21));
733 }
734
735 #[test]
736 fn test_fn_with_return_type() {
737 assert_eq!(parse_fn_params_body("(): void {}"), Ok(11));
738 assert_eq!(parse_fn_params_body("(): number { return 42; }"), Ok(25));
739 assert_eq!(
740 parse_fn_params_body("(a: number): string { return String(a); }"),
741 Ok(41)
742 );
743 }
744
745 #[test]
746 fn test_fn_with_typed_params() {
747 assert_eq!(parse_fn_params_body("(a: number) {}"), Ok(14));
748 assert_eq!(parse_fn_params_body("(a: string, b: number) {}"), Ok(25));
749 }
750
751 #[test]
752 fn test_fn_with_defaults() {
753 assert_eq!(parse_fn_params_body("(a = 1) {}"), Ok(10));
754 assert_eq!(parse_fn_params_body("(a: number = 1) {}"), Ok(18));
755 }
756
757 #[test]
758 fn test_fn_with_rest() {
759 assert_eq!(parse_fn_params_body("(...args) {}"), Ok(12));
760 assert_eq!(parse_fn_params_body("(a, ...rest) {}"), Ok(15));
761 }
762
763 #[test]
764 fn test_fn_trailing_garbage() {
765 // Should stop at the closing brace
766 assert_eq!(parse_fn_params_body("() {} garbage"), Ok(5));
767 assert_eq!(parse_fn_params_body("() {} + more"), Ok(5));
768 }
769
770 #[test]
771 fn test_fn_no_paren() {
772 let result = parse_fn_params_body("a) {}");
773 assert!(result.is_err());
774 }
775
776 fn parse_var_binding(source: &str) -> Result<u32, OxcDiagnostic> {
777 let mut l = LexState::new(source);
778 super::parse_var_binding(&mut l)
779 }
780
781 #[test]
782 fn test_var_binding_identifier() {
783 assert_eq!(parse_var_binding("foo"), Ok(3));
784 assert_eq!(parse_var_binding("bar123"), Ok(6));
785 assert_eq!(parse_var_binding("_private"), Ok(8));
786 }
787
788 #[test]
789 fn test_var_binding_with_type() {
790 assert_eq!(parse_var_binding("foo: string meow"), Ok(11));
791 assert_eq!(parse_var_binding("bar: number"), Ok(11));
792 assert_eq!(parse_var_binding("x: boolean"), Ok(10));
793 }
794
795 #[test]
796 fn test_var_binding_object_destructure() {
797 assert_eq!(parse_var_binding("{ a }"), Ok(5));
798 assert_eq!(parse_var_binding("{ a, b }"), Ok(8));
799 assert_eq!(parse_var_binding("{ a: x, b: y }"), Ok(14));
800 }
801
802 #[test]
803 fn test_var_binding_array_destructure() {
804 assert_eq!(parse_var_binding("[a]"), Ok(3));
805 assert_eq!(parse_var_binding("[a, b]"), Ok(6));
806 assert_eq!(parse_var_binding("[a, , b]"), Ok(8));
807 }
808
809 #[test]
810 fn test_var_binding_destructure_with_type() {
811 assert_eq!(parse_var_binding("{ a, b }: T abc"), Ok(11));
812 assert_eq!(parse_var_binding("[x, y]: [number, number], yolo"), Ok(24));
813 }
814
815 #[test]
816 fn test_var_binding_no_default() {
817 assert_eq!(parse_var_binding("foo = 123"), Ok(3));
818 assert_eq!(parse_var_binding("foo: string = 'hi'"), Ok(11));
819 }
820
821 #[test]
822 fn test_var_binding_empty() {
823 let result = parse_var_binding("");
824 assert!(result.is_err());
825 }
826
827 #[test]
828 fn test_var_binding_identifier_garbage() {
829 assert_eq!(parse_var_binding("foo meow meow meow"), Ok(3));
830 assert_eq!(parse_var_binding("foo|etc"), Ok(3));
831 assert_eq!(parse_var_binding("foo|etc, 1"), Ok(3));
832 assert_eq!(parse_var_binding("nya: string, 💥"), Ok(11));
833 }
834
835 #[test]
836 fn test_var_binding_identifier_invalid_destructure() {
837 assert!(parse_var_binding("{ x: y + 2 }").is_err());
838 assert!(parse_var_binding("[ y + 2 ]").is_err());
839 }
840
841 #[test]
842 fn test_var_binding_identifier_unicode() {
843 assert_eq!(parse_var_binding("café"), Ok(5));
844 }
845
846 #[test]
847 fn test_var_binding_equals() {
848 assert_eq!(parse_var_binding("foo = 1"), Ok(3));
849 assert_eq!(parse_var_binding("foo| of=1>"), Ok(3));
850 }
851
852 #[test]
853 fn test_stmt_function_with_garbage() {
854 let source = "function hello() {\n console.log(1);\n}\n\nrandom markdown garbage";
855 let end = scan_first_statement_forbid_trailing(source).unwrap();
856 assert_eq!(
857 &source[..end as usize],
858 "function hello() {\n console.log(1);\n}"
859 );
860 }
861
862 #[test]
863 fn test_stmt_import_with_garbage() {
864 let source = "import { foo } from 'bar';\n\n# markdown heading";
865 let end = scan_first_statement_forbid_trailing(source).unwrap();
866 assert_eq!(&source[..end as usize], "import { foo } from 'bar';");
867 }
868
869 #[test]
870 fn test_stmt_interface_with_garbage() {
871 let source = "interface Foo {\n bar: string;\n}\n\nsome text";
872 let end = scan_first_statement_forbid_trailing(source).unwrap();
873 assert_eq!(
874 &source[..end as usize],
875 "interface Foo {\n bar: string;\n}"
876 );
877 }
878
879 #[test]
880 fn test_stmt_const_declaration() {
881 let source = "const answer = 42;\n\n# Next section";
882 let end = scan_first_statement_forbid_trailing(source).unwrap();
883 assert_eq!(&source[..end as usize], "const answer = 42;");
884 }
885
886 #[test]
887 fn test_stmt_expression_statement() {
888 let source = "console.log('hello');\n\nmore content";
889 let end = scan_first_statement_forbid_trailing(source).unwrap();
890 assert_eq!(&source[..end as usize], "console.log('hello');");
891 }
892
893 #[test]
894 fn test_stmt_pure_garbage() {
895 let source = "random unexpected garbage";
896 let result = scan_first_statement_forbid_trailing(source);
897 assert!(result.is_err());
898 }
899
900 #[test]
901 fn test_stmt_multiline_function() {
902 let source = "function sort(items) {\n while (!isSorted()) {\n shuffle(items);\n }\n return items;\n}\n\n# Heading";
903 let end = scan_first_statement_forbid_trailing(source).unwrap();
904 assert!(source[..end as usize].contains("return items;"));
905 assert!(source[..end as usize].contains("}"));
906 }
907
908 #[test]
909 fn test_stmt_trailing_content_on_same_line() {
910 let source = "const x = 1; const y = 2;";
911 let result = scan_first_statement_forbid_trailing(source);
912 assert!(result.is_err());
913 }
914
915 #[test]
916 fn test_stmt_trailing_whitespace_ok() {
917 let source = "const x = 1; \n\nmore content";
918 let result = scan_first_statement_forbid_trailing(source);
919 assert!(result.is_ok());
920 }
921
922 #[test]
923 fn test_no_gt_with_marko_tags_after() {
924 assert_eq!(parse_expr_no_gt("'test'>"), Ok(6));
925 assert_eq!(parse_expr_no_gt("'a'><b>"), Ok(3));
926 assert_eq!(parse_expr_no_gt("'x'><b/>"), Ok(3));
927 assert_eq!(parse_expr_no_gt("'x'><b></b>"), Ok(3));
928 assert_eq!(parse_expr_no_gt("'x'></>"), Ok(3));
929 assert_eq!(parse_expr_no_gt("'x'>text</>"), Ok(3));
930 assert_eq!(parse_expr_no_gt("'x'><a/></>"), Ok(3));
931 assert_eq!(parse_expr_no_gt("'x'><a></a></>"), Ok(3));
932 assert_eq!(parse_expr_no_gt("'x'><a>b</a></>"), Ok(3));
933 assert_eq!(parse_expr_no_gt("'x'><a>b</a> c</>"), Ok(3));
934 }
935
936 #[test]
937 fn test_no_gt_double_quoted_string() {
938 assert_eq!(parse_expr_no_gt("\"hello\">>><stuff>"), Ok(7));
939 }
940
941 #[test]
942 fn test_no_gt_string_followed_by_gt() {
943 // Simple string then >
944 assert_eq!(parse_expr_no_gt("'test'>"), Ok(6));
945 assert_eq!(parse_expr_no_gt("\"test\">"), Ok(6));
946 }
947
948 #[test]
949 fn test_no_gt_string_then_jsx_like() {
950 assert_eq!(parse_expr_no_gt("'a'><b>"), Ok(3)); // just 'a'
951 assert_eq!(parse_expr_no_gt("'a'>b"), Ok(3)); // just 'a'
952 }
953
954 #[test]
955 fn test_no_gt_string_then_jsx_variations() {
956 assert_eq!(parse_expr_no_gt("'x'><b>c</b>"), Ok(3));
957 assert_eq!(parse_expr_no_gt("'x'><b/>"), Ok(3));
958 assert_eq!(parse_expr_no_gt("'x'><b></b>"), Ok(3));
959 assert_eq!(parse_expr_no_gt("'x'><b>c</>"), Ok(3));
960 assert_eq!(parse_expr_no_gt("'x'><code>aaa</code>"), Ok(3));
961 }
962
963 #[test]
964 fn test_no_gt_other_ops() {
965 assert_eq!(parse_expr_no_gt("3 + 4 ___"), Ok(5));
966 assert_eq!(parse_expr_no_gt("3 + 4 > 4 ___"), Ok(5));
967 assert_eq!(parse_expr_no_gt("3 + 4 < 4 ___"), Ok(9));
968 assert_eq!(parse_expr_no_gt("3 < 4 ___"), Ok(5));
969 }
970}