diff options
-rw-r--r-- | src/construct/partial_mdx_expression.rs | 30 | ||||
-rw-r--r-- | src/construct/partial_mdx_jsx.rs | 50 | ||||
-rw-r--r-- | src/event.rs | 2 | ||||
-rw-r--r-- | src/state.rs | 9 | ||||
-rw-r--r-- | tests/mdx_jsx_flow.rs | 35 | ||||
-rw-r--r-- | tests/mdx_jsx_text.rs | 302 |
6 files changed, 248 insertions, 180 deletions
diff --git a/src/construct/partial_mdx_expression.rs b/src/construct/partial_mdx_expression.rs index a949347..aeea52e 100644 --- a/src/construct/partial_mdx_expression.rs +++ b/src/construct/partial_mdx_expression.rs @@ -36,13 +36,22 @@ pub fn before(tokenizer: &mut Tokenizer) -> State { State::Next(StateName::MdxExpressionEolAfter) }, Some(b'}') if tokenizer.tokenize_state.size == 0 => { - tokenizer.enter(Name::MdxExpressionMarker); - tokenizer.consume(); - tokenizer.exit(Name::MdxExpressionMarker); - tokenizer.exit(tokenizer.tokenize_state.token_1.clone()); - State::Ok + if tokenizer.tokenize_state.token_1 == Name::MdxJsxTagAttributeValueExpression && !tokenizer.tokenize_state.seen { + State::Error(format!( + "{}:{}: Unexpected empty in expression, expected a value between braces", + tokenizer.point.line, tokenizer.point.column + )) + } else { + tokenizer.tokenize_state.seen = false; + tokenizer.enter(Name::MdxExpressionMarker); + tokenizer.consume(); + tokenizer.exit(Name::MdxExpressionMarker); + tokenizer.exit(tokenizer.tokenize_state.token_1.clone()); + State::Ok + } }, Some(_) => { + tokenizer.tokenize_state.seen = true; tokenizer.enter(Name::MdxExpressionData); State::Retry(StateName::MdxExpressionInside) } @@ -56,9 +65,11 @@ pub fn inside(tokenizer: &mut Tokenizer) -> State { tokenizer.exit(Name::MdxExpressionData); State::Retry(StateName::MdxExpressionBefore) } else { - // To do: only count if agnostic. + // To do: don’t count if gnostic. if tokenizer.current == Some(b'{') { tokenizer.tokenize_state.size += 1; + } else if tokenizer.current == Some(b'}') { + tokenizer.tokenize_state.size -= 1; } tokenizer.consume(); @@ -67,8 +78,11 @@ pub fn inside(tokenizer: &mut Tokenizer) -> State { } pub fn eol_after(tokenizer: &mut Tokenizer) -> State { - // Lazy continuation in a flow expression is a syntax error. - if tokenizer.tokenize_state.token_1 == Name::MdxFlowExpression && tokenizer.lazy { + // Lazy continuation in a flow expression (or in a flow tag) is a syntax error. + if (tokenizer.tokenize_state.token_1 == Name::MdxFlowExpression + || tokenizer.tokenize_state.token_2 == Name::MdxJsxFlowTag) + && tokenizer.lazy + { State::Error(format!( "{}:{}: Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", tokenizer.point.line, tokenizer.point.column diff --git a/src/construct/partial_mdx_jsx.rs b/src/construct/partial_mdx_jsx.rs index 05a2c0c..9177b5b 100644 --- a/src/construct/partial_mdx_jsx.rs +++ b/src/construct/partial_mdx_jsx.rs @@ -610,7 +610,17 @@ pub fn attribute_before(tokenizer: &mut Tokenizer) -> State { // End of tag. Some(b'>') => State::Retry(StateName::MdxJsxTagEnd), // Attribute expression. - Some(b'{') => unreachable!("to do: attribute expression"), + Some(b'{') => { + // To do: force `spread: true` if gnostic. + // To do: pass `start_point` if gnostic. + tokenizer.tokenize_state.token_2 = tokenizer.tokenize_state.token_1.clone(); + tokenizer.tokenize_state.token_1 = Name::MdxJsxTagAttributeExpression; + tokenizer.attempt( + State::Next(StateName::MdxJsxAttributeExpressionAfter), + State::Nok, + ); + State::Retry(StateName::MdxExpressionStart) + } _ => { // Start of an attribute name. if id_start(char_after_index( @@ -633,6 +643,19 @@ pub fn attribute_before(tokenizer: &mut Tokenizer) -> State { } } +/// After attribute expression. +/// +/// ```markdown +/// > | a <b {c} d/> e +/// ^ +/// ``` +pub fn attribute_expression_after(tokenizer: &mut Tokenizer) -> State { + tokenizer.tokenize_state.token_1 = tokenizer.tokenize_state.token_2.clone(); + tokenizer.tokenize_state.token_2 = Name::Data; + tokenizer.attempt(State::Next(StateName::MdxJsxAttributeBefore), State::Nok); + State::Retry(StateName::MdxJsxEsWhitespaceStart) +} + /// In primary attribute name. /// /// ```markdown @@ -862,7 +885,16 @@ pub fn attribute_value_before(tokenizer: &mut Tokenizer) -> State { State::Next(StateName::MdxJsxAttributeValueQuotedStart) } // Attribute value expression. - Some(b'{') => unreachable!("to do: attribute value expression"), + Some(b'{') => { + // To do: pass `start_point` if gnostic. + tokenizer.tokenize_state.token_2 = tokenizer.tokenize_state.token_1.clone(); + tokenizer.tokenize_state.token_1 = Name::MdxJsxTagAttributeValueExpression; + tokenizer.attempt( + State::Next(StateName::MdxJsxAttributeValueExpressionAfter), + State::Nok, + ); + State::Retry(StateName::MdxExpressionStart) + } _ => crash( tokenizer, "before attribute value", @@ -878,6 +910,20 @@ pub fn attribute_value_before(tokenizer: &mut Tokenizer) -> State { } } +/// After attribute value expression. +/// +/// ```markdown +/// > | a <b c={d} e/> f +/// ^ +/// ``` +pub fn attribute_value_expression_after(tokenizer: &mut Tokenizer) -> State { + tokenizer.tokenize_state.token_1 = tokenizer.tokenize_state.token_2.clone(); + tokenizer.tokenize_state.token_2 = Name::Data; + tokenizer.exit(Name::MdxJsxTagAttribute); + tokenizer.attempt(State::Next(StateName::MdxJsxAttributeBefore), State::Nok); + State::Retry(StateName::MdxJsxEsWhitespaceStart) +} + /// Before quoted literal attribute value. /// /// ```markdown diff --git a/src/event.rs b/src/event.rs index b18835e..2375369 100644 --- a/src/event.rs +++ b/src/event.rs @@ -3171,6 +3171,8 @@ pub enum Name { MdxTextExpression, MdxExpressionMarker, // void MdxExpressionData, // void + MdxJsxTagAttributeValueExpression, + MdxJsxTagAttributeExpression, } /// List of void events, used to make sure everything is working well. diff --git a/src/state.rs b/src/state.rs index 2158966..dcabbd7 100644 --- a/src/state.rs +++ b/src/state.rs @@ -448,6 +448,8 @@ pub enum Name { MdxExpressionBefore, MdxExpressionInside, MdxExpressionEolAfter, + MdxJsxAttributeValueExpressionAfter, + MdxJsxAttributeExpressionAfter, } #[allow(clippy::too_many_lines)] @@ -467,6 +469,13 @@ pub fn call(tokenizer: &mut Tokenizer, name: Name) -> State { Name::MdxExpressionInside => construct::partial_mdx_expression::inside, Name::MdxExpressionEolAfter => construct::partial_mdx_expression::eol_after, + Name::MdxJsxAttributeValueExpressionAfter => { + construct::partial_mdx_jsx::attribute_value_expression_after + } + Name::MdxJsxAttributeExpressionAfter => { + construct::partial_mdx_jsx::attribute_expression_after + } + Name::AttentionStart => construct::attention::start, Name::AttentionInside => construct::attention::inside, diff --git a/tests/mdx_jsx_flow.rs b/tests/mdx_jsx_flow.rs index ff53dcb..9b0453f 100644 --- a/tests/mdx_jsx_flow.rs +++ b/tests/mdx_jsx_flow.rs @@ -33,12 +33,11 @@ fn mdx_jsx_flow_agnostic() -> Result<(), String> { "should support an element w/ containers as content" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("<a b c:d e=\"\" f={/* g */} {...h} />", &mdx)?, - // "", - // "should support attributes" - // ); + assert_eq!( + micromark_with_options("<a b c:d e=\"\" f={/* g */} {...h} />", &mdx)?, + "", + "should support attributes" + ); Ok(()) } @@ -97,7 +96,7 @@ fn mdx_jsx_flow_essence() -> Result<(), String> { ); assert_eq!( - micromark_with_options("> <a b='\nc'/> d", &mdx) + micromark_with_options("> <a b='\nc'/>", &mdx) .err() .unwrap(), "2:1: Unexpected lazy line in jsx in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", @@ -105,7 +104,7 @@ fn mdx_jsx_flow_essence() -> Result<(), String> { ); assert_eq!( - micromark_with_options("> <a b='c\n'/> d", &mdx) + micromark_with_options("> <a b='c\n'/>", &mdx) .err() .unwrap(), "2:1: Unexpected lazy line in jsx in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", @@ -113,7 +112,7 @@ fn mdx_jsx_flow_essence() -> Result<(), String> { ); assert_eq!( - micromark_with_options("> <a b='c\nd'/> e", &mdx) + micromark_with_options("> <a b='c\nd'/>", &mdx) .err() .unwrap(), "2:1: Unexpected lazy line in jsx in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", @@ -121,9 +120,25 @@ fn mdx_jsx_flow_essence() -> Result<(), String> { ); assert_eq!( + micromark_with_options("> <a b={c\nd}/>", &mdx) + .err() + .unwrap(), + "2:1: Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", + "should not support lazy flow (5)" + ); + + assert_eq!( + micromark_with_options("> <a {b\nc}/>", &mdx) + .err() + .unwrap(), + "2:1: Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc", + "should not support lazy flow (6)" + ); + + assert_eq!( micromark_with_options("> a\n<X />", &mdx)?, "<blockquote>\n<p>a</p>\n</blockquote>\n", - "should not support lazy flow (5)" + "should not support lazy flow (7)" ); Ok(()) diff --git a/tests/mdx_jsx_text.rs b/tests/mdx_jsx_text.rs index 782fb6a..1872b05 100644 --- a/tests/mdx_jsx_text.rs +++ b/tests/mdx_jsx_text.rs @@ -67,19 +67,17 @@ fn mdx_jsx_text_agnosic() -> Result<(), String> { "should support an unclosed element" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b {1 + 1} /> c", &mdx)?, - // "<p>a c</p>", - // "should support an attribute expression" - // ); + assert_eq!( + micromark_with_options("a <b {1 + 1} /> c", &mdx)?, + "<p>a c</p>", + "should support an attribute expression" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b c={1 + 1} /> d", &mdx)?, - // "<p>a d</p>", - // "should support an attribute value expression" - // ); + assert_eq!( + micromark_with_options("a <b c={1 + 1} /> d", &mdx)?, + "<p>a d</p>", + "should support an attribute value expression" + ); Ok(()) } @@ -109,85 +107,79 @@ fn mdx_jsx_text_gnostic() -> Result<(), String> { "should support an unclosed element" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b {...c} /> d", &mdx)?, - // "<p>a d</p>", - // "should support an attribute expression" - // ); + assert_eq!( + micromark_with_options("a <b {...c} /> d", &mdx)?, + "<p>a d</p>", + "should support an attribute expression" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b {...{c: 1, d: Infinity, e: false}} /> f", &mdx)?, - // "<p>a f</p>", - // "should support more complex attribute expression (1)" - // ); + assert_eq!( + micromark_with_options("a <b {...{c: 1, d: Infinity, e: false}} /> f", &mdx)?, + "<p>a f</p>", + "should support more complex attribute expression (1)" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b {...[1, Infinity, false]} /> d", &mdx)?, - // "<p>a d</p>", - // "should support more complex attribute expression (2)" - // ); + assert_eq!( + micromark_with_options("a <b {...[1, Infinity, false]} /> d", &mdx)?, + "<p>a d</p>", + "should support more complex attribute expression (2)" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b c={1 + 1} /> d", &mdx)?, - // "<p>a d</p>", - // "should support an attribute value expression" - // ); + assert_eq!( + micromark_with_options("a <b c={1 + 1} /> d", &mdx)?, + "<p>a d</p>", + "should support an attribute value expression" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b c={} /> d", &mdx) - // .err() - // .unwrap(), - // "Unexpected empty expression", - // "should crash on an empty attribute value expression" - // ); + assert_eq!( + micromark_with_options("a <b c={} /> d", &mdx) + .err() + .unwrap(), + "1:9: Unexpected empty in expression, expected a value between braces", + "should crash on an empty attribute value expression" + ); - // To do: expressions. + // To do: swc. // assert_eq!( - // micromark_with_options("a <b {1 + 1} /> c", &mdx) + // 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: expressions. + // To do: swc. // assert_eq!( - // micromark_with_options("a <b c={?} /> d", &mdx) + // 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: expressions. + // To do: swc. // assert_eq!( - // micromark_with_options("a <b {?} /> c", &mdx) + // 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: expressions. + // To do: swc. // assert_eq!( - // micromark_with_options("a <b{c=d}={}/> f", &mdx) + // 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)" // ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b c={(2)} d={<e />} /> f", &mdx)?, - // "<p>a f</p>", - // "should support parenthesized expressions" - // ); + assert_eq!( + micromark_with_options("a <b c={(2)} d={<e />} /> f", &mdx)?, + "<p>a f</p>", + "should support parenthesized expressions" + ); Ok(()) } @@ -421,61 +413,53 @@ fn mdx_jsx_text_complete() -> Result<(), String> { "should crash on a nonconforming character after name" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b {...props} {...rest}>c</b>.", &mdx)?, - // "<p>a c.</p>", - // "should support attribute expressions" - // ); + assert_eq!( + micromark_with_options("a <b {...props} {...rest}>c</b>.", &mdx)?, + "<p>a c.</p>", + "should support attribute expressions" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b {...{\"a\": \"b\"}}>c</b>.", &mdx)?, - // "<p>a c.</p>", - // "should support nested balanced braces in attribute expressions" - // ); + assert_eq!( + micromark_with_options("a <b {...{\"a\": \"b\"}}>c</b>.", &mdx)?, + "<p>a c.</p>", + "should support nested balanced braces in attribute expressions" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("<a{...b}/>.", &mdx)?, - // "<p>.</p>", - // "should support attribute expressions directly after a name" - // ); + assert_eq!( + micromark_with_options("<a{...b}/>.", &mdx)?, + "<p>.</p>", + "should support attribute expressions directly after a name" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("<a.b{...c}/>.", &mdx)?, - // "<p>.</p>", - // "should support attribute expressions directly after a member name" - // ); + assert_eq!( + micromark_with_options("<a.b{...c}/>.", &mdx)?, + "<p>.</p>", + "should support attribute expressions directly after a member name" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("<a:b{...c}/>.", &mdx)?, - // "<p>.</p>", - // "should support attribute expressions directly after a local name" - // ); + assert_eq!( + micromark_with_options("<a:b{...c}/>.", &mdx)?, + "<p>.</p>", + "should support attribute expressions directly after a local name" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b c{...d}/>.", &mdx)?, - // "<p>a .</p>", - // "should support attribute expressions directly after boolean attributes" - // ); + assert_eq!( + micromark_with_options("a <b c{...d}/>.", &mdx)?, + "<p>a .</p>", + "should support attribute expressions directly after boolean attributes" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b c:d{...e}/>.", &mdx)?, - // "<p>a .</p>", - // "should support attribute expressions directly after boolean qualified attributes" - // ); + assert_eq!( + micromark_with_options("a <b c:d{...e}/>.", &mdx)?, + "<p>a .</p>", + "should support attribute expressions directly after boolean qualified attributes" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b a {...props} b>c</b>.", &mdx)?, - // "<p>a c.</p>", - // "should support attribute expressions and normal attributes" - // ); + assert_eq!( + micromark_with_options("a <b a {...props} b>c</b>.", &mdx)?, + "<p>a c.</p>", + "should support attribute expressions and normal attributes" + ); assert_eq!( micromark_with_options("a <b c d=\"d\"\t\tefg=\"e\">c</b>.", &mdx)?, @@ -483,23 +467,21 @@ fn mdx_jsx_text_complete() -> Result<(), String> { "should support attributes" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b {...p}~>c</b>.", &mdx) - // .err() - // .unwrap(), - // "Unexpected character `~` (U+007E) before attribute name, expected a character that can start an attribute name, such as a letter, `$`, or `_`; whitespace before attributes; or the end of the tag", - // "should crash on a nonconforming character before an attribute name" - // ); + assert_eq!( + micromark_with_options("a <b {...p}~>c</b>.", &mdx) + .err() + .unwrap(), + "1:12: Unexpected character `~` (U+007E) before attribute name, expected a character that can start an attribute name, such as a letter, `$`, or `_`; whitespace before attributes; or the end of the tag", + "should crash on a nonconforming character before an attribute name" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b {...", &mdx) - // .err() - // .unwrap(), - // "Unexpected end of file in expression, expected a corresponding closing brace for `{`", - // "should crash on a missing closing brace in attribute expression" - // ); + assert_eq!( + micromark_with_options("a <b {...", &mdx) + .err() + .unwrap(), + "1:10: Unexpected end of file in expression, expected a corresponding closing brace for `{`", + "should crash on a missing closing brace in attribute expression" + ); assert_eq!( micromark_with_options("a <a b@> c.", &mdx) @@ -553,19 +535,17 @@ fn mdx_jsx_text_complete() -> Result<(), String> { "should crash on a nonconforming character after a local attribute name" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b c={1 + 1}>c</b>.", &mdx)?, - // "<p>a c.</p>", - // "should support attribute value expressions" - // ); + assert_eq!( + micromark_with_options("a <b c={1 + 1}>c</b>.", &mdx)?, + "<p>a c.</p>", + "should support attribute value expressions" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <b c={1 + ({a: 1}).a}>c</b>.", &mdx)?, - // "<p>a c.</p>", - // "should support nested balanced braces in attribute value expressions" - // ); + assert_eq!( + micromark_with_options("a <b c={1 + ({a: 1}).a}>c</b>.", &mdx)?, + "<p>a c.</p>", + "should support nested balanced braces in attribute value expressions" + ); assert_eq!( micromark_with_options("a <a b=``> c.", &mdx) @@ -599,14 +579,13 @@ fn mdx_jsx_text_complete() -> Result<(), String> { "should crash on a missing closing quote in single quoted attribute value" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("a <a b={> c.", &mdx) - // .err() - // .unwrap(), - // "Unexpected end of file in expression, expected a corresponding closing brace for `{`", - // "should crash on a missing closing brace in an attribute value expression" - // ); + assert_eq!( + micromark_with_options("a <a b={> c.", &mdx) + .err() + .unwrap(), + "1:13: Unexpected end of file in expression, expected a corresponding closing brace for `{`", + "should crash on a missing closing brace in an attribute value expression" + ); assert_eq!( micromark_with_options("a <a b=\"\"*> c.", &mdx) @@ -622,12 +601,11 @@ fn mdx_jsx_text_complete() -> Result<(), String> { "should support an attribute directly after a value" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("<a{...b}c/>.", &mdx)?, - // "<p>.</p>", - // "should support an attribute directly after an attribute expression" - // ); + assert_eq!( + micromark_with_options("<a{...b}c/>.", &mdx)?, + "<p>.</p>", + "should support an attribute directly after an attribute expression" + ); assert_eq!( micromark_with_options("a <a/b> c.", &mdx) @@ -797,19 +775,17 @@ fn mdx_jsx_text_complete() -> Result<(), String> { "should support line endings in attribute values" ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("> a <b c={d\ne} /> f", &mdx)?, - // "<blockquote>\n<p>a f</p>\n</blockquote>", - // "should support line endings in attribute value expressions" - // ); + assert_eq!( + micromark_with_options("> a <b c={d\ne} /> f", &mdx)?, + "<blockquote>\n<p>a f</p>\n</blockquote>", + "should support line endings in attribute value expressions" + ); - // To do: expressions. - // assert_eq!( - // micromark_with_options("> a <b {c\nd} /> e", &mdx)?, - // "<blockquote>\n<p>a e</p>\n</blockquote>", - // "should support line endings in attribute expressions" - // ); + assert_eq!( + micromark_with_options("> a <b {c\nd} /> e", &mdx)?, + "<blockquote>\n<p>a e</p>\n</blockquote>", + "should support line endings in attribute expressions" + ); assert_eq!( micromark_with_options("> a <b\n/> c", &mdx)?, @@ -836,6 +812,12 @@ fn mdx_jsx_text_complete() -> Result<(), String> { ); assert_eq!( + micromark_with_options("> a <b c={d\ne}/> f", &mdx)?, + "<blockquote>\n<p>a f</p>\n</blockquote>", + "should support lazy text (5)" + ); + + assert_eq!( micromark_with_options("1 < 3", &mdx)?, "<p>1 < 3</p>", "should allow `<` followed by markdown whitespace as text in markdown" |