From a4a2b9e40b546078cae57f9ddc95701fc538419d Mon Sep 17 00:00:00 2001 From: clover caruso Date: Thu, 12 Feb 2026 22:22:37 -0800 Subject: [PATCH] chore: drop tree sitter, fix bugs --- Cargo.lock | 37 ----- Cargo.toml | 3 - examples/next-js.mdo | 7 +- src/lib.rs | 6 + src/marko.rs | 141 ++++++++-------- src/plugin/statement.rs | 47 ++---- src/typescript.rs | 154 ++++++++++++------ tests/fixtures.rs | 12 ++ tests/fixtures/13-kitchen-sink.marko | 3 +- tests/fixtures/13-kitchen-sink.mdo | 18 +- .../18-multiline-tag-with-value.marko | 6 + .../fixtures/18-multiline-tag-with-value.mdo | 9 + tests/fixtures/19-extra-cases.marko | 3 + tests/fixtures/19-extra-cases.mdo | 4 + 14 files changed, 248 insertions(+), 202 deletions(-) create mode 100644 tests/fixtures/18-multiline-tag-with-value.marko create mode 100644 tests/fixtures/18-multiline-tag-with-value.mdo create mode 100644 tests/fixtures/19-extra-cases.marko create mode 100644 tests/fixtures/19-extra-cases.mdo diff --git a/Cargo.lock b/Cargo.lock index 844c02464fb2f9bfe7f23242065dc9b2904ea0c7..3a775c3a288b2a2436fad8fdfec58895a0be1e05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -403,8 +403,6 @@ dependencies = [ "serde", "serde_json", "serde_yml", - "tree-sitter", - "tree-sitter-typescript", ] [[package]] @@ -924,12 +922,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "streaming-iterator" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" - [[package]] name = "strsim" version = "0.11.1" @@ -1004,35 +996,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tree-sitter" -version = "0.24.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5387dffa7ffc7d2dae12b50c6f7aab8ff79d6210147c6613561fc3d474c6f75" -dependencies = [ - "cc", - "regex", - "regex-syntax", - "streaming-iterator", - "tree-sitter-language", -] - -[[package]] -name = "tree-sitter-language" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009994f150cc0cd50ff54917d5bc8bffe8cad10ca10d81c34da2ec421ae61782" - -[[package]] -name = "tree-sitter-typescript" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5f76ed8d947a75cc446d5fccd8b602ebf0cde64ccf2ffa434d873d7a575eff" -dependencies = [ - "cc", - "tree-sitter-language", -] - [[package]] name = "unicode-bidi" version = "0.3.18" diff --git a/Cargo.toml b/Cargo.toml index 30b9afddd2c4ca96c66d0acc281fb2d9d5a2a062..47edb4de13565e2dd87ae02743e9fbd64fa0c5ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,3 @@ oxc_parser = "0.96.0" oxc_span = "0.96.0" serde_json = "1.0" serde_yml = "0.0.12" -tree-sitter = "0.24" -tree-sitter-typescript = "0.23" - diff --git a/examples/next-js.mdo b/examples/next-js.mdo index c22c29f10601459982d195e421731f594e451ca0..dbb63eae159cab3cf042f92fd2bf8efd20d35e02 100644 --- a/examples/next-js.mdo +++ b/examples/next-js.mdo @@ -656,7 +656,7 @@ object, but instead uses inline JSX to fill that gap. [lib-jsr]: https://jsr.io/@clo/lib [lib-npm]: https://npmjs.com/@paperclover/lib -

`next/og` is Good Too +

next/og is Good Too No strong opinions. I just want to remind everyone that the `@vercel/og` package exists. @@ -742,8 +742,9 @@ subscribe to the email list:


