From 42a5c17e506a403cb512b3b0911376820175109a Mon Sep 17 00:00:00 2001 From: clover caruso Date: Mon, 16 Feb 2026 20:48:18 -0800 Subject: [PATCH] bugfixing --- lib/jsr.json | 2 +- src/outline.rs | 2 +- src/plugin/tags.rs | 59 +++++++++++++++++++++++++++++++++ src/plugin/template.rs | 3 +- src/typescript.rs | 74 +++++++++++++++++++++++++++++++++++++----- 5 files changed, 128 insertions(+), 12 deletions(-) diff --git a/lib/jsr.json b/lib/jsr.json index 0eb5e0b26c128ea7281dab8b0b3bbdff2abea360..b7be3b5673a4857b728d4ebca2f56488ced2a87f 100644 --- a/lib/jsr.json +++ b/lib/jsr.json @@ -1,6 +1,6 @@ { "name": "@clo/markodown", - "version": "1.0.0-rc.5", + "version": "1.0.0-rc.6", "license": "ISC", "exports": { ".": "./mod.ts", diff --git a/src/outline.rs b/src/outline.rs index 4625eee05d7e5c20337e40e5c25346eec0a8cb67..8ac8747b8edae387402e42ad0b5991d13d104d20 100644 --- a/src/outline.rs +++ b/src/outline.rs @@ -11,7 +11,7 @@ use std::collections::HashSet; use crate::marko_ast::AttributeValue; use crate::plugin::tags::{MarkoBlockComplete, MarkoOpen, MarkoOpenWithText}; -use crate::typescript::{parse_expr, parse_expr_extra}; +use crate::typescript::parse_expr_extra; /// A heading entry for the outline, with content reference for hoisting. #[derive(Debug, Clone)] diff --git a/src/plugin/tags.rs b/src/plugin/tags.rs index 6776720b69d3e2fabf45dac4f1834e17ca1dafd7..fdce64a09d4d7ddcda2a432eac126316e7fea080 100644 --- a/src/plugin/tags.rs +++ b/src/plugin/tags.rs @@ -372,3 +372,62 @@ impl InlineRule for Rule { } } } + +#[cfg(test)] +mod tests { + use super::find_same_line_close; + use crate::marko; + + #[test] + fn test_find_same_line_close_with_nested_tags() { + // Should find after the nested + let content = "aaa mmmm"; + let result = find_same_line_close(content, "h3"); + assert!(result.is_some(), "Should find closing tag"); + let (before, close) = result.unwrap(); + assert_eq!(before, "aaa mmmm"); + assert_eq!(close, ""); + } + + #[test] + fn test_block_tag_with_inline_content() { + // the string + // 'bbbbbbb'>aaa mmmm + // + // 'string' > id < /regexp/ > + let src = "

aaa mmmm"; + let open = marko::parse_open(src).unwrap(); + assert_eq!(open.tag_name(), "h3"); + assert_eq!(open.src, "

"); + + // The rest after the tag + let rest = &src[open.src.len()..]; + assert_eq!(rest, "aaa mmmm"); + + // find_same_line_close should find the at the end + let result = find_same_line_close(rest, "h3"); + assert!(result.is_some(), "Should find closing tag for h3"); + let (content, close) = result.unwrap(); + assert_eq!(content, "aaa mmmm"); + assert_eq!(close, ""); + } + + #[test] + fn test_nested_greater_than() { + let src = "

aaa mmmm"; + let open = marko::parse_open(src).unwrap(); + assert_eq!(open.tag_name(), "h3"); + assert_eq!(open.src, "

"); + + // The rest after the tag + let rest = &src[open.src.len()..]; + assert_eq!(rest, "aaa mmmm"); + + // find_same_line_close should find the at the end + let result = find_same_line_close(rest, "h3"); + assert!(result.is_some(), "Should find closing tag for h3"); + let (content, close) = result.unwrap(); + assert_eq!(content, "aaa mmmm"); + assert_eq!(close, ""); + } +} diff --git a/src/plugin/template.rs b/src/plugin/template.rs index c601749aa54ab2707a97ca5ef82ec5c89b8bfb87..84452f8ea9e61f694773708d8672fc0835395d6f 100644 --- a/src/plugin/template.rs +++ b/src/plugin/template.rs @@ -25,7 +25,8 @@ impl InlineRule for Rule { let allocator = Allocator::default(); let source_type = SourceType::default() .with_module(true) - .with_typescript(true); + .with_typescript(true) + .with_jsx(false); let expr = match Parser::new(&allocator, expr_source, source_type).parse_expression() { Ok(expr) => expr, diff --git a/src/typescript.rs b/src/typescript.rs index 5fb3d586715645556e77667fda6806b0c18a8ff5..2c32b4b6edf1768ae96d06557aecc72d2c1312bd 100644 --- a/src/typescript.rs +++ b/src/typescript.rs @@ -39,7 +39,8 @@ fn parse_stmt_extra<'alloc, 'src: 'alloc>( let source_type = SourceType::default() .with_module(true) - .with_typescript(true); + .with_typescript(true) + .with_jsx(false); let mut result = oxc_parser::Parser::new(allocator, source, source_type).parse(); if !result.errors.is_empty() && result.program.body.is_empty() { @@ -111,7 +112,8 @@ pub fn parse_expr_extra<'alloc, 'src: 'alloc>( let source_type = SourceType::default() .with_module(true) - .with_typescript(true); + .with_typescript(true) + .with_jsx(false); let expr = match oxc_parser::Parser::new(allocator, source, source_type).parse_expression() { Ok(expr) => expr, @@ -185,25 +187,30 @@ pub fn parse_expr_without_gt(l: &mut LexState) -> Result { let rest = l.peek_rest(); let mut allocator = Allocator::new(); - // First, try parsing the full source and using AST walking to strip trailing `>`. + // First, try parsing the full source and using AST walking to find the earliest `>`. // This handles balanced cases like `arr[a > 0]` and `fn(a > b)` correctly. + // We walk down the left spine of binary expressions, looking for any `>` operator. + // When we find one, we record the span of its left operand. We keep walking to find + // the innermost `>` (which appears earliest in the source). if let Ok(expr) = parse_expr_extra(rest, l.offset().cast_signed(), &mut allocator) { let mut e = &expr; + let mut result_end = expr.span().end; // default: whole expression + while let Expression::BinaryExpression(bin) = e { if bin.operator == BinaryOperator::GreaterThan || bin.operator == BinaryOperator::GreaterEqualThan || bin.operator == BinaryOperator::ShiftRight || bin.operator == BinaryOperator::ShiftRightZeroFill { - e = &bin.left; - } else { - break; + // Found a `>`, update result to be left side's span + result_end = bin.left.span().end; } + // Continue walking left to find any nested `>` operators + e = &bin.left; } - let length = e.span().end; - l.advance(length); - return Ok(length); + l.advance(result_end); + return Ok(result_end); } // If full parse failed (e.g., due to JSX-like content after the expression), @@ -871,4 +878,53 @@ mod tests { let result = scan_first_statement_forbid_trailing(source); assert!(result.is_ok()); } + + #[test] + fn test_no_gt_with_marko_tags_after() { + assert_eq!(parse_expr_no_gt("'test'>"), Ok(6)); + assert_eq!(parse_expr_no_gt("'a'>"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>text"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>b"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>b c"), Ok(3)); + } + + #[test] + fn test_no_gt_double_quoted_string() { + assert_eq!(parse_expr_no_gt("\"hello\">>>"), Ok(7)); + } + + #[test] + fn test_no_gt_string_followed_by_gt() { + // Simple string then > + assert_eq!(parse_expr_no_gt("'test'>"), Ok(6)); + assert_eq!(parse_expr_no_gt("\"test\">"), Ok(6)); + } + + #[test] + fn test_no_gt_string_then_jsx_like() { + assert_eq!(parse_expr_no_gt("'a'>"), Ok(3)); // just 'a' + assert_eq!(parse_expr_no_gt("'a'>b"), Ok(3)); // just 'a' + } + + #[test] + fn test_no_gt_string_then_jsx_variations() { + assert_eq!(parse_expr_no_gt("'x'>c"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>c"), Ok(3)); + assert_eq!(parse_expr_no_gt("'x'>aaa"), Ok(3)); + } + + #[test] + fn test_no_gt_other_ops() { + assert_eq!(parse_expr_no_gt("3 + 4 ___"), Ok(5)); + assert_eq!(parse_expr_no_gt("3 + 4 > 4 ___"), Ok(5)); + assert_eq!(parse_expr_no_gt("3 + 4 < 4 ___"), Ok(9)); + assert_eq!(parse_expr_no_gt("3 < 4 ___"), Ok(5)); + } } -- 2.54.0