| author | |
| committer | |
| log | a4577db2eb8de0f1edf734aa8d476a349b7e8182 |
| tree | 0c7a3389a40fd2100de62a5a8f435a4f06347067 |
| parent | a4a2b9e40b546078cae57f9ddc95701fc538419d |
| signature |
6 files changed, 82 insertions(+), 14 deletions(-)
AGENTS.md+5| ... | @@ -15,6 +15,11 @@ be extremely hard to succeed. | ... | @@ -15,6 +15,11 @@ be extremely hard to succeed. |
| 15 | - please maintain objective, high quality tests with `cargo test` | 15 | - please maintain objective, high quality tests with `cargo test` |
| 16 | - never execute `git` commands. if you have broken your environment or are | 16 | - never execute `git` commands. if you have broken your environment or are |
| 17 | unsure how to continue, yield to the user. | 17 | unsure how to continue, yield to the user. |
| 18 | - if you are messing with the ts parsing helpers, remember to specifically care | ||
| 19 | about edge cases. you cannot write any manual lexer because you will almost | ||
| 20 | certainly implement it wrong (such as a brace close detection via depth | ||
| 21 | tracking). this is annoying but what we have to do since oxc doesn't expose | ||
| 22 | its internals. `typescript.rs` is full of clever solutions. | ||
| 18 | 23 | ||
| 19 | ## marko v6 tag syntax legend | 24 | ## marko v6 tag syntax legend |
| 20 | 25 |
ARCHITECTURE.md+4| ... | @@ -2,6 +2,10 @@ | ... | @@ -2,6 +2,10 @@ |
| 2 | 2 | ||
| 3 | - `src/lib.rs` - main library, primary function `transform` | 3 | - `src/lib.rs` - main library, primary function `transform` |
| 4 | - `src/main.rs` - main cli | 4 | - `src/main.rs` - main cli |
| 5 | - `src/typescript.rs` - wrappers around oxc parser to expose parsers for | ||
| 6 | different subsets of the ast, including a much more garbage-forgiving | ||
| 7 | expression parser. | ||
| 8 | - `src/marko.rs` - general marko tag parser | ||
| 5 | - `src/plugin/` - `markdown-it` plugin and all rules | 9 | - `src/plugin/` - `markdown-it` plugin and all rules |
| 6 | 10 | ||
| 7 | markdown-it does not support failiable plugins, so instead errors are lowered | 11 | markdown-it does not support failiable plugins, so instead errors are lowered |
src/typescript.rs+46-12| ... | @@ -177,23 +177,57 @@ pub fn parse_expr(l: &mut LexState) -> Result<usize, OxcDiagnostic> { | ... | @@ -177,23 +177,57 @@ pub fn parse_expr(l: &mut LexState) -> Result<usize, OxcDiagnostic> { |
| 177 | pub fn parse_expr_without_gt(l: &mut LexState) -> Result<usize, OxcDiagnostic> { | 177 | pub fn parse_expr_without_gt(l: &mut LexState) -> Result<usize, OxcDiagnostic> { |
| 178 | use oxc_ast::ast::{BinaryOperator, Expression}; | 178 | use oxc_ast::ast::{BinaryOperator, Expression}; |
| 179 | 179 | ||
| 180 | let rest = l.peek_rest(); | ||
| 180 | let mut allocator = Allocator::new(); | 181 | let mut allocator = Allocator::new(); |
| 181 | let expr = parse_expr_extra(l.peek_rest(), l.offset().cast_signed(), &mut allocator)?; | ||
| 182 | 182 | ||
| 183 | let mut e = &expr; | 183 | // First, try parsing the full source and using AST walking to strip trailing `>`. |
| 184 | while let Expression::BinaryExpression(bin) = e { | 184 | // This handles balanced cases like `arr[a > 0]` and `fn(a > b)` correctly. |
| 185 | if bin.operator == BinaryOperator::GreaterThan | 185 | if let Ok(expr) = parse_expr_extra(rest, l.offset().cast_signed(), &mut allocator) { |
| 186 | || bin.operator == BinaryOperator::GreaterEqualThan | 186 | let mut e = &expr; |
| 187 | || bin.operator == BinaryOperator::ShiftRight | 187 | while let Expression::BinaryExpression(bin) = e { |
| 188 | || bin.operator == BinaryOperator::ShiftRightZeroFill | 188 | if bin.operator == BinaryOperator::GreaterThan |
| 189 | { | 189 | || bin.operator == BinaryOperator::GreaterEqualThan |
| 190 | e = &bin.left; | 190 | || bin.operator == BinaryOperator::ShiftRight |
| 191 | } else { | 191 | || bin.operator == BinaryOperator::ShiftRightZeroFill |
| 192 | break; | 192 | { |
| 193 | e = &bin.left; | ||
| 194 | } else { | ||
| 195 | break; | ||
| 196 | } | ||
| 197 | } | ||
| 198 | |||
| 199 | let length = e.span().end as usize; | ||
| 200 | l.advance(length); | ||
| 201 | return Ok(length); | ||
| 202 | } | ||
| 203 | |||
| 204 | // If full parse failed (e.g., due to JSX-like content after the expression), | ||
| 205 | // try parsing prefixes ending at each `>` position. | ||
| 206 | let gt_positions: Vec<usize> = rest | ||
| 207 | .char_indices() | ||
| 208 | .filter(|(_, c)| *c == '>') | ||
| 209 | .map(|(i, _)| i) | ||
| 210 | .collect(); | ||
| 211 | |||
| 212 | // Try from longest to shortest prefix, return first success | ||
| 213 | for &pos in gt_positions.iter().rev() { | ||
| 214 | let candidate = &rest[..pos]; | ||
| 215 | allocator.reset(); | ||
| 216 | if let Ok(expr) = parse_expr_extra(candidate, l.offset().cast_signed(), &mut allocator) { | ||
| 217 | // Check that the parse consumed most of the candidate (not just a prefix) | ||
| 218 | let length = expr.span().end as usize; | ||
| 219 | // If the expression spans close to the full candidate, use it | ||
| 220 | if length >= pos.saturating_sub(1) { | ||
| 221 | l.advance(length); | ||
| 222 | return Ok(length); | ||
| 223 | } | ||
| 193 | } | 224 | } |
| 194 | } | 225 | } |
| 195 | 226 | ||
| 196 | let length = e.span().end as usize; | 227 | // Last resort: return error from the original parse attempt |
| 228 | allocator.reset(); | ||
| 229 | let expr = parse_expr_extra(rest, l.offset().cast_signed(), &mut allocator)?; | ||
| 230 | let length = expr.span().end as usize; | ||
| 197 | l.advance(length); | 231 | l.advance(length); |
| 198 | Ok(length) | 232 | Ok(length) |
| 199 | } | 233 | } |
tests/fixtures.rs-2| ... | @@ -148,13 +148,11 @@ fn fixture_17_consecutive_statements() { | ... | @@ -148,13 +148,11 @@ fn fixture_17_consecutive_statements() { |
| 148 | run_fixture("17-consecutive-statements"); | 148 | run_fixture("17-consecutive-statements"); |
| 149 | } | 149 | } |
| 150 | 150 | ||
| 151 | #[ignore = "known bug"] | ||
| 152 | #[test] | 151 | #[test] |
| 153 | fn fixture_18_multiline_tag_with_value() { | 152 | fn fixture_18_multiline_tag_with_value() { |
| 154 | run_fixture("18-multiline-tag-with-value"); | 153 | run_fixture("18-multiline-tag-with-value"); |
| 155 | } | 154 | } |
| 156 | 155 | ||
| 157 | #[ignore = "known bug"] | ||
| 158 | #[test] | 156 | #[test] |
| 159 | fn fixture_19_extra_cases() { | 157 | fn fixture_19_extra_cases() { |
| 160 | run_fixture("19-extra-cases"); | 158 | run_fixture("19-extra-cases"); |
tests/fixtures/19-extra-cases.marko+12| ... | @@ -1,3 +1,15 @@ | ... | @@ -1,3 +1,15 @@ |
| 1 | <tag attr=(value | 1 | <tag attr=(value |
| 2 | > | 2 | > |
| 3 | 5)> | 3 | 5)> |
| 4 | </> | ||
| 5 | <tag attr=(value | ||
| 6 | |||
| 7 | **content** | ||
| 8 | |||
| 9 | 5 | ||
| 10 | |||
| 11 | > | ||
| 12 | |||
| 13 | 5)> | ||
| 14 | <p><strong>content</strong></p> | ||
| 15 | </> |
tests/fixtures/19-extra-cases.mdo+15| ... | @@ -2,3 +2,18 @@ | ... | @@ -2,3 +2,18 @@ |
| 2 | <tag attr=(value | 2 | <tag attr=(value |
| 3 | > | 3 | > |
| 4 | 5)> | 4 | 5)> |
| 5 | </> | ||
| 6 | |||
| 7 | <tag attr=(value | ||
| 8 | |||
| 9 | **content** | ||
| 10 | |||
| 11 | 5 | ||
| 12 | |||
| 13 | > | ||
| 14 | |||
| 15 | 5)> | ||
| 16 | |||
| 17 | **content** | ||
| 18 | |||
| 19 | </> |