aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/code_indented.rs6
-rw-r--r--tests/gfm_table.rs6
-rw-r--r--tests/math_text.rs14
-rw-r--r--tests/mdx_esm.rs241
-rw-r--r--tests/mdx_expression_flow.rs281
-rw-r--r--tests/mdx_expression_text.rs422
-rw-r--r--tests/mdx_jsx_text.rs96
-rw-r--r--tests/mdx_swc.rs47
-rw-r--r--tests/test_utils/mod.rs245
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
+}