Markdown parser fork with extended syntax for personal use.
1use markdown::{
2 mdast::{Definition, LinkReference, Node, Paragraph, ReferenceKind, Root, Text},
3 message, to_html, to_html_with_options, to_mdast,
4 unist::Position,
5 CompileOptions, Constructs, Options, ParseOptions,
6};
7use pretty_assertions::assert_eq;
8
9#[test]
10fn link_reference() -> Result<(), message::Message> {
11 let danger = Options {
12 compile: CompileOptions {
13 allow_dangerous_html: true,
14 allow_dangerous_protocol: true,
15 ..Default::default()
16 },
17 ..Default::default()
18 };
19
20 assert_eq!(
21 to_html("[bar]: /url \"title\"\n\n[foo][bar]"),
22 "<p><a href=\"/url\" title=\"title\">foo</a></p>",
23 "should support link references"
24 );
25
26 assert_eq!(
27 to_html("[ref]: /uri\n\n[link [foo [bar]]][ref]"),
28 "<p><a href=\"/uri\">link [foo [bar]]</a></p>",
29 "should support balanced brackets in link references"
30 );
31
32 assert_eq!(
33 to_html("[ref]: /uri\n\n[link \\[bar][ref]"),
34 "<p><a href=\"/uri\">link [bar</a></p>",
35 "should support escaped brackets in link references"
36 );
37
38 assert_eq!(
39 to_html("[ref]: /uri\n\n[link *foo **bar** `#`*][ref]"),
40 "<p><a href=\"/uri\">link <em>foo <strong>bar</strong> <code>#</code></em></a></p>",
41 "should support content in link references"
42 );
43
44 assert_eq!(
45 to_html("[ref]: /uri\n\n[][ref]"),
46 "<p><a href=\"/uri\"><img src=\"moon.jpg\" alt=\"moon\" /></a></p>",
47 "should support images in link references"
48 );
49
50 assert_eq!(
51 to_html("[ref]: /uri\n\n[foo [bar](/uri)][ref]"),
52 "<p>[foo <a href=\"/uri\">bar</a>]<a href=\"/uri\">ref</a></p>",
53 "should not support links in link references"
54 );
55
56 assert_eq!(
57 to_html("[ref]: /uri\n\n[foo *bar [baz][ref]*][ref]"),
58 "<p>[foo <em>bar <a href=\"/uri\">baz</a></em>]<a href=\"/uri\">ref</a></p>",
59 "should not support deep links in link references"
60 );
61
62 assert_eq!(
63 to_html("[ref]: /uri\n\n*[foo*][ref]"),
64 "<p>*<a href=\"/uri\">foo*</a></p>",
65 "should prefer link references over emphasis (1)"
66 );
67
68 assert_eq!(
69 to_html("[ref]: /uri\n\n[foo *bar][ref]"),
70 "<p><a href=\"/uri\">foo *bar</a></p>",
71 "should prefer link references over emphasis (2)"
72 );
73
74 assert_eq!(
75 to_html_with_options("[ref]: /uri\n\n[foo <bar attr=\"][ref]\">", &danger)?,
76 "<p>[foo <bar attr=\"][ref]\"></p>",
77 "should prefer HTML over link references"
78 );
79
80 assert_eq!(
81 to_html("[ref]: /uri\n\n[foo`][ref]`"),
82 "<p>[foo<code>][ref]</code></p>",
83 "should prefer code over link references"
84 );
85
86 assert_eq!(
87 to_html("[ref]: /uri\n\n[foo<http://example.com/?search=][ref]>"),
88 "<p>[foo<a href=\"http://example.com/?search=%5D%5Bref%5D\">http://example.com/?search=][ref]</a></p>",
89 "should prefer autolinks over link references"
90 );
91
92 assert_eq!(
93 to_html("[bar]: /url \"title\"\n\n[foo][BaR]"),
94 "<p><a href=\"/url\" title=\"title\">foo</a></p>",
95 "should match references to definitions case-insensitively"
96 );
97
98 assert_eq!(
99 to_html("[ТОЛПОЙ]: /url\n\n[Толпой][Толпой] is a Russian word."),
100 "<p><a href=\"/url\">Толпой</a> is a Russian word.</p>",
101 "should match references to definitions w/ unicode case-folding"
102 );
103
104 assert_eq!(
105 to_html("[Foo\n bar]: /url\n\n[Baz][Foo bar]"),
106 "<p><a href=\"/url\">Baz</a></p>",
107 "should match references to definitions w/ collapsing"
108 );
109
110 assert_eq!(
111 to_html("[bar]: /url \"title\"\n\n[foo] [bar]"),
112 "<p>[foo] <a href=\"/url\" title=\"title\">bar</a></p>",
113 "should not support whitespace between label and reference (1)"
114 );
115
116 assert_eq!(
117 to_html("[bar]: /url \"title\"\n\n[foo]\n[bar]"),
118 "<p>[foo]\n<a href=\"/url\" title=\"title\">bar</a></p>",
119 "should not support whitespace between label and reference (2)"
120 );
121
122 assert_eq!(
123 to_html("[foo]: /url1\n\n[foo]: /url2\n\n[bar][foo]"),
124 "<p><a href=\"/url1\">bar</a></p>",
125 "should prefer earlier definitions"
126 );
127
128 assert_eq!(
129 to_html("[foo!]: /url\n\n[bar][foo\\!]"),
130 "<p>[bar][foo!]</p>",
131 "should not match references to definitions w/ escapes"
132 );
133
134 assert_eq!(
135 to_html("[ref[]: /uri\n\n[foo][ref[]"),
136 "<p>[ref[]: /uri</p>\n<p>[foo][ref[]</p>",
137 "should not support references w/ brackets (1)"
138 );
139
140 assert_eq!(
141 to_html("[ref[bar]]: /uri\n\n[foo][ref[bar]]"),
142 "<p>[ref[bar]]: /uri</p>\n<p>[foo][ref[bar]]</p>",
143 "should not support references w/ brackets (2)"
144 );
145
146 assert_eq!(
147 to_html("[[[foo]]]: /url\n\n[[[foo]]]"),
148 "<p>[[[foo]]]: /url</p>\n<p>[[[foo]]]</p>",
149 "should not support references w/ brackets (3)"
150 );
151
152 assert_eq!(
153 to_html("[ref\\[]: /uri\n\n[foo][ref\\[]"),
154 "<p><a href=\"/uri\">foo</a></p>",
155 "should match references to definitions w/ matching escapes"
156 );
157
158 assert_eq!(
159 to_html("[bar\\\\]: /uri\n\n[bar\\\\]"),
160 "<p><a href=\"/uri\">bar\\</a></p>",
161 "should support escapes"
162 );
163
164 assert_eq!(
165 to_html("[]: /uri\n\n[]"),
166 "<p>[]: /uri</p>\n<p>[]</p>",
167 "should not support empty references"
168 );
169
170 assert_eq!(
171 to_html("[\n ]: /uri\n\n[\n ]"),
172 "<p>[\n]: /uri</p>\n<p>[\n]</p>",
173 "should not support blank references"
174 );
175
176 assert_eq!(
177 to_html("[foo]: /url \"title\"\n\n[foo][]"),
178 "<p><a href=\"/url\" title=\"title\">foo</a></p>",
179 "should support collaped references"
180 );
181
182 assert_eq!(
183 to_html("[*foo* bar]: /url \"title\"\n\n[*foo* bar][]"),
184 "<p><a href=\"/url\" title=\"title\"><em>foo</em> bar</a></p>",
185 "should support content in collaped references"
186 );
187
188 assert_eq!(
189 to_html("[foo]: /url \"title\"\n\n[Foo][]"),
190 "<p><a href=\"/url\" title=\"title\">Foo</a></p>",
191 "should match references to definitions case-insensitively"
192 );
193
194 assert_eq!(
195 to_html("[foo]: /url \"title\"\n\n[foo] \n[]"),
196 "<p><a href=\"/url\" title=\"title\">foo</a>\n[]</p>",
197 "should not support whitespace between label and collaped reference"
198 );
199
200 assert_eq!(
201 to_html("[foo]: /url \"title\"\n\n[foo]"),
202 "<p><a href=\"/url\" title=\"title\">foo</a></p>",
203 "should support shortcut references"
204 );
205
206 assert_eq!(
207 to_html("[*foo* bar]: /url \"title\"\n\n[*foo* bar]"),
208 "<p><a href=\"/url\" title=\"title\"><em>foo</em> bar</a></p>",
209 "should support content in shortcut references (1)"
210 );
211
212 assert_eq!(
213 to_html("[*foo* bar]: /url \"title\"\n\n[[*foo* bar]]"),
214 "<p>[<a href=\"/url\" title=\"title\"><em>foo</em> bar</a>]</p>",
215 "should support content in shortcut references (2)"
216 );
217
218 assert_eq!(
219 to_html("[foo]: /url\n\n[[bar [foo]"),
220 "<p>[[bar <a href=\"/url\">foo</a></p>",
221 "should support content in shortcut references (3)"
222 );
223
224 assert_eq!(
225 to_html("[foo]: /url \"title\"\n\n[Foo]"),
226 "<p><a href=\"/url\" title=\"title\">Foo</a></p>",
227 "should match shortcut references to definitions case-insensitively"
228 );
229
230 assert_eq!(
231 to_html("[foo]: /url\n\n[foo] bar"),
232 "<p><a href=\"/url\">foo</a> bar</p>",
233 "should support whitespace after a shortcut reference"
234 );
235
236 assert_eq!(
237 to_html("[foo]: /url \"title\"\n\n\\[foo]"),
238 "<p>[foo]</p>",
239 "should “support” an escaped shortcut reference"
240 );
241
242 assert_eq!(
243 to_html("[foo*]: /url\n\n*[foo*]"),
244 "<p>*<a href=\"/url\">foo*</a></p>",
245 "should prefer shortcut references over emphasis"
246 );
247
248 assert_eq!(
249 to_html("[foo]: /url1\n[bar]: /url2\n\n[foo][bar]"),
250 "<p><a href=\"/url2\">foo</a></p>",
251 "should prefer full references over shortcut references"
252 );
253
254 assert_eq!(
255 to_html("[foo]: /url1\n\n[foo][]"),
256 "<p><a href=\"/url1\">foo</a></p>",
257 "should prefer collapsed references over shortcut references"
258 );
259
260 assert_eq!(
261 to_html("[foo]: /url\n\n[foo]()"),
262 "<p><a href=\"\">foo</a></p>",
263 "should prefer resources over shortcut references (1)"
264 );
265
266 assert_eq!(
267 to_html("[foo]: /url \"title\"\n\n[foo]()"),
268 "<p><a href=\"\">foo</a></p>",
269 "should prefer resources over shortcut references (2)"
270 );
271
272 assert_eq!(
273 to_html("[foo]: /url1\n\n[foo](not a link)"),
274 "<p><a href=\"/url1\">foo</a>(not a link)</p>",
275 "should support shortcut references when followed by nonconforming resources"
276 );
277
278 assert_eq!(
279 to_html("[baz]: /url\n\n[foo][bar][baz]"),
280 "<p>[foo]<a href=\"/url\">bar</a></p>",
281 "stable/unstable (1)"
282 );
283
284 assert_eq!(
285 to_html("[baz]: /url1\n[bar]: /url2\n\n[foo][bar][baz]"),
286 "<p><a href=\"/url2\">foo</a><a href=\"/url1\">baz</a></p>",
287 "stable/unstable (2)"
288 );
289
290 assert_eq!(
291 to_html("[baz]: /url1\n[foo]: /url2\n\n[foo][bar][baz]"),
292 "<p>[foo]<a href=\"/url1\">bar</a></p>",
293 "stable/unstable (3)"
294 );
295
296 // Extra
297 // This matches most implimentations, but is not strictly according to spec.
298 // See: <https://github.com/commonmark/commonmark-spec/issues/653>
299 assert_eq!(
300 to_html("[x]: /url\n\n[x][ ], [x][\t], [x][\n], [x][]"),
301 "<p>[x][ ], [x][\t], [x][\n], <a href=\"/url\">x</a></p>",
302 "should not support whitespace-only full references"
303 );
304
305 // See also: <https://github.com/commonmark/commonmark-spec/issues/616>
306 assert_eq!(
307 to_html("[+]: example.com\n[\\;]: example.com\n\nWill it link? [\\+], [;]"),
308 "<p>Will it link? [+], [;]</p>",
309 "should not support mismatched character escapes in shortcuts"
310 );
311
312 assert_eq!(
313 to_html("[©]: example.com\n[&]: example.com\n\nWill it link? [©], [&]"),
314 "<p>Will it link? [©], [&]</p>",
315 "should not support mismatched character references in shortcuts"
316 );
317
318 assert_eq!(
319 to_html("[+]: example.com\n[\\;]: example.com\n\nWill it link? [\\+][], [;][]"),
320 "<p>Will it link? [+][], [;][]</p>",
321 "should not support mismatched character escapes in collapsed"
322 );
323
324 assert_eq!(
325 to_html("[©]: example.com\n[&]: example.com\n\nWill it link? [©][], [&][]"),
326 "<p>Will it link? [©][], [&][]</p>",
327 "should not support mismatched character references in collapsed"
328 );
329
330 assert_eq!(
331 to_html("[+]: example.com\n[\\;]: example.com\n\nWill it link? [a][ \\+ ], [b][ ; ]"),
332 "<p>Will it link? [a][ + ], [b][ ; ]</p>",
333 "should not support mismatched character escapes in fulls"
334 );
335
336 assert_eq!(
337 to_html("[©]: example.com\n[&]: example.com\n\nWill it link? [a][ © ], [b][ & ]"),
338 "<p>Will it link? [a][ © ], [b][ & ]</p>",
339 "should not support mismatched character references in fulls"
340 );
341
342 assert_eq!(
343 to_html(
344 "[*f*][]
345[;][]
346[\\;][]
347[;][]
348[*f*;][]
349[*f*\\;][]
350[*f*;][]
351
352[*f*]: alpha
353[;]: bravo
354[\\;]: charlie
355[;]: delta
356[*f*;]: echo
357[*f*\\;]: foxtrot
358[*f*;]: golf"
359 ),
360 "<p><a href=\"alpha\"><em>f</em></a>
361<a href=\"bravo\">;</a>
362<a href=\"charlie\">;</a>
363<a href=\"delta\">;</a>
364<a href=\"echo\"><em>f</em>;</a>
365<a href=\"foxtrot\"><em>f</em>;</a>
366<a href=\"golf\"><em>f</em>;</a></p>
367",
368 "should properly handle labels w/ character references and -escapes, and phrasing"
369 );
370
371 // 999 `x` characters.
372 let max = "x".repeat(999);
373
374 assert_eq!(
375 to_html(format!("[{}]: a\n[y][{}]", max, max).as_str()),
376 "<p><a href=\"a\">y</a></p>",
377 "should support 999 characters in a reference"
378 );
379
380 assert_eq!(
381 to_html(format!("[{}x]: a\n[y][{}x]", max, max).as_str()),
382 format!("<p>[{}x]: a\n[y][{}x]</p>", max, max),
383 "should not support 1000 characters in a reference"
384 );
385
386 assert_eq!(
387 to_html("[x] missing-colon\n\nWill it link? [x]"),
388 "<p>[x] missing-colon</p>\n<p>Will it link? [x]</p>",
389 "should not fail on a missing colon in a definition"
390 );
391
392 assert_eq!(
393 to_html_with_options(
394 "[x]()",
395 &Options {
396 parse: ParseOptions {
397 constructs: Constructs {
398 label_start_link: false,
399 ..Default::default()
400 },
401 ..Default::default()
402 },
403 ..Default::default()
404 }
405 )?,
406 "<p>[x]()</p>",
407 "should support turning off label start (link)"
408 );
409
410 assert_eq!(
411 to_html_with_options(
412 "[x]()",
413 &Options {
414 parse: ParseOptions {
415 constructs: Constructs {
416 label_end: false,
417 ..Default::default()
418 },
419 ..Default::default()
420 },
421 ..Default::default()
422 }
423 )?,
424 "<p>[x]()</p>",
425 "should support turning off label end"
426 );
427
428 assert_eq!(
429 to_mdast("[x]: y\n\na [x] b [x][] c [d][x] e.", &Default::default())?,
430 Node::Root(Root {
431 children: vec![
432 Node::Definition(Definition {
433 identifier: "x".into(),
434 label: Some("x".into()),
435 url: "y".into(),
436 title: None,
437 position: Some(Position::new(1, 1, 0, 1, 7, 6))
438 }),
439 Node::Paragraph(Paragraph {
440 children: vec![
441 Node::Text(Text {
442 value: "a ".into(),
443 position: Some(Position::new(3, 1, 8, 3, 3, 10))
444 }),
445 Node::LinkReference(LinkReference {
446 reference_kind: ReferenceKind::Shortcut,
447 identifier: "x".into(),
448 label: Some("x".into()),
449 children: vec![Node::Text(Text {
450 value: "x".into(),
451 position: Some(Position::new(3, 4, 11, 3, 5, 12))
452 }),],
453 position: Some(Position::new(3, 3, 10, 3, 6, 13))
454 }),
455 Node::Text(Text {
456 value: " b ".into(),
457 position: Some(Position::new(3, 6, 13, 3, 9, 16))
458 }),
459 Node::LinkReference(LinkReference {
460 reference_kind: ReferenceKind::Collapsed,
461 identifier: "x".into(),
462 label: Some("x".into()),
463 children: vec![Node::Text(Text {
464 value: "x".into(),
465 position: Some(Position::new(3, 10, 17, 3, 11, 18))
466 }),],
467 position: Some(Position::new(3, 9, 16, 3, 14, 21))
468 }),
469 Node::Text(Text {
470 value: " c ".into(),
471 position: Some(Position::new(3, 14, 21, 3, 17, 24))
472 }),
473 Node::LinkReference(LinkReference {
474 reference_kind: ReferenceKind::Full,
475 identifier: "x".into(),
476 label: Some("x".into()),
477 children: vec![Node::Text(Text {
478 value: "d".into(),
479 position: Some(Position::new(3, 18, 25, 3, 19, 26))
480 }),],
481 position: Some(Position::new(3, 17, 24, 3, 23, 30))
482 }),
483 Node::Text(Text {
484 value: " e.".into(),
485 position: Some(Position::new(3, 23, 30, 3, 26, 33))
486 }),
487 ],
488 position: Some(Position::new(3, 1, 8, 3, 26, 33))
489 }),
490 ],
491 position: Some(Position::new(1, 1, 0, 3, 26, 33))
492 }),
493 "should support link (reference) as `LinkReference`s in mdast"
494 );
495
496 Ok(())
497}