Skip to content

Commit 71361e4

Browse files
committed
Add support for flow JSX, expressions after JSX, expressions
Related-to: micromark/micromark-extension-mdx-jsx@f68f69d Related-to: micromark/micromark-extension-mdx-expression@d5d92b9
1 parent 58a1f62 commit 71361e4

File tree

5 files changed

+215
-7
lines changed

5 files changed

+215
-7
lines changed

Diff for: src/configuration.rs

+2
Original file line numberDiff line numberDiff line change
@@ -1068,6 +1068,8 @@ pub struct ParseOptions {
10681068
/// However, single dollars can interfere with “normal” dollars in text.
10691069
/// Pass `false`, to only allow math (text) to form when two or more
10701070
/// dollars are used.
1071+
/// If you pass `false`, you can still use two or more dollars for text
1072+
/// math.
10711073
///
10721074
/// ## Examples
10731075
///

Diff for: src/construct/mdx_expression_flow.rs

+77-6
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,83 @@ pub fn after(tokenizer: &mut Tokenizer) -> State {
107107
/// ^
108108
/// ```
109109
pub fn end(tokenizer: &mut Tokenizer) -> State {
110-
tokenizer.concrete = false;
111-
tokenizer.tokenize_state.token_1 = Name::Data;
110+
// We want to allow tags directly after expressions.
111+
//
112+
// This case is useful:
113+
//
114+
// ```mdx
115+
// <a>{b}</a>
116+
// ```
117+
//
118+
// This case is not (very?) useful:
119+
//
120+
// ```mdx
121+
// {a}<b/>
122+
// ```
123+
//
124+
// …but it would be tougher than needed to disallow.
125+
//
126+
// To allow that, here we call the MDX JSX flow construct, and there we
127+
// call this one.
128+
//
129+
// It would introduce a cyclical interdependency if we test JSX and
130+
// expressions here.
131+
// Because the JSX extension already uses parts of this monorepo, we
132+
// instead test it there.
133+
//
134+
// Note: in the JS version of micromark, arbitrary extensions could be
135+
// loaded.
136+
// Here we know that only our own construct `mdx_expression_flow` can be
137+
// enabled.
112138

113-
if matches!(tokenizer.current, None | Some(b'\n')) {
114-
State::Ok
115-
} else {
116-
State::Nok
139+
// if matches!(tokenizer.current, None | Some(b'\n')) {
140+
// State::Ok
141+
// } else {
142+
// State::Nok
143+
// }
144+
match tokenizer.current {
145+
None | Some(b'\n') => {
146+
reset(tokenizer);
147+
State::Ok
148+
}
149+
// Tag.
150+
Some(b'<') if tokenizer.parse_state.options.constructs.mdx_jsx_flow => {
151+
// We can’t just say: fine.
152+
// Lines of blocks have to be parsed until an eol/eof.
153+
tokenizer.attempt(
154+
State::Next(StateName::MdxExpressionFlowAfter),
155+
State::Next(StateName::MdxExpressionFlowNok),
156+
);
157+
State::Retry(StateName::MdxJsxStart)
158+
}
159+
// // An expression.
160+
// Some(b'{') if tokenizer.parse_state.options.constructs.mdx_expression_flow => {
161+
// tokenizer.attempt(
162+
// State::Next(StateName::MdxExpressionFlowAfter),
163+
// State::Next(StateName::MdxExpressionFlowNok),
164+
// );
165+
// State::Retry(StateName::MdxExpressionFlowStart)
166+
// }
167+
_ => {
168+
reset(tokenizer);
169+
State::Nok
170+
}
117171
}
118172
}
173+
174+
/// At something that wasn’t an MDX expression (flow).
175+
///
176+
/// ```markdown
177+
/// > | {A} x
178+
/// ^
179+
/// ```
180+
pub fn nok(tokenizer: &mut Tokenizer) -> State {
181+
reset(tokenizer);
182+
State::Nok
183+
}
184+
185+
/// Reset state.
186+
fn reset(tokenizer: &mut Tokenizer) {
187+
tokenizer.concrete = false;
188+
tokenizer.tokenize_state.token_1 = Name::Data;
189+
}

Diff for: src/construct/mdx_jsx_flow.rs

+19-1
Original file line numberDiff line numberDiff line change
@@ -112,19 +112,37 @@ pub fn after(tokenizer: &mut Tokenizer) -> State {
112112
/// ^
113113
/// ```
114114
pub fn end(tokenizer: &mut Tokenizer) -> State {
115+
// We want to allow expressions directly after tags.
116+
// See <https://github.com/micromark/micromark-extension-mdx-expression/blob/d5d92b9/packages/micromark-extension-mdx-expression/dev/lib/syntax.js#L183>
117+
// for more info.
118+
//
119+
// Note: in the JS version of micromark, arbitrary extensions could be
120+
// loaded.
121+
// Here we know that only our own construct `mdx_expression_flow` can be
122+
// enabled.
115123
match tokenizer.current {
116124
None | Some(b'\n') => {
117125
reset(tokenizer);
118126
State::Ok
119127
}
120-
// Another?
128+
// Another tag.
121129
Some(b'<') => {
130+
// We can’t just say: fine.
131+
// Lines of blocks have to be parsed until an eol/eof.
122132
tokenizer.attempt(
123133
State::Next(StateName::MdxJsxFlowAfter),
124134
State::Next(StateName::MdxJsxFlowNok),
125135
);
126136
State::Retry(StateName::MdxJsxStart)
127137
}
138+
// An expression.
139+
Some(b'{') if tokenizer.parse_state.options.constructs.mdx_expression_flow => {
140+
tokenizer.attempt(
141+
State::Next(StateName::MdxJsxFlowAfter),
142+
State::Next(StateName::MdxJsxFlowNok),
143+
);
144+
State::Retry(StateName::MdxExpressionFlowStart)
145+
}
128146
_ => {
129147
reset(tokenizer);
130148
State::Nok

Diff for: src/state.rs

+2
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,7 @@ pub enum Name {
357357
MdxExpressionFlowBefore,
358358
MdxExpressionFlowAfter,
359359
MdxExpressionFlowEnd,
360+
MdxExpressionFlowNok,
360361

361362
MdxExpressionStart,
362363
MdxExpressionBefore,
@@ -843,6 +844,7 @@ pub fn call(tokenizer: &mut Tokenizer, name: Name) -> State {
843844
Name::MdxExpressionFlowBefore => construct::mdx_expression_flow::before,
844845
Name::MdxExpressionFlowAfter => construct::mdx_expression_flow::after,
845846
Name::MdxExpressionFlowEnd => construct::mdx_expression_flow::end,
847+
Name::MdxExpressionFlowNok => construct::mdx_expression_flow::nok,
846848

847849
Name::MdxExpressionTextStart => construct::mdx_expression_text::start,
848850
Name::MdxExpressionTextAfter => construct::mdx_expression_text::after,

Diff for: tests/mdx_jsx_flow.rs

+115
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
mod test_utils;
12
use markdown::{
23
mdast::{List, ListItem, MdxJsxFlowElement, Node, Paragraph, Root, Text},
34
to_html_with_options, to_mdast,
45
unist::Position,
56
Constructs, Options, ParseOptions,
67
};
78
use pretty_assertions::assert_eq;
9+
use test_utils::swc::{parse_esm, parse_expression};
810

911
#[test]
1012
fn mdx_jsx_flow_agnostic() -> Result<(), String> {
@@ -226,3 +228,116 @@ fn mdx_jsx_flow_essence() -> Result<(), String> {
226228

227229
Ok(())
228230
}
231+
232+
// Flow is mostly the same as `text`, so we only test the relevant
233+
// differences.
234+
#[test]
235+
fn mdx_jsx_flow_interleaving_with_expressions() -> Result<(), String> {
236+
let mdx = Options {
237+
parse: ParseOptions::mdx(),
238+
..Default::default()
239+
};
240+
let swc = Options {
241+
parse: ParseOptions {
242+
constructs: Constructs::mdx(),
243+
mdx_esm_parse: Some(Box::new(parse_esm)),
244+
mdx_expression_parse: Some(Box::new(parse_expression)),
245+
..Default::default()
246+
},
247+
..Default::default()
248+
};
249+
250+
assert_eq!(
251+
to_html_with_options("<div>\n{1}\n</div>", &mdx)?,
252+
"",
253+
"should support tags and expressions (unaware)"
254+
);
255+
256+
assert_eq!(
257+
to_html_with_options("<div>\n{'}'}\n</div>", &swc)?,
258+
"",
259+
"should support tags and expressions (aware)"
260+
);
261+
262+
assert_eq!(
263+
to_html_with_options("x<em>{1}</em>", &swc)?,
264+
"<p>x</p>",
265+
"should support tags and expressions with text before (text)"
266+
);
267+
268+
assert_eq!(
269+
to_html_with_options("<em>x{1}</em>", &swc)?,
270+
"<p>x</p>",
271+
"should support tags and expressions with text between, early (text)"
272+
);
273+
274+
assert_eq!(
275+
to_html_with_options("<em>{1}x</em>", &swc)?,
276+
"<p>x</p>",
277+
"should support tags and expressions with text between, late (text)"
278+
);
279+
280+
assert_eq!(
281+
to_html_with_options("<em>{1}</em>x", &swc)?,
282+
"<p>x</p>",
283+
"should support tags and expressions with text after (text)"
284+
);
285+
286+
assert_eq!(
287+
to_html_with_options("<x/>{1}", &swc)?,
288+
"",
289+
"should support a tag and then an expression (flow)"
290+
);
291+
292+
assert_eq!(
293+
to_html_with_options("<x/>{1}x", &swc)?,
294+
"<p>x</p>",
295+
"should support a tag, an expression, then text (text)"
296+
);
297+
298+
assert_eq!(
299+
to_html_with_options("x<x/>{1}", &swc)?,
300+
"<p>x</p>",
301+
"should support text, a tag, then an expression (text)"
302+
);
303+
304+
assert_eq!(
305+
to_html_with_options("{1}<x/>", &swc)?,
306+
"",
307+
"should support an expression and then a tag (flow)"
308+
);
309+
310+
assert_eq!(
311+
to_html_with_options("{1}<x/>x", &swc)?,
312+
"<p>x</p>",
313+
"should support an expression, a tag, then text (text)"
314+
);
315+
316+
assert_eq!(
317+
to_html_with_options("x{1}<x/>", &swc)?,
318+
"<p>x</p>",
319+
"should support text, an expression, then a tag (text)"
320+
);
321+
322+
assert_eq!(
323+
to_html_with_options("<x>{[\n'',\n{c:''}\n]}</x>", &swc)?,
324+
"",
325+
"should nicely interleaf (micromark/micromark-extension-mdx-jsx#9)"
326+
);
327+
328+
assert_eq!(
329+
to_html_with_options(
330+
"
331+
<style>{`
332+
.foo {}
333+
.bar {}
334+
`}</style>
335+
",
336+
&swc
337+
)?,
338+
"",
339+
"should nicely interleaf (mdx-js/mdx#1945)"
340+
);
341+
342+
Ok(())
343+
}

0 commit comments

Comments
 (0)