diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/code_indented.rs | 6 | ||||
| -rw-r--r-- | tests/gfm_table.rs | 6 | ||||
| -rw-r--r-- | tests/math_text.rs | 14 | ||||
| -rw-r--r-- | tests/mdx_esm.rs | 241 | ||||
| -rw-r--r-- | tests/mdx_expression_flow.rs | 281 | ||||
| -rw-r--r-- | tests/mdx_expression_text.rs | 422 | ||||
| -rw-r--r-- | tests/mdx_jsx_text.rs | 96 | ||||
| -rw-r--r-- | tests/mdx_swc.rs | 47 | ||||
| -rw-r--r-- | tests/test_utils/mod.rs | 245 | 
9 files changed, 941 insertions, 417 deletions
diff --git a/tests/code_indented.rs b/tests/code_indented.rs index 29d8909..bf39fa3 100644 --- a/tests/code_indented.rs +++ b/tests/code_indented.rs @@ -167,7 +167,11 @@ fn code_indented() -> Result<(), String> {              "a <?\n    ?>",              &Options {                  allow_dangerous_html: true, -                ..off.clone() +                constructs: Constructs { +                    code_indented: false, +                    ..Constructs::default() +                }, +                ..Options::default()              }          )?,          "<p>a <?\n?></p>", diff --git a/tests/gfm_table.rs b/tests/gfm_table.rs index b7f884a..8c46a30 100644 --- a/tests/gfm_table.rs +++ b/tests/gfm_table.rs @@ -1037,7 +1037,8 @@ bar  "###,              &Options {                  allow_dangerous_html: true, -                ..gfm.clone() +                constructs: Constructs::gfm(), +                ..Options::default()              }          )?,          r###"<h1>Grave accents</h1> @@ -1345,7 +1346,8 @@ b  "###,              &Options {                  allow_dangerous_html: true, -                ..gfm.clone() +                constructs: Constructs::gfm(), +                ..Options::default()              }          )?,          r###"<h2>Blank line</h2> diff --git a/tests/math_text.rs b/tests/math_text.rs index dced393..7b53268 100644 --- a/tests/math_text.rs +++ b/tests/math_text.rs @@ -30,7 +30,12 @@ fn math_text() -> Result<(), String> {              "$foo$ $$bar$$",              &Options {                  math_text_single_dollar: false, -                ..math.clone() +                constructs: Constructs { +                    math_text: true, +                    math_flow: true, +                    ..Constructs::default() +                }, +                ..Options::default()              }          )?,          "<p>$foo$ <code class=\"language-math math-inline\">bar</code></p>", @@ -133,7 +138,12 @@ fn math_text() -> Result<(), String> {              &Options {                  allow_dangerous_html: true,                  allow_dangerous_protocol: true, -                ..math.clone() +                constructs: Constructs { +                    math_text: true, +                    math_flow: true, +                    ..Constructs::default() +                }, +                ..Options::default()              }          )?,          "<p><a href=\"$\">$</p>", diff --git a/tests/mdx_esm.rs b/tests/mdx_esm.rs new file mode 100644 index 0000000..f1ea122 --- /dev/null +++ b/tests/mdx_esm.rs @@ -0,0 +1,241 @@ +extern crate micromark; +mod test_utils; +use micromark::{micromark_with_options, Constructs, Options}; +use pretty_assertions::assert_eq; +use test_utils::{parse_esm, parse_expression}; + +#[test] +fn mdx_esm() -> Result<(), String> { +    let swc = Options { +        constructs: Constructs::mdx(), +        mdx_esm_parse: Some(Box::new(parse_esm)), +        mdx_expression_parse: Some(Box::new(parse_expression)), +        ..Options::default() +    }; + +    assert_eq!( +        micromark_with_options("import a from 'b'\n\nc", &swc)?, +        "<p>c</p>", +        "should support an import" +    ); + +    assert_eq!( +        micromark_with_options("export default a\n\nb", &swc)?, +        "<p>b</p>", +        "should support an export" +    ); + +    assert_eq!( +        micromark_with_options("impossible", &swc)?, +        "<p>impossible</p>", +        "should not support other keywords (`impossible`)" +    ); + +    assert_eq!( +        micromark_with_options("exporting", &swc)?, +        "<p>exporting</p>", +        "should not support other keywords (`exporting`)" +    ); + +    assert_eq!( +        micromark_with_options("import.", &swc)?, +        "<p>import.</p>", +        "should not support a non-whitespace after the keyword" +    ); + +    assert_eq!( +        micromark_with_options("import('a')", &swc)?, +        "<p>import('a')</p>", +        "should not support a non-whitespace after the keyword (import-as-a-function)" +    ); + +    assert_eq!( +        micromark_with_options("  import a from 'b'\n  export default c", &swc)?, +        "<p>import a from 'b'\nexport default c</p>", +        "should not support an indent" +    ); + +    assert_eq!( +        micromark_with_options("- import a from 'b'\n> export default c", &swc)?, +        "<ul>\n<li>import a from 'b'</li>\n</ul>\n<blockquote>\n<p>export default c</p>\n</blockquote>", +        "should not support keywords in containers" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b'\nexport default c", &swc)?, +        "", +        "should support imports and exports in the same “block”" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b'\n\nexport default c", &swc)?, +        "", +        "should support imports and exports in separate “blocks”" +    ); + +    assert_eq!( +        micromark_with_options("a\n\nimport a from 'b'\n\nb\n\nexport default c", &swc)?, +        "<p>a</p>\n<p>b</p>\n", +        "should support imports and exports in between other constructs" +    ); + +    assert_eq!( +        micromark_with_options("a\nimport a from 'b'\n\nb\nexport default c", &swc)?, +        "<p>a\nimport a from 'b'</p>\n<p>b\nexport default c</p>", +        "should not support import/exports when interrupting paragraphs" +    ); + +    assert_eq!( +        micromark_with_options("import a", &swc).err().unwrap(), +        "1:9: Could not parse esm with swc: Expected ',', got '<eof>'", +        "should crash on invalid import/exports (1)" +    ); + +    assert_eq!( +        micromark_with_options("import 1/1", &swc).err().unwrap(), +        "1:9: Could not parse esm with swc: Expected 'from', got 'numeric literal (1, 1)'", +        "should crash on invalid import/exports (2)" +    ); + +    assert_eq!( +        micromark_with_options("export {\n  a\n} from 'b'\n\nc", &swc)?, +        "<p>c</p>", +        "should support line endings in import/exports" +    ); + +    assert_eq!( +        micromark_with_options("export {\n\n  a\n\n} from 'b'\n\nc", &swc)?, +        "<p>c</p>", +        "should support blank lines in import/exports" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b'\n*md*?", &swc) +            .err() +            .unwrap(), +        "2:6: Could not parse esm with swc: Unexpected token `?`. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, ` for template literal, (, or an identifier", +        "should crash on markdown after import/export w/o blank line" +    ); + +    assert_eq!( +        micromark_with_options("export var a = 1\n// b\n/* c */\n\nd", &swc)?, +        "<p>d</p>", +        "should support comments in “blocks”" +    ); + +    assert_eq!( +        micromark_with_options("export var a = 1\nvar b\n\nc", &swc) +            .err() +            .unwrap(), +        "2:1: Unexpected statement in code: only import/exports are supported", +        "should crash on other statements in “blocks”" +    ); + +    assert_eq!( +        micromark_with_options("import ('a')\n\nb", &swc) +            .err() +            .unwrap(), +        "1:1: Unexpected statement in code: only import/exports are supported", +        "should crash on import-as-a-function with a space `import (x)`" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b'\nexport {a}\n\nc", &swc)?, +        "<p>c</p>", +        "should support a reexport from another import" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b';\nexport {a};\n\nc", &swc)?, +        "<p>c</p>", +        "should support a reexport from another import w/ semicolons" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b'\nexport {a as default}\n\nc", &swc)?, +        "<p>c</p>", +        "should support a reexport default from another import" +    ); + +    assert_eq!( +        micromark_with_options("export var a = () => <b />", &swc)?, +        "", +        "should support JSX by default" +    ); + +    assert_eq!( +        micromark_with_options("export {a}\n", &swc)?, +        "", +        "should support EOF after EOL" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b'\n\nexport {a}\n\nc", &swc)?, +        "<p>c</p>", +        "should support a reexport from another esm block (1)" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b'\n\nexport {a}\n\n# c", &swc)?, +        "<h1>c</h1>", +        "should support a reexport from another esm block (2)" +    ); + +    let cases = vec![ +        ("default", "import a from \"b\""), +        ("whole", "import * as a from \"b\""), +        ("destructuring", "import {a} from \"b\""), +        ("destructuring and rename", "import {a as b} from \"c\""), +        ("default and destructuring", "import a, {b as c} from \"d\""), +        ("default and whole", "import a, * as b from \"c\""), +        ("side-effects", "import \"a\""), +    ]; + +    for case in cases { +        assert_eq!( +            micromark_with_options(case.1, &swc)?, +            "", +            "should support imports: {}", +            case.0 +        ); +    } + +    let cases = vec![ +        ("var", "export var a = \"\""), +        ("const", "export const a = \"\""), +        ("let", "export let a = \"\""), +        ("multiple", "export var a, b"), +        ("multiple w/ assignment", "export var a = \"a\", b = \"b\""), +        ("function", "export function a() {}"), +        ("class", "export class a {}"), +        ("destructuring", "export var {a} = {}"), +        ("rename destructuring", "export var {a: b} = {}"), +        ("array destructuring", "export var [a] = []"), +        ("default", "export default a = 1"), +        ("default function", "export default function a() {}"), +        ("default class", "export default class a {}"), +        ("aggregate", "export * from \"a\""), +        ("whole reexport", "export * as a from \"b\""), +        ("reexport destructuring", "export {a} from \"b\""), +        ( +            "reexport destructuring w rename", +            "export {a as b} from \"c\"", +        ), +        ("reexport as a default whole", "export {default} from \"b\""), +        ( +            "reexport default and non-default", +            "export {default as a, b} from \"c\"", +        ), +    ]; + +    for case in cases { +        assert_eq!( +            micromark_with_options(case.1, &swc)?, +            "", +            "should support exports: {}", +            case.0 +        ); +    } + +    Ok(()) +} diff --git a/tests/mdx_expression_flow.rs b/tests/mdx_expression_flow.rs index 2a66a9d..81a31a7 100644 --- a/tests/mdx_expression_flow.rs +++ b/tests/mdx_expression_flow.rs @@ -1,6 +1,8 @@  extern crate micromark; +mod test_utils;  use micromark::{micromark_with_options, Constructs, Options};  use pretty_assertions::assert_eq; +use test_utils::{parse_esm, parse_expression};  #[test]  fn mdx_expression_flow_agnostic() -> Result<(), String> { @@ -82,153 +84,132 @@ fn mdx_expression_flow_agnostic() -> Result<(), String> {      Ok(())  } -// To do: swc. -// #[test] -// fn mdx_expression_flow_gnostic() -> Result<(), String> { -//     assert_eq!( -//         micromark_with_options("{a}", &swc), -//         "", -//         "should support an expression" -//     ); - -//     assert_eq!( -//         micromark_with_options("{}", &swc)?, -//         "", -//         "should support an empty expression" -//     ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("{a", &swc); -//     //     }, -//     //     /Unexpected end of file in expression, expected a corresponding closing brace for `{`/, -//     //     "should crash if no closing brace is found (1)" -//     // ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("{b { c }", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected content after expression/, -//     //     "should crash if no closing brace is found (2)" -//     // ); - -//     assert_eq!( -//         micromark_with_options("{\n}\na", &swc)?, -//         "<p>a</p>", -//         "should support a line ending in an expression" -//     ); - -//     assert_eq!( -//         micromark_with_options("{ a } \t\nb", &swc)?, -//         "<p>b</p>", -//         "should support expressions followed by spaces" -//     ); - -//     assert_eq!( -//         micromark_with_options("  { a }\nb", &swc)?, -//         "<p>b</p>", -//         "should support expressions preceded by spaces" -//     ); - -//     assert_eq!( -//         micromark_with_options("  {`\n    a\n  `}", &swc)?, -//         "", -//         "should support indented expressions" -//     ); - -//     assert_eq!( -//         micromark_with_options("a{(b)}c", &swc)?, -//         "<p>ac</p>", -//         "should support expressions padded w/ parens" -//     ); - -//     assert_eq!( -//         micromark_with_options("a{/* b */ ( (c) /* d */ + (e) )}f", &swc)?, -//         "<p>af</p>", -//         "should support expressions padded w/ parens and comments" -//     ); - -//     Ok(()) -// } - -// To do: move to JSX, actually test spread in expressions? -// To do: swc. -// #[test] -// fn mdx_expression_spread() -> Result<(), String> { -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("a {b} c", &swc); -//     //     }, -//     //     /Unexpected `Property` in code: only spread elements are supported/, -//     //     "should crash if not a spread" -//     // ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("a {...?} c", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected token/, -//     //     "should crash on an incorrect spread" -//     // ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("a {...b,c} d", &swc); -//     //     }, -//     //     /Unexpected extra content in spread: only a single spread is supported/, -//     //     "should crash if a spread and other things" -//     // ); - -//     assert_eq!( -//         micromark_with_options("a {} b", &swc)?, -//         "<p>a  b</p>", -//         "should support an empty spread" -//     ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("a {} b", &swc); -//     //     }, -//     //     /Unexpected empty expression/, -//     //     "should crash on an empty spread w/ `allowEmpty: false`" -//     // ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("{a=b}", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Shorthand property assignments are valid only in destructuring patterns/, -//     //     "should crash if not a spread w/ `allowEmpty`" -//     // ); - -//     assert_eq!( -//         micromark_with_options("a {/* b */} c", &swc)?, -//         "<p>a  c</p>", -//         "should support a comment spread" -//     ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("a {/* b */} c", &swc); -//     //     }, -//     //     /Unexpected empty expression/, -//     //     "should crash on a comment spread w/ `allowEmpty: false`" -//     // ); - -//     assert_eq!( -//         micromark_with_options("a {...b} c", &swc)?, -//         "<p>a  c</p>", -//         "should support a spread" -//     ); - -//     Ok(()) -// } +#[test] +fn mdx_expression_flow_gnostic() -> Result<(), String> { +    let swc = Options { +        constructs: Constructs::mdx(), +        mdx_esm_parse: Some(Box::new(parse_esm)), +        mdx_expression_parse: Some(Box::new(parse_expression)), +        ..Options::default() +    }; + +    assert_eq!( +        micromark_with_options("{a}", &swc)?, +        "", +        "should support an expression" +    ); + +    assert_eq!( +        micromark_with_options("{}", &swc)?, +        "", +        "should support an empty expression" +    ); + +    assert_eq!( +        micromark_with_options("{a", &swc).err().unwrap(), +        "1:3: Unexpected end of file in expression, expected a corresponding closing brace for `{`", +        "should crash if no closing brace is found (1)" +    ); + +    assert_eq!( +        micromark_with_options("{b { c }", &swc).err().unwrap(), +        "1:4: Could not parse expression with swc: Unexpected content after expression", +        "should crash if no closing brace is found (2)" +    ); + +    assert_eq!( +        micromark_with_options("{\n}\na", &swc)?, +        "<p>a</p>", +        "should support a line ending in an expression" +    ); + +    assert_eq!( +        micromark_with_options("{ a } \t\nb", &swc)?, +        "<p>b</p>", +        "should support expressions followed by spaces" +    ); + +    assert_eq!( +        micromark_with_options("  { a }\nb", &swc)?, +        "<p>b</p>", +        "should support expressions preceded by spaces" +    ); + +    assert_eq!( +        micromark_with_options("  {`\n    a\n  `}", &swc)?, +        "", +        "should support indented expressions" +    ); + +    assert_eq!( +        micromark_with_options("a{(b)}c", &swc)?, +        "<p>ac</p>", +        "should support expressions padded w/ parens" +    ); + +    assert_eq!( +        micromark_with_options("a{/* b */ ( (c) /* d */ + (e) )}f", &swc)?, +        "<p>af</p>", +        "should support expressions padded w/ parens and comments" +    ); + +    Ok(()) +} + +#[test] +fn mdx_expression_spread() -> Result<(), String> { +    let swc = Options { +        constructs: Constructs::mdx(), +        mdx_esm_parse: Some(Box::new(parse_esm)), +        mdx_expression_parse: Some(Box::new(parse_expression)), +        ..Options::default() +    }; + +    assert_eq!( +        micromark_with_options("<a {...b} />", &swc)?, +        "", +        "should support spreads for attribute expression" +    ); + +    assert_eq!( +        micromark_with_options("<a {b} />", &swc).err().unwrap(), +        "1:5: Expected a single spread value, such as `...x`", +        "should crash if not a spread" +    ); + +    assert_eq!( +        micromark_with_options("<a {...?} />", &swc).err().unwrap(), +        "1:13: Could not parse expression with swc: Unexpected token `?`. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, ` for template literal, (, or an identifier", +        "should crash on an incorrect spread" +    ); + +    assert_eq!( +        micromark_with_options("<a {...b,c} d>", &swc) +            .err() +            .unwrap(), +        "1:5: Expected a single spread value, such as `...x`", +        "should crash if a spread and other things" +    ); + +    assert_eq!( +        micromark_with_options("<a {} />", &swc).err().unwrap(), +        "1:5: Expected a single spread value, such as `...x`", +        "should crash on an empty spread" +    ); + +    assert_eq!( +        micromark_with_options("<a {a=b} />", &swc).err().unwrap(), +        "1:12: Could not parse expression with swc: assignment property is invalid syntax", +        "should crash if not an identifier" +    ); + +    assert_eq!( +        micromark_with_options("<a {/* b */} />", &swc) +            .err() +            .unwrap(), +        "1:5: Expected a single spread value, such as `...x`", +        "should crash on a comment spread" +    ); + +    Ok(()) +} diff --git a/tests/mdx_expression_text.rs b/tests/mdx_expression_text.rs index b42faf2..3a48965 100644 --- a/tests/mdx_expression_text.rs +++ b/tests/mdx_expression_text.rs @@ -1,147 +1,144 @@  extern crate micromark; +mod test_utils;  use micromark::{micromark_with_options, Constructs, Options};  use pretty_assertions::assert_eq; +use test_utils::{parse_esm, parse_expression}; -// To do: swc. -// #[test] -// fn mdx_expression_text_gnostic_core() -> Result<(), String> { -//     assert_eq!( -//         micromark_with_options("a {} b", &swc)?, -//         "<p>a  b</p>", -//         "should support an empty expression (1)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a { \t\r\n} b", &swc)?, -//         "<p>a  b</p>", -//         "should support an empty expression (2)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {/**/} b", &swc)?, -//         "<p>a  b</p>", -//         "should support a multiline comment (1)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {  /*\n*/\t} b", &swc)?, -//         "<p>a  b</p>", -//         "should support a multiline comment (2)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {/*b*//*c*/} d", &swc)?, -//         "<p>a  d</p>", -//         "should support a multiline comment (3)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {b/*c*/} d", &swc)?, -//         "<p>a  d</p>", -//         "should support a multiline comment (4)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {/*b*/c} d", &swc)?, -//         "<p>a  d</p>", -//         "should support a multiline comment (4)" -//     ); - -//     //   To do: errors. -//     //   t.throws( -//     //     () => { -//     //       micromark_with_options("a {//} b", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected token/, -//     //     "should crash on an incorrect line comment (1)" -//     //   ); - -//     //   To do: errors. -//     //   t.throws( -//     //     () => { -//     //       micromark_with_options("a { // b } c", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected token/, -//     //     "should crash on an incorrect line comment (2)" -//     //   ); - -//     assert_eq!( -//         micromark_with_options("a {//\n} b", &swc)?, -//         "<p>a  b</p>", -//         "should support a line comment followed by a line ending" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {// b\nd} d", &swc)?, -//         "<p>a  d</p>", -//         "should support a line comment followed by a line ending and an expression" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {b// c\n} d", &swc)?, -//         "<p>a  d</p>", -//         "should support an expression followed by a line comment and a line ending" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {/*b*/ // c\n} d", &swc)?, -//         "<p>a  d</p>", -//         "should support comments (1)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {b.c} d", &swc)?, -//         "<p>a  d</p>", -//         "should support expression statements (1)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {1 + 1} b", &swc)?, -//         "<p>a  b</p>", -//         "should support expression statements (2)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a {function () {}} b", &swc)?, -//         "<p>a  b</p>", -//         "should support expression statements (3)" -//     ); - -//     //   To do: errors. -//     //   t.throws( -//     //     () => { -//     //       micromark_with_options("a {var b = \"c\"} d", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected token/, -//     //     "should crash on non-expressions" -//     //   ); - -//     assert_eq!( -//         micromark_with_options("> a {\n> b} c", &swc)?, -//         "<blockquote>\n<p>a  c</p>\n</blockquote>", -//         "should support expressions in containers" -//     ); - -//     //   To do: errors. -//     //   t.throws( -//     //     () => { -//     //       micromark_with_options("> a {\n> b<} c", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected token/, -//     //     "should crash on incorrect expressions in containers (1)" -//     //   ); - -//     //   To do: errors. -//     //   t.throws( -//     //     () => { -//     //       micromark_with_options("> a {\n> b\n> c} d", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected content after expression/, -//     //     "should crash on incorrect expressions in containers (2)" -//     //   ); - -//     Ok(()) -// } +#[test] +fn mdx_expression_text_gnostic_core() -> Result<(), String> { +    let swc = Options { +        constructs: Constructs::mdx(), +        mdx_esm_parse: Some(Box::new(parse_esm)), +        mdx_expression_parse: Some(Box::new(parse_expression)), +        ..Options::default() +    }; + +    assert_eq!( +        micromark_with_options("a {} b", &swc)?, +        "<p>a  b</p>", +        "should support an empty expression (1)" +    ); + +    assert_eq!( +        micromark_with_options("a { \t\r\n} b", &swc)?, +        "<p>a  b</p>", +        "should support an empty expression (2)" +    ); + +    assert_eq!( +        micromark_with_options("a {/**/} b", &swc)?, +        "<p>a  b</p>", +        "should support a multiline comment (1)" +    ); + +    assert_eq!( +        micromark_with_options("a {  /*\n*/\t} b", &swc)?, +        "<p>a  b</p>", +        "should support a multiline comment (2)" +    ); + +    assert_eq!( +        micromark_with_options("a {/*b*//*c*/} d", &swc)?, +        "<p>a  d</p>", +        "should support a multiline comment (3)" +    ); + +    assert_eq!( +        micromark_with_options("a {b/*c*/} d", &swc)?, +        "<p>a  d</p>", +        "should support a multiline comment (4)" +    ); + +    assert_eq!( +        micromark_with_options("a {/*b*/c} d", &swc)?, +        "<p>a  d</p>", +        "should support a multiline comment (4)" +    ); + +    assert_eq!( +        micromark_with_options("a {//} b", &swc).err().unwrap(), +        "1:4: Could not parse expression with swc: Unexpected eof", +        "should crash on an incorrect line comment (1)" +    ); + +    assert_eq!( +        micromark_with_options("a { // b } c", &swc).err().unwrap(), +        "1:4: Could not parse expression with swc: Unexpected eof", +        "should crash on an incorrect line comment (2)" +    ); + +    assert_eq!( +        micromark_with_options("a {//\n} b", &swc)?, +        "<p>a  b</p>", +        "should support a line comment followed by a line ending" +    ); + +    assert_eq!( +        micromark_with_options("a {// b\nd} d", &swc)?, +        "<p>a  d</p>", +        "should support a line comment followed by a line ending and an expression" +    ); + +    assert_eq!( +        micromark_with_options("a {b// c\n} d", &swc)?, +        "<p>a  d</p>", +        "should support an expression followed by a line comment and a line ending" +    ); + +    assert_eq!( +        micromark_with_options("a {/*b*/ // c\n} d", &swc)?, +        "<p>a  d</p>", +        "should support comments (1)" +    ); + +    assert_eq!( +        micromark_with_options("a {b.c} d", &swc)?, +        "<p>a  d</p>", +        "should support expression statements (1)" +    ); + +    assert_eq!( +        micromark_with_options("a {1 + 1} b", &swc)?, +        "<p>a  b</p>", +        "should support expression statements (2)" +    ); + +    assert_eq!( +        micromark_with_options("a {function () {}} b", &swc)?, +        "<p>a  b</p>", +        "should support expression statements (3)" +    ); + +    assert_eq!( +        micromark_with_options("a {var b = \"c\"} d", &swc).err().unwrap(), +        "1:7: Could not parse expression with swc: Unexpected token `var`. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, ` for template literal, (, or an identifier", +        "should crash on non-expressions" +    ); + +    assert_eq!( +        micromark_with_options("> a {\n> b} c", &swc)?, +        "<blockquote>\n<p>a  c</p>\n</blockquote>", +        "should support expressions in containers" +    ); + +    assert_eq!( +        micromark_with_options("> a {\n> b<} c", &swc) +            .err() +            .unwrap(), +        "2:8: Could not parse expression with swc: Unexpected eof", +        "should crash on incorrect expressions in containers (1)" +    ); + +    assert_eq!( +        micromark_with_options("> a {\n> b\n> c} d", &swc) +            .err() +            .unwrap(), +        "3:3: Could not parse expression with swc: Unexpected content after expression", +        "should crash on incorrect expressions in containers (2)" +    ); + +    Ok(()) +}  #[test]  fn mdx_expression_text_agnostic() -> Result<(), String> { @@ -197,77 +194,74 @@ fn mdx_expression_text_agnostic() -> Result<(), String> {      Ok(())  } -// // To do: swc. -// #[test] -// fn mdx_expression_text_gnostic() -> Result<(), String> { -//     assert_eq!( -//         micromark_with_options("a {b} c", &swc)?, -//         "<p>a  c</p>", -//         "should support an expression" -//     ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("a {??} b", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected token/, -//     //     "should crash on an incorrect expression" -//     // ); - -//     assert_eq!( -//         micromark_with_options("a {} b", &swc)?, -//         "<p>a  b</p>", -//         "should support an empty expression" -//     ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("a {b c", &swc); -//     //     }, -//     //     /Unexpected end of file in expression, expected a corresponding closing brace for `{`/, -//     //     "should crash if no closing brace is found (1)" -//     // ); - -//     //   To do: errors. -//     // t.throws( -//     //     () => { -//     //     micromark_with_options("a {b { c } d", &swc); -//     //     }, -//     //     /Could not parse expression with swc: Unexpected content after expression/, -//     //     "should crash if no closing brace is found (2)" -//     // ); - -//     assert_eq!( -//         micromark_with_options("a {\n} b", &swc)?, -//         "<p>a  b</p>", -//         "should support a line ending in an expression" -//     ); - -//     assert_eq!( -//         micromark_with_options("a } b", &swc)?, -//         "<p>a } b</p>", -//         "should support just a closing brace" -//     ); - -//     assert_eq!( -//         micromark_with_options("{ a } b", &swc)?, -//         "<p> b</p>", -//         "should support expressions as the first thing when following by other things" -//     ); - -//     assert_eq!( -//         micromark_with_options("a { /* { */ } b", &swc)?, -//         "<p>a  b</p>", -//         "should support an unbalanced opening brace (if JS permits)" -//     ); - -//     assert_eq!( -//         micromark_with_options("a { /* } */ } b", &swc)?, -//         "<p>a  b</p>", -//         "should support an unbalanced closing brace (if JS permits)" -//     ); - -//     Ok(()) -// } +#[test] +fn mdx_expression_text_gnostic() -> Result<(), String> { +    let swc = Options { +        constructs: Constructs::mdx(), +        mdx_esm_parse: Some(Box::new(parse_esm)), +        mdx_expression_parse: Some(Box::new(parse_expression)), +        ..Options::default() +    }; + +    assert_eq!( +        micromark_with_options("a {b} c", &swc)?, +        "<p>a  c</p>", +        "should support an expression" +    ); + +    assert_eq!( +        micromark_with_options("a {??} b", &swc).err().unwrap(), +        "1:9: Could not parse expression with swc: Unexpected eof", +        "should crash on an incorrect expression" +    ); + +    assert_eq!( +        micromark_with_options("a {} b", &swc)?, +        "<p>a  b</p>", +        "should support an empty expression" +    ); + +    assert_eq!( +        micromark_with_options("a {b c", &swc).err().unwrap(), +        "1:7: Unexpected end of file in expression, expected a corresponding closing brace for `{`", +        "should crash if no closing brace is found (1)" +    ); + +    assert_eq!( +        micromark_with_options("a {b { c } d", &swc).err().unwrap(), +        "1:6: Could not parse expression with swc: Unexpected content after expression", +        "should crash if no closing brace is found (2)" +    ); + +    assert_eq!( +        micromark_with_options("a {\n} b", &swc)?, +        "<p>a  b</p>", +        "should support a line ending in an expression" +    ); + +    assert_eq!( +        micromark_with_options("a } b", &swc)?, +        "<p>a } b</p>", +        "should support just a closing brace" +    ); + +    assert_eq!( +        micromark_with_options("{ a } b", &swc)?, +        "<p> b</p>", +        "should support expressions as the first thing when following by other things" +    ); + +    assert_eq!( +        micromark_with_options("a { /* { */ } b", &swc)?, +        "<p>a  b</p>", +        "should support an unbalanced opening brace (if JS permits)" +    ); + +    assert_eq!( +        micromark_with_options("a { /* } */ } b", &swc)?, +        "<p>a  b</p>", +        "should support an unbalanced closing brace (if JS permits)" +    ); + +    Ok(()) +} diff --git a/tests/mdx_jsx_text.rs b/tests/mdx_jsx_text.rs index cf507ee..be76d6f 100644 --- a/tests/mdx_jsx_text.rs +++ b/tests/mdx_jsx_text.rs @@ -1,6 +1,8 @@  extern crate micromark; +mod test_utils;  use micromark::{micromark_with_options, Constructs, Options};  use pretty_assertions::assert_eq; +use test_utils::{parse_esm, parse_expression};  #[test]  fn mdx_jsx_text_core() -> Result<(), String> { @@ -84,99 +86,97 @@ fn mdx_jsx_text_agnosic() -> Result<(), String> {  #[test]  fn mdx_jsx_text_gnostic() -> Result<(), String> { -    let mdx = Options { +    let swc = Options {          constructs: Constructs::mdx(), +        mdx_esm_parse: Some(Box::new(parse_esm)), +        mdx_expression_parse: Some(Box::new(parse_expression)),          ..Options::default()      };      assert_eq!( -        micromark_with_options("a <b /> c", &mdx)?, +        micromark_with_options("a <b /> c", &swc)?,          "<p>a  c</p>",          "should support a self-closing element"      );      assert_eq!( -        micromark_with_options("a <b> c </b> d", &mdx)?, +        micromark_with_options("a <b> c </b> d", &swc)?,          "<p>a  c  d</p>",          "should support a closed element"      );      assert_eq!( -        micromark_with_options("a <b> c", &mdx)?, +        micromark_with_options("a <b> c", &swc)?,          "<p>a  c</p>",          "should support an unclosed element"      );      assert_eq!( -        micromark_with_options("a <b {...c} /> d", &mdx)?, +        micromark_with_options("a <b {...c} /> d", &swc)?,          "<p>a  d</p>",          "should support an attribute expression"      );      assert_eq!( -        micromark_with_options("a <b {...{c: 1, d: Infinity, e: false}} /> f", &mdx)?, +        micromark_with_options("a <b {...{c: 1, d: Infinity, e: false}} /> f", &swc)?,          "<p>a  f</p>",          "should support more complex attribute expression (1)"      );      assert_eq!( -        micromark_with_options("a <b {...[1, Infinity, false]} /> d", &mdx)?, +        micromark_with_options("a <b {...[1, Infinity, false]} /> d", &swc)?,          "<p>a  d</p>",          "should support more complex attribute expression (2)"      );      assert_eq!( -        micromark_with_options("a <b c={1 + 1} /> d", &mdx)?, +        micromark_with_options("a <b c={1 + 1} /> d", &swc)?,          "<p>a  d</p>",          "should support an attribute value expression"      );      assert_eq!( -        micromark_with_options("a <b c={} /> d", &mdx) +        micromark_with_options("a <b c={} /> d", &swc)              .err()              .unwrap(), -        "1:9: Unexpected empty expression, expected a value between braces", +        "1:15: Could not parse expression with swc: Unexpected eof",          "should crash on an empty attribute value expression"      ); -    // To do: swc. -    // assert_eq!( -    //     micromark_with_options("a <b {1 + 1} /> c", &swc) -    //         .err() -    //         .unwrap(), -    //     "Could not parse expression with acorn: Unexpected token", -    //     "should crash on a non-spread attribute expression" -    // ); - -    // To do: swc. -    // assert_eq!( -    //     micromark_with_options("a <b c={?} /> d", &swc) -    //         .err() -    //         .unwrap(), -    //     "Could not parse expression with acorn: Unexpected token", -    //     "should crash on invalid JS in an attribute value expression" -    // ); - -    // To do: swc. -    // assert_eq!( -    //     micromark_with_options("a <b {?} /> c", &swc) -    //         .err() -    //         .unwrap(), -    //     "Could not parse expression with acorn: Unexpected token", -    //     "should crash on invalid JS in an attribute expression" -    // ); - -    // To do: swc. -    // assert_eq!( -    //     micromark_with_options("a <b{c=d}={}/> f", &swc) -    //         .err() -    //         .unwrap(), -    //     "Unexpected `ExpressionStatement` in code: expected an object spread", -    //     "should crash on invalid JS in an attribute expression (2)" -    // ); - -    assert_eq!( -        micromark_with_options("a <b c={(2)} d={<e />} /> f", &mdx)?, +    assert_eq!( +        micromark_with_options("a <b {1 + 1} /> c", &swc) +            .err() +            .unwrap(), +        "1:18: Could not parse expression with swc: Expected ',', got '}'", +        "should crash on a non-spread attribute expression" +    ); + +    assert_eq!( +        micromark_with_options("a <b c={?} /> d", &swc) +            .err() +            .unwrap(), +        "1:16: Could not parse expression with swc: Unexpected token `?`. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, ` for template literal, (, or an identifier", +        "should crash on invalid JS in an attribute value expression" +    ); + +    assert_eq!( +        micromark_with_options("a <b {?} /> c", &swc) +            .err() +            .unwrap(), +        "1:14: Could not parse expression with swc: Unexpected token `?`. Expected identifier, string literal, numeric literal or [ for the computed key", +        "should crash on invalid JS in an attribute expression" +    ); + +    assert_eq!( +        micromark_with_options("a <b{c=d}={}/> f", &swc) +            .err() +            .unwrap(), +        "1:6: Expected a single spread value, such as `...x`", +        "should crash on invalid JS in an attribute expression (2)" +    ); + +    assert_eq!( +        micromark_with_options("a <b c={(2)} d={<e />} /> f", &swc)?,          "<p>a  f</p>",          "should support parenthesized expressions"      ); diff --git a/tests/mdx_swc.rs b/tests/mdx_swc.rs new file mode 100644 index 0000000..c9a2a61 --- /dev/null +++ b/tests/mdx_swc.rs @@ -0,0 +1,47 @@ +extern crate micromark; +mod test_utils; +use micromark::{micromark_with_options, Constructs, Options}; +use pretty_assertions::assert_eq; +use test_utils::{parse_esm, parse_expression}; + +#[test] +fn mdx_swc() -> Result<(), String> { +    let mdx = Options { +        constructs: Constructs::mdx(), +        mdx_esm_parse: Some(Box::new(parse_esm)), +        mdx_expression_parse: Some(Box::new(parse_expression)), +        ..Options::default() +    }; + +    assert_eq!( +        micromark_with_options("{'}'}", &mdx)?, +        "", +        "should support JavaScript-aware flow expressions w/ `mdx_expression_parse`" +    ); + +    assert_eq!( +        micromark_with_options("a {'}'} b", &mdx)?, +        "<p>a  b</p>", +        "should support JavaScript-aware text expressions w/ `mdx_expression_parse`" +    ); + +    assert_eq!( +        micromark_with_options("<a {...a/*}*/} />", &mdx)?, +        "", +        "should support JavaScript-aware attribute expressions w/ `mdx_expression_parse`" +    ); + +    assert_eq!( +        micromark_with_options("<a b={'}'} />", &mdx)?, +        "", +        "should support JavaScript-aware attribute value expressions w/ `mdx_expression_parse`" +    ); + +    assert_eq!( +        micromark_with_options("import a from 'b'\n\nexport {a}\n\n# c", &mdx)?, +        "<h1>c</h1>", +        "should support JavaScript-aware ESM w/ `mdx_esm_parse`" +    ); + +    Ok(()) +} diff --git a/tests/test_utils/mod.rs b/tests/test_utils/mod.rs new file mode 100644 index 0000000..10b9643 --- /dev/null +++ b/tests/test_utils/mod.rs @@ -0,0 +1,245 @@ +extern crate micromark; +extern crate swc_common; +extern crate swc_ecma_ast; +extern crate swc_ecma_parser; +use micromark::{MdxExpressionKind, MdxSignal}; +use swc_common::{source_map::Pos, BytePos, FileName, SourceFile, Spanned}; +use swc_ecma_ast::{EsVersion, Expr, Module}; +use swc_ecma_parser::{ +    error::Error as SwcError, parse_file_as_expr, parse_file_as_module, EsConfig, Syntax, +}; + +/// Parse ESM in MDX with SWC. +pub fn parse_esm(value: &str) -> MdxSignal { +    let (file, syntax, version) = create_config(value.to_string()); +    let mut errors = vec![]; +    let result = parse_file_as_module(&file, syntax, version, None, &mut errors); + +    match result { +        Err(error) => swc_error_to_signal(&error, value.len(), 0, "esm"), +        Ok(tree) => { +            if errors.is_empty() { +                check_esm_ast(tree) +            } else { +                if errors.len() > 1 { +                    println!("parse_esm: todo: multiple errors? {:?}", errors); +                } +                swc_error_to_signal(&errors[0], value.len(), 0, "esm") +            } +        } +    } +} + +/// Parse expressions in MDX with SWC. +pub fn parse_expression(value: &str, kind: MdxExpressionKind) -> MdxSignal { +    // Empty expressions are OK. +    if matches!(kind, MdxExpressionKind::Expression) +        && matches!(whitespace_and_comments(0, value), MdxSignal::Ok) +    { +        return MdxSignal::Ok; +    } + +    // For attribute expression, a spread is needed, for which we have to prefix +    // and suffix the input. +    // See `check_expression_ast` for how the AST is verified. +    let (prefix, suffix) = if matches!(kind, MdxExpressionKind::AttributeExpression) { +        ("({", "})") +    } else { +        ("", "") +    }; + +    let (file, syntax, version) = create_config(format!("{}{}{}", prefix, value, suffix)); +    let mut errors = vec![]; +    let result = parse_file_as_expr(&file, syntax, version, None, &mut errors); + +    match result { +        Err(error) => swc_error_to_signal(&error, value.len(), prefix.len(), "expression"), +        Ok(tree) => { +            if errors.is_empty() { +                let place = fix_swc_position(tree.span().hi.to_usize(), prefix.len()); +                let result = check_expression_ast(tree, kind); +                if matches!(result, MdxSignal::Ok) { +                    whitespace_and_comments(place, value) +                } else { +                    result +                } +            } else { +                if errors.len() > 1 { +                    unreachable!("parse_expression: todo: multiple errors? {:?}", errors); +                } +                swc_error_to_signal(&errors[0], value.len(), prefix.len(), "expression") +            } +        } +    } +} + +/// Check that the resulting AST of ESM is OK. +/// +/// This checks that only module declarations (import/exports) are used, not +/// statements. +fn check_esm_ast(tree: Module) -> MdxSignal { +    let mut index = 0; +    while index < tree.body.len() { +        let node = &tree.body[index]; + +        if !node.is_module_decl() { +            let place = fix_swc_position(node.span().hi.to_usize(), 0); +            return MdxSignal::Error( +                "Unexpected statement in code: only import/exports are supported".to_string(), +                place, +            ); +        } + +        index += 1; +    } + +    MdxSignal::Ok +} + +/// Check that the resulting AST of an expressions is OK. +/// +/// This checks that attribute expressions are the expected spread. +fn check_expression_ast(tree: Box<Expr>, kind: MdxExpressionKind) -> MdxSignal { +    if matches!(kind, MdxExpressionKind::AttributeExpression) +        && tree +            .unwrap_parens() +            .as_object() +            .and_then(|object| { +                if object.props.len() == 1 { +                    object.props[0].as_spread() +                } else { +                    None +                } +            }) +            .is_none() +    { +        MdxSignal::Error( +            "Expected a single spread value, such as `...x`".to_string(), +            0, +        ) +    } else { +        MdxSignal::Ok +    } +} + +/// Turn an SWC error into an `MdxSignal`. +/// +/// * If the error happens at `value_len`, yields `MdxSignal::Eof` +/// * Else, yields `MdxSignal::Error`. +fn swc_error_to_signal( +    error: &SwcError, +    value_len: usize, +    prefix_len: usize, +    name: &str, +) -> MdxSignal { +    let message = error.kind().msg().to_string(); +    let place = fix_swc_position(error.span().hi.to_usize(), prefix_len); +    let message = format!("Could not parse {} with swc: {}", name, message); + +    if place >= value_len { +        MdxSignal::Eof(message) +    } else { +        MdxSignal::Error(message, place) +    } +} + +/// Move past JavaScript whitespace (well, actually ASCII whitespace) and +/// comments. +/// +/// This is needed because for expressions, we use an API that parses up to +/// a valid expression, but there may be more expressions after it, which we +/// don’t alow. +fn whitespace_and_comments(mut index: usize, value: &str) -> MdxSignal { +    let bytes = value.as_bytes(); +    let len = bytes.len(); +    let mut in_multiline = false; +    let mut in_line = false; + +    while index < len { +        // In a multiline comment: `/* a */`. +        if in_multiline { +            if index + 1 < len && bytes[index] == b'*' && bytes[index + 1] == b'/' { +                index += 1; +                in_multiline = false; +            } +        } +        // In a line comment: `// a`. +        else if in_line { +            if index + 1 < len && bytes[index] == b'\r' && bytes[index + 1] == b'\n' { +                index += 1; +                in_line = false; +            } else if bytes[index] == b'\r' || bytes[index] == b'\n' { +                in_line = false; +            } +        } +        // Not in a comment, opening a multiline comment: `/* a */`. +        else if index + 1 < len && bytes[index] == b'/' && bytes[index + 1] == b'*' { +            index += 1; +            in_multiline = true; +        } +        // Not in a comment, opening a line comment: `// a`. +        else if index + 1 < len && bytes[index] == b'/' && bytes[index + 1] == b'/' { +            index += 1; +            in_line = true; +        } +        // Outside comment, whitespace. +        else if bytes[index].is_ascii_whitespace() { +            // Fine! +        } +        // Outside comment, not whitespace. +        else { +            return MdxSignal::Error( +                "Could not parse expression with swc: Unexpected content after expression" +                    .to_string(), +                index, +            ); +        } + +        index += 1; +    } + +    if in_multiline { +        MdxSignal::Error( +            "Could not parse expression with swc: Unexpected unclosed multiline comment, expected closing: `*/`".to_string(), +            index, +        ) +    } else if in_line { +        // EOF instead of EOL is specifically not allowed, because that would +        // mean the closing brace is on the commented-out line +        MdxSignal::Error( +            "Could not parse expression with swc: Unexpected unclosed line comment, expected line ending: `\\n`".to_string(), +            index, +        ) +    } else { +        MdxSignal::Ok +    } +} + +/// Create configuration for SWC, shared between ESM and expressions. +/// +/// This enables modern JavaScript (ES2022) + JSX. +fn create_config(source: String) -> (SourceFile, Syntax, EsVersion) { +    ( +        // File. +        SourceFile::new( +            FileName::Anon, +            false, +            FileName::Anon, +            source, +            BytePos::from_usize(1), +        ), +        // Syntax. +        Syntax::Es(EsConfig { +            jsx: true, +            ..EsConfig::default() +        }), +        // Version. +        EsVersion::Es2022, +    ) +} + +/// Turn an SWC byte position from a resulting AST to an offset in the original +/// input string. +fn fix_swc_position(index: usize, prefix_len: usize) -> usize { +    index - 1 - prefix_len +}  | 