- -
2025 (c) paper clover
+
+2025 (c) paper clover +
diff --git a/src/lib.rs b/src/lib.rs index dfae36e9693e8a68dd3af15f1d9031cb824818f4..4630e702fffdff2fc963ab208bc37eb94a1ad3b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,8 @@ pub mod marko; pub mod plugin; pub mod typescript; +use std::borrow::Cow; + use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; use plugin::tags::{MarkoClose, MarkoOpen}; @@ -223,3 +225,7 @@ fn validate_marko_tags(node: &markdown_it::Node, errors: &mut Vec ); } } + +pub fn err>>(str: T, offset: usize, length: usize) -> OxcDiagnostic { + OxcDiagnostic::error(str).and_label(LabeledSpan::new(None, offset, length)) +} diff --git a/src/marko.rs b/src/marko.rs index 366530a89b38bf24daeaabab2c5288f9edec37c1..9240d56928b3c6cbeb049203f68658f7d469f166 100644 --- a/src/marko.rs +++ b/src/marko.rs @@ -1,9 +1,12 @@ -use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; +use oxc_diagnostics::OxcDiagnostic; use oxc_span::Span; -use crate::typescript::{ - parse_call_arguments, parse_expr, parse_expr_without_gt, parse_fn_params_and_body, - parse_var_binding, +use crate::{ + err, + typescript::{ + parse_call_arguments, parse_expr, parse_expr_without_gt, parse_fn_params_and_body, + parse_var_binding, + }, }; #[derive(Debug, Clone, PartialEq, Eq)] @@ -100,13 +103,7 @@ impl<'a> LexState<'a> { self.offset += 1; return Ok(b); } - Err( - OxcDiagnostic::error("Unexpected end of file").and_label(LabeledSpan::new( - None, - self.offset, - 1, - )), - ) + Err(err("Unexpected end of file", self.offset, 1)) } pub fn expect(&mut self, expected: &str) -> Result<(), OxcDiagnostic> { @@ -114,13 +111,7 @@ impl<'a> LexState<'a> { self.offset += expected.len(); Ok(()) } else { - Err( - OxcDiagnostic::error(format!("Expected {expected}")).and_label(LabeledSpan::new( - None, - self.offset, - 1, - )), - ) + Err(err(format!("Expected {expected}"), self.offset, 1)) } } @@ -153,10 +144,7 @@ pub fn parse_open(src: &str) -> Result { if byte == b'#' { if has_id { - return Err( - OxcDiagnostic::error("Cannot specify two ID shorthands".to_string()) - .and_label(LabeledSpan::new(None, offset, name.len())), - ); + return Err(err("Cannot specify two ID shorthands", offset, name.len())); } has_id = true; } @@ -174,6 +162,9 @@ pub fn parse_open(src: &str) -> Result { match byte { b'|' => { // parameters + if has_js_params { + return Err(err("Params already specified", offset, 1)); + } l.offset += 1; l.skip_whitespace(); while l.peek_byte() != Some(b'|') && l.peek_byte().is_some() { @@ -192,6 +183,9 @@ pub fn parse_open(src: &str) -> Result { } b'(' => { // arguments + if has_js_arguments { + return Err(err("Function call already specified", offset, 1)); + } // TODO: METHOD SHORTHAND println!("{}", l.peek_rest()); parse_call_arguments(&mut l)?; @@ -202,6 +196,9 @@ pub fn parse_open(src: &str) -> Result { if l.peek_rest().starts_with("/>") { break; } + if has_js_variable { + return Err(err("Variable already specified", offset, 1)); + } l.offset += 1; l.skip_whitespace(); parse_var_binding(&mut l)?; @@ -212,45 +209,55 @@ pub fn parse_open(src: &str) -> Result { } if is_attribute_tag && byte == b'/' { - return Err( - OxcDiagnostic::error("Attribute tags do not support variables") - .and_label(LabeledSpan::new(None, offset, l.offset - offset)), - ); + return Err(err( + "Attribute tags do not support variables", + offset, + l.offset - offset, + )); } if is_attribute_tag && byte == b'(' { - return Err( - OxcDiagnostic::error("Attribute tags do not support arguments") - .and_label(LabeledSpan::new(None, offset, l.offset - offset)), - ); + return Err(err( + "Attribute tags do not support arguments", + offset, + l.offset - offset, + )); } } - // keyless value - if l.peek_byte() == Some(b'=') { - l.offset += 1; - l.skip_whitespace(); - parse_expr_without_gt(&mut l)?; - l.skip_whitespace(); - } - - // attributes let mut self_closing = false; - loop { - match l.peek_byte() { - None => return Err(l.expect_byte().expect_err("err")), - Some(b'>') => { - l.advance(1); - break; - } - Some(_) => { - if l.expect("/>").is_ok() { - self_closing = true; + if !has_js_arguments { + // keyless value + if l.peek_byte() == Some(b'=') { + l.offset += 1; + l.skip_whitespace(); + parse_expr_without_gt(&mut l)?; + l.skip_whitespace(); + } + + // attributes + loop { + match l.peek_byte() { + None => return Err(l.expect_byte().expect_err("err")), + Some(b'>') => { + l.advance(1); break; } - parse_attribute(&mut l)?; - l.skip_whitespace(); + Some(_) => { + if l.expect("/>").is_ok() { + self_closing = true; + break; + } + parse_attribute(&mut l)?; + l.skip_whitespace(); + } } } + } else { + if l.expect("/>").is_ok() { + self_closing = true; + } else { + l.expect(">")?; + } } Ok(Open { @@ -299,8 +306,7 @@ fn parse_tag_name<'a>(l: &mut LexState<'a>) -> Result<&'a str, OxcDiagnostic> { if !tag_name.is_empty() { Ok(tag_name) } else { - Err(OxcDiagnostic::error("Expected tag name".to_string()) - .and_label(LabeledSpan::new(None, l.offset, 1))) + Err(err("Expected tag name", l.offset, 1)) } } @@ -316,8 +322,7 @@ fn parse_attr_name<'a>(l: &mut LexState<'a>) -> Result<&'a str, OxcDiagnostic> { if !tag_name.is_empty() { Ok(tag_name) } else { - Err(OxcDiagnostic::error("Expected attribute name".to_string()) - .and_label(LabeledSpan::new(None, l.offset, 1))) + Err(err("Expected attribute name", l.offset, 1)) } } @@ -348,10 +353,7 @@ fn parse_attribute(l: &mut LexState) -> Result<(), OxcDiagnostic> { parse_fn_params_and_body(l)?; l.skip_whitespace(); } else if !has_ws_or_end { - return Err( - OxcDiagnostic::error("Expected whitespace or '>'".to_string()) - .and_label(LabeledSpan::new(None, l.offset, 1)), - ); + return Err(err("Expected whitespace or '>'", l.offset, 1)); } Ok(()) @@ -375,12 +377,15 @@ fn parse_class_name_shorthand<'a>( if !tag_name.is_empty() { Ok(tag_name) } else { - Err(OxcDiagnostic::error(if id { - "Expected id" - } else { - "Expected class name" - }) - .and_label(LabeledSpan::new(None, l.offset, 1))) + Err(err( + if id { + "Expected id" + } else { + "Expected class name" + }, + l.offset, + 1, + )) } } @@ -1095,10 +1100,10 @@ mod tests { #[test] fn test_combined_complex() { assert_eq!( - parse_open(""), + parse_open(""), Ok(Open { tag_name: "my-tag", - content: "", + content: "", self_closing: false, }) ); @@ -1270,13 +1275,11 @@ mod tests { } #[test] - #[ignore = "need to fix parse_var_binding"] fn test_tag_params_simple() { parse_open("").unwrap(); } #[test] - #[ignore = "need to fix parse_var_binding"] fn test_tag_params_destructure() { parse_open("").unwrap(); } diff --git a/src/plugin/statement.rs b/src/plugin/statement.rs index 4ff40d7dc01c775acd8ef43ee1f4f67d7aba5930..55c59821e1205be090becda07237ad741c33d4db 100644 --- a/src/plugin/statement.rs +++ b/src/plugin/statement.rs @@ -3,7 +3,7 @@ use markdown_it::Node; use crate::adjust_err; use crate::plugin::{ErrorBlock, RawBlock}; -use crate::typescript::find_statement_extent; +use crate::typescript::scan_first_statement_forbid_trailing; /// Parse JavaScript statements (import, export, static X, server X, client X) pub struct Rule; @@ -11,20 +11,6 @@ pub struct Rule; const KEYWORDS: &[&str] = &["import ", "export ", "static ", "server ", "client "]; const MARKO_PREFIXES: &[&str] = &["static ", "server ", "client "]; -/// Check if a line is a valid statement (starts with keyword and parses successfully). -fn is_valid_statement(line: &str) -> bool { - let trimmed = line.trim_start(); - let Some(keyword) = KEYWORDS.iter().find(|k| trimmed.starts_with(*k)) else { - return false; - }; - let keyword_trim = MARKO_PREFIXES - .iter() - .find(|k| *k == keyword) - .map(|k| k.len()) - .unwrap_or(0); - find_statement_extent(&trimmed[keyword_trim..]).is_ok() -} - impl BlockRule for Rule { fn run(state: &mut BlockState) -> Option<(Node, usize)> { if state.line >= state.line_max || state.blk_indent > 0 { @@ -37,9 +23,11 @@ impl BlockRule for Rule { // Statements must start a new block - they can't be in the middle of a paragraph. // Allow if: start of document, previous line is empty, or previous line is a valid statement. if state.line > 0 { - let prev_line = state.get_line(state.line - 1); - let prev_is_empty = prev_line.trim().is_empty(); - if !prev_is_empty && !is_valid_statement(prev_line) { + let prev_line = state.get_line(state.line - 1).trim(); + if !prev_line.is_empty() + && prev_line != "---" + && !KEYWORDS.iter().any(|k| prev_line.starts_with(*k)) + { return None; } } @@ -52,17 +40,18 @@ impl BlockRule for Rule { let unbounded_src = &state.src[state.line_offsets[state.line].first_nonspace..]; - let statement_end = match find_statement_extent(&unbounded_src[keyword_trim..]) { - Ok(ok) => ok, - Err(err) => { - return Some(( - Node::new(ErrorBlock { - errors: vec![adjust_err(err, keyword_trim.cast_signed())], - }), - 1, - )); - } - }; + let statement_end = + match scan_first_statement_forbid_trailing(&unbounded_src[keyword_trim..]) { + Ok(ok) => ok, + Err(err) => { + return Some(( + Node::new(ErrorBlock { + errors: vec![adjust_err(err, keyword_trim.cast_signed())], + }), + 1, + )); + } + }; let total_end = keyword_trim + statement_end; let content = &unbounded_src[0..unbounded_src.len().min(total_end + 1)]; diff --git a/src/typescript.rs b/src/typescript.rs index f8185edfc720c5766bd1df2a6db077814c6939a2..ca3281db3ab6b68132fbac2cfa2e3dba97d53765 100644 --- a/src/typescript.rs +++ b/src/typescript.rs @@ -1,53 +1,93 @@ //! implement ts partial parsing helpers -use crate::{adjust_err, marko::LexState}; +use crate::{adjust_err, err, marko::LexState}; use oxc_allocator::Allocator; -use oxc_ast::ast::Expression; +use oxc_ast::ast::{Expression, Statement}; use oxc_diagnostics::{LabeledSpan, OxcDiagnostic}; use oxc_span::{GetSpan, SourceType}; -use std::borrow::Cow; -use tree_sitter::Parser; -fn err>>(str: T, offset: usize, length: usize) -> OxcDiagnostic { - OxcDiagnostic::error(str).and_label(LabeledSpan::new(None, offset, length)) +/// TODO: Replace with oxc parser, but oxc is tough here +pub fn scan_first_statement_forbid_trailing(source: &str) -> Result { + let mut allocator = Allocator::new(); + let expr = parse_stmt_extra(source, 0, &mut allocator)?; + let span = expr.span(); + let len = (span.end - span.start) as usize; + + if let Some(trailing) = source[span.end as usize..].lines().next() { + if trailing.trim().len() > 0 { + return Err(err( + "Trailing content not allowed here", + span.end as usize + (trailing.len() - trailing.trim_start().len()), + trailing.trim().len(), + )); + } + } + Ok(len) } -/// TODO: Blow up -pub fn find_statement_extent(source: &str) -> Result { - let mut parser = Parser::new(); - - let language = tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into(); - parser - .set_language(&language) - .expect("Failed to load TypeScript grammar"); - - let tree = parser.parse(source, None).ok_or_else(|| { - OxcDiagnostic::error("Failed to parse").and_label(LabeledSpan::new(None, 0, 1)) - })?; - let root = tree.root_node(); - - let first_statement = root.named_child(0).ok_or_else(|| { - OxcDiagnostic::error("Failed to parse").and_label(LabeledSpan::new(None, 0, 1)) - })?; - - if first_statement.kind() == "ERROR" { - return Err(OxcDiagnostic::error("Invalid TypeScript/JavaScript syntax") - .and_label(LabeledSpan::new(None, 0, first_statement.end_byte()))); +fn parse_stmt_extra<'alloc, 'src: 'alloc>( + source: &'src str, + offset: isize, + allocator: &'alloc mut Allocator, +) -> Result, OxcDiagnostic> { + if source.is_empty() { + return Err(err( + "Expected expression, found end of file", + offset.max(0).cast_unsigned(), + 1, + )); } - let end_byte = first_statement.end_byte(); + let source_type = SourceType::default() + .with_module(true) + .with_typescript(true); + + let mut result = oxc_parser::Parser::new(allocator, source, source_type).parse(); + if !result.errors.is_empty() && result.program.body.len() == 0 { + let first_err = result + .errors + .into_iter() + .next() + .expect("no errors but no result!"); + let first_err_offset = first_err + .labels + .as_ref() + .ok_or_else(|| err("Expected statement", offset.max(0).cast_unsigned(), 1))? + .first() + .expect("labels, but no labels!") + .offset(); - let remaining = &source[end_byte..]; - let same_line = remaining - .find('\n') - .map(|x| &remaining[..x]) - .unwrap_or(remaining); - if !same_line.trim().is_empty() { - return Err(OxcDiagnostic::error("Unexpected content after statement.") - .with_label(LabeledSpan::new(None, end_byte, same_line.len()))); + let mut candidate = source[0..first_err_offset].trim_end(); + let mut whitespace_groups = 0; + loop { + if candidate.is_empty() { + return Err(adjust_err(first_err, offset)); + } + result = oxc_parser::Parser::new(allocator, candidate, source_type).parse(); + if (!result.errors.is_empty() || result.panicked) && result.program.body.len() == 0 { + let before_trim = &candidate[..candidate.len() - 1]; + let after_trim = before_trim.trim_end(); + if after_trim.len() < before_trim.len() { + whitespace_groups += 1; + if whitespace_groups > 1 { + return Err(adjust_err(first_err, offset)); + } + } + candidate = after_trim; + } else { + break; + } + } + } else { + assert!(!result.panicked); } - Ok(end_byte) + result + .program + .body + .into_iter() + .next() + .ok_or_else(|| err("Expected statement", offset.max(0).cast_unsigned(), 1)) } fn parse_expr_extra<'alloc, 'src: 'alloc>( @@ -71,15 +111,22 @@ fn parse_expr_extra<'alloc, 'src: 'alloc>( Ok(expr) => expr, Err(errors) => { let first_err = errors.into_iter().next().expect("no errors but no result!"); - let first_err_offset = first_err + let first_label = first_err .labels .as_ref() .ok_or_else(|| err("Expected expression", offset.max(0).cast_unsigned(), 1))? .first() - .expect("labels, but no labels!") - .offset(); + .expect("labels, but no labels!"); + let first_err_offset = first_label.offset(); + let first_err_len = first_label.len(); - let mut candidate = source[0..first_err_offset].trim_end(); + // Use end of error span when error is at start (e.g. "Cannot assign to this expression") + let initial_end = if first_err_offset == 0 { + (first_err_offset + first_err_len).min(source.len()) + } else { + first_err_offset + }; + let mut candidate = source[0..initial_end].trim_end(); let mut whitespace_groups = 0; loop { if candidate.is_empty() { @@ -184,6 +231,7 @@ pub fn parse_call_arguments(l: &mut LexState) -> Result { /// parse variable binding. identifier or destructuring pattern /// also parses optional `: Type` pub fn parse_var_binding(l: &mut LexState) -> Result { + // TODO: this approach has bugs let offset = l.offset(); let source = l.peek_rest(); if source.is_empty() { @@ -724,52 +772,58 @@ mod tests { assert_eq!(parse_var_binding("café"), Ok(5)); } + #[test] + fn test_var_binding_equals() { + assert_eq!(parse_var_binding("foo = 1"), Ok(3)); + assert_eq!(parse_var_binding("foo| of=1>"), Ok(3)); + } + #[test] fn test_stmt_function_with_garbage() { let source = "function hello() {\n console.log(1);\n}\n\nrandom markdown garbage"; - let end = find_statement_extent(source).unwrap(); + let end = scan_first_statement_forbid_trailing(source).unwrap(); assert_eq!(&source[..end], "function hello() {\n console.log(1);\n}"); } #[test] fn test_stmt_import_with_garbage() { let source = "import { foo } from 'bar';\n\n# markdown heading"; - let end = find_statement_extent(source).unwrap(); + let end = scan_first_statement_forbid_trailing(source).unwrap(); assert_eq!(&source[..end], "import { foo } from 'bar';"); } #[test] fn test_stmt_interface_with_garbage() { let source = "interface Foo {\n bar: string;\n}\n\nsome text"; - let end = find_statement_extent(source).unwrap(); + let end = scan_first_statement_forbid_trailing(source).unwrap(); assert_eq!(&source[..end], "interface Foo {\n bar: string;\n}"); } #[test] fn test_stmt_const_declaration() { let source = "const answer = 42;\n\n# Next section"; - let end = find_statement_extent(source).unwrap(); + let end = scan_first_statement_forbid_trailing(source).unwrap(); assert_eq!(&source[..end], "const answer = 42;"); } #[test] fn test_stmt_expression_statement() { let source = "console.log('hello');\n\nmore content"; - let end = find_statement_extent(source).unwrap(); + let end = scan_first_statement_forbid_trailing(source).unwrap(); assert_eq!(&source[..end], "console.log('hello');"); } #[test] fn test_stmt_pure_garbage() { let source = "random unexpected garbage"; - let result = find_statement_extent(source); + let result = scan_first_statement_forbid_trailing(source); assert!(result.is_err()); } #[test] fn test_stmt_multiline_function() { let source = "function sort(items) {\n while (!isSorted()) {\n shuffle(items);\n }\n return items;\n}\n\n# Heading"; - let end = find_statement_extent(source).unwrap(); + let end = scan_first_statement_forbid_trailing(source).unwrap(); assert!(source[..end].contains("return items;")); assert!(source[..end].contains("}")); } @@ -777,14 +831,14 @@ mod tests { #[test] fn test_stmt_trailing_content_on_same_line() { let source = "const x = 1; const y = 2;"; - let result = find_statement_extent(source); + let result = scan_first_statement_forbid_trailing(source); assert!(result.is_err()); } #[test] fn test_stmt_trailing_whitespace_ok() { let source = "const x = 1; \n\nmore content"; - let result = find_statement_extent(source); + let result = scan_first_statement_forbid_trailing(source); assert!(result.is_ok()); } } diff --git a/tests/fixtures.rs b/tests/fixtures.rs index 5546dd3ba6648e6454c76211cb698ffb5192a5ad..b71571a07019e283c3ffb44dc548a0068061e674 100644 --- a/tests/fixtures.rs +++ b/tests/fixtures.rs @@ -147,3 +147,15 @@ fn fixture_16_same_line_text() { fn fixture_17_consecutive_statements() { run_fixture("17-consecutive-statements"); } + +#[ignore = "known bug"] +#[test] +fn fixture_18_multiline_tag_with_value() { + run_fixture("18-multiline-tag-with-value"); +} + +#[ignore = "known bug"] +#[test] +fn fixture_19_extra_cases() { + run_fixture("19-extra-cases"); +} diff --git a/tests/fixtures/13-kitchen-sink.marko b/tests/fixtures/13-kitchen-sink.marko index 0d3dd4baf05ef155ef24a692bdf796bf49d6fb19..0be8f9a75eb5e952f1ea85171e869f579bf3c2c5 100644 --- a/tests/fixtures/13-kitchen-sink.marko +++ b/tests/fixtures/13-kitchen-sink.marko @@ -1,5 +1,4 @@ export const title = "Full Example"; - import Chart from "./chart.marko"; static const year = 2026; export const slug = "full-example"; @@ -19,7 +18,7 @@ server { -

No data available. Visit example.com for help.

+No data available. Visit example.com for help.
diff --git a/tests/fixtures/13-kitchen-sink.mdo b/tests/fixtures/13-kitchen-sink.mdo index 27c708c71c193f6b5ec85bf3a9f6bf1fe7266580..cf17139a9a8c1d080af8ad6c7e9f43b5ecbb7b20 100644 --- a/tests/fixtures/13-kitchen-sink.mdo +++ b/tests/fixtures/13-kitchen-sink.mdo @@ -1,11 +1,8 @@ --- title: Full Example --- - import Chart from "./chart.marko"; - static const year = 2026; - export const slug = "full-example"; // Page starts here @@ -19,16 +16,19 @@ server { } - ## Chart - +## Chart - - - **${point.label}**: ${point.value} - + + + + +- **${point.label}**: ${point.value} + + - No data available. Visit for help. +No data available. Visit for help. diff --git a/tests/fixtures/18-multiline-tag-with-value.marko b/tests/fixtures/18-multiline-tag-with-value.marko new file mode 100644 index 0000000000000000000000000000000000000000..5ae522df641ac04596d794ec208a30f21d8c4916 --- /dev/null +++ b/tests/fixtures/18-multiline-tag-with-value.marko @@ -0,0 +1,6 @@ + +

next/metadata is Great +

hi

+ diff --git a/tests/fixtures/18-multiline-tag-with-value.mdo b/tests/fixtures/18-multiline-tag-with-value.mdo new file mode 100644 index 0000000000000000000000000000000000000000..3b351ccf6b7b466330afbd44b4dd3678fc9846f3 --- /dev/null +++ b/tests/fixtures/18-multiline-tag-with-value.mdo @@ -0,0 +1,9 @@ + + +

`next/metadata` is Great + +hi + + diff --git a/tests/fixtures/19-extra-cases.marko b/tests/fixtures/19-extra-cases.marko new file mode 100644 index 0000000000000000000000000000000000000000..8df27aa35e461d8ad2d621211d1d2197db7c0c60 --- /dev/null +++ b/tests/fixtures/19-extra-cases.marko @@ -0,0 +1,3 @@ + +5)> diff --git a/tests/fixtures/19-extra-cases.mdo b/tests/fixtures/19-extra-cases.mdo new file mode 100644 index 0000000000000000000000000000000000000000..c112954d2d96388f67ff84ff0a153804b887b3f3 --- /dev/null +++ b/tests/fixtures/19-extra-cases.mdo @@ -0,0 +1,4 @@ + + +5)> -- 2.54.0