Markdown parser fork with extended syntax for personal use.
at hack 766 lines 17 kB view raw
1use markdown::{mdast::Node, message::Message, Constructs, ParseOptions}; 2use test_utils::swc::{parse_esm, parse_expression}; 3mod test_utils; 4 5#[allow(unused)] 6#[derive(Debug)] 7enum Error { 8 Mdast(Message), 9 Serde(serde_json::Error), 10} 11 12#[test] 13#[cfg(feature = "serde")] 14fn serde_constructs() -> Result<(), Error> { 15 use pretty_assertions::assert_eq; 16 17 assert_eq!( 18 serde_json::to_string(&Constructs::default()).unwrap(), 19 r#"{"attention":true,"autolink":true,"blockQuote":true,"characterEscape":true,"characterReference":true,"codeIndented":true,"codeFenced":true,"codeText":true,"definition":true,"frontmatter":false,"gfmAutolinkLiteral":false,"gfmFootnoteDefinition":false,"gfmLabelStartFootnote":false,"gfmStrikethrough":false,"gfmTable":false,"gfmTaskListItem":false,"hardBreakEscape":true,"hardBreakTrailing":true,"headingAtx":true,"headingSetext":true,"htmlFlow":true,"htmlText":true,"labelStartImage":true,"labelStartLink":true,"labelEnd":true,"listItem":true,"mathFlow":false,"mathText":false,"mdxEsm":false,"mdxExpressionFlow":false,"mdxExpressionText":false,"mdxJsxFlow":false,"mdxJsxText":false,"thematicBreak":true}"# 20 ); 21 22 Ok(()) 23} 24 25#[test] 26#[cfg(feature = "serde")] 27fn serde_compile_options() -> Result<(), Error> { 28 use pretty_assertions::assert_eq; 29 30 assert_eq!( 31 serde_json::to_string(&markdown::CompileOptions::gfm()).unwrap(), 32 r#"{"allowAnyImgSrc":false,"allowDangerousHtml":false,"allowDangerousProtocol":false,"defaultLineEnding":"\n","gfmFootnoteBackLabel":null,"gfmFootnoteClobberPrefix":null,"gfmFootnoteLabelAttributes":null,"gfmFootnoteLabelTagName":null,"gfmFootnoteLabel":null,"gfmTaskListItemCheckable":false,"gfmTagfilter":true}"# 33 ); 34 35 Ok(()) 36} 37 38#[test] 39#[cfg(feature = "serde")] 40fn serde_parse_options() -> Result<(), Error> { 41 use pretty_assertions::assert_eq; 42 43 assert_eq!( 44 serde_json::to_string(&ParseOptions::gfm()).unwrap(), 45 r#"{"constructs":{"attention":true,"autolink":true,"blockQuote":true,"characterEscape":true,"characterReference":true,"codeIndented":true,"codeFenced":true,"codeText":true,"definition":true,"frontmatter":false,"gfmAutolinkLiteral":true,"gfmFootnoteDefinition":true,"gfmLabelStartFootnote":true,"gfmStrikethrough":true,"gfmTable":true,"gfmTaskListItem":true,"hardBreakEscape":true,"hardBreakTrailing":true,"headingAtx":true,"headingSetext":true,"htmlFlow":true,"htmlText":true,"labelStartImage":true,"labelStartLink":true,"labelEnd":true,"listItem":true,"mathFlow":false,"mathText":false,"mdxEsm":false,"mdxExpressionFlow":false,"mdxExpressionText":false,"mdxJsxFlow":false,"mdxJsxText":false,"thematicBreak":true},"gfmStrikethroughSingleTilde":true,"mathTextSingleDollar":true}"# 46 ); 47 48 Ok(()) 49} 50 51#[test] 52fn serde_blockquote() -> Result<(), Error> { 53 assert_serde( 54 "> a", 55 r#"{ 56 "type": "root", 57 "children": [ 58 { 59 "type": "blockquote", 60 "children": [ 61 { 62 "type": "paragraph", 63 "children": [{"type": "text", "value": "a"}] 64 } 65 ] 66 } 67 ] 68} 69"#, 70 ParseOptions::default(), 71 ) 72} 73 74#[test] 75fn serde_footnote_definition() -> Result<(), Error> { 76 assert_serde( 77 "[^a]: b", 78 r#"{ 79 "type": "root", 80 "children": [ 81 { 82 "type": "footnoteDefinition", 83 "identifier": "a", 84 "label": "a", 85 "children": [ 86 { 87 "type": "paragraph", 88 "children": [{"type": "text", "value": "b"}] 89 } 90 ] 91 } 92 ] 93}"#, 94 ParseOptions::gfm(), 95 ) 96} 97 98#[test] 99fn serde_mdx_jsx_flow_element() -> Result<(), Error> { 100 let options = ParseOptions { 101 mdx_esm_parse: Some(Box::new(parse_esm)), 102 mdx_expression_parse: Some(Box::new(parse_expression)), 103 ..ParseOptions::mdx() 104 }; 105 106 assert_serde( 107 "<a b c={d} e=\"f\" {...g} />", 108 r#"{ 109 "type": "root", 110 "children": [ 111 { 112 "type": "mdxJsxFlowElement", 113 "name": "a", 114 "attributes": [ 115 {"type": "mdxJsxAttribute", "name": "b"}, 116 { 117 "type": "mdxJsxAttribute", 118 "name": "c", 119 "value": { 120 "_markdownRsStops": [[0, 8]], 121 "type": "mdxJsxAttributeValueExpression", 122 "value": "d" 123 } 124 }, 125 {"type": "mdxJsxAttribute", "name": "e", "value": "f"}, 126 { 127 "_markdownRsStops": [[0, 18]], 128 "type": "mdxJsxExpressionAttribute", 129 "value": "...g" 130 } 131 ], 132 "children": [] 133 } 134 ] 135}"#, 136 options, 137 ) 138} 139 140#[test] 141fn serde_list() -> Result<(), Error> { 142 assert_serde( 143 "* a", 144 r#"{ 145 "type": "root", 146 "children": [ 147 { 148 "type": "list", 149 "ordered": false, 150 "spread": false, 151 "children": [ 152 { 153 "type": "listItem", 154 "spread": false, 155 "children": [ 156 { 157 "type": "paragraph", 158 "children": [{"type": "text", "value": "a"}] 159 } 160 ] 161 } 162 ] 163 } 164 ] 165}"#, 166 ParseOptions::default(), 167 ) 168} 169 170#[test] 171fn serde_mdxjs_esm() -> Result<(), Error> { 172 let options = ParseOptions { 173 mdx_esm_parse: Some(Box::new(parse_esm)), 174 mdx_expression_parse: Some(Box::new(parse_expression)), 175 ..ParseOptions::mdx() 176 }; 177 178 assert_serde( 179 "import a, {b} from 'c'", 180 r#"{ 181 "type": "root", 182 "children": [ 183 { 184 "_markdownRsStops": [[0, 0]], 185 "type": "mdxjsEsm", 186 "value": "import a, {b} from 'c'" 187 } 188 ] 189}"#, 190 options, 191 ) 192} 193 194#[test] 195fn serde_toml() -> Result<(), Error> { 196 assert_serde( 197 "+++\na: b\n+++", 198 r#"{ 199 "type": "root", 200 "children": [{"type": "toml", "value": "a: b"}] 201}"#, 202 ParseOptions { 203 constructs: Constructs { 204 frontmatter: true, 205 ..Constructs::default() 206 }, 207 ..ParseOptions::default() 208 }, 209 ) 210} 211 212#[test] 213fn serde_yaml() -> Result<(), Error> { 214 assert_serde( 215 "---\na: b\n---", 216 r#"{ 217 "type": "root", 218 "children": [{"type": "yaml", "value": "a: b"}] 219}"#, 220 ParseOptions { 221 constructs: Constructs { 222 frontmatter: true, 223 ..Constructs::default() 224 }, 225 ..ParseOptions::default() 226 }, 227 ) 228} 229 230#[test] 231fn serde_break() -> Result<(), Error> { 232 assert_serde( 233 "a\\\nb", 234 r#"{ 235 "type": "root", 236 "children": [ 237 { 238 "type": "paragraph", 239 "children": [ 240 {"type": "text", "value": "a"}, 241 {"type": "break"}, 242 {"type": "text", "value": "b"} 243 ] 244 } 245 ] 246}"#, 247 ParseOptions::default(), 248 ) 249} 250 251#[test] 252fn serde_inline_code() -> Result<(), Error> { 253 assert_serde( 254 "`a`", 255 r#"{ 256 "type": "root", 257 "children": [ 258 { 259 "type": "paragraph", 260 "children": [{"type": "inlineCode", "value": "a"}] 261 } 262 ] 263}"#, 264 ParseOptions::default(), 265 ) 266} 267 268#[test] 269fn serde_inline_math() -> Result<(), Error> { 270 assert_serde( 271 "$a$", 272 r#"{ 273 "type": "root", 274 "children": [ 275 { 276 "type": "paragraph", 277 "children": [{"type": "inlineMath","value": "a"}] 278 } 279 ] 280}"#, 281 ParseOptions { 282 constructs: Constructs { 283 math_text: true, 284 ..Constructs::default() 285 }, 286 ..ParseOptions::default() 287 }, 288 ) 289} 290 291#[test] 292fn serde_delete() -> Result<(), Error> { 293 assert_serde( 294 "~~a~~", 295 r#"{ 296 "type": "root", 297 "children": [ 298 { 299 "type": "paragraph", 300 "children": [ 301 { 302 "type": "delete", 303 "children": [{"type": "text","value": "a"}] 304 } 305 ] 306 } 307 ] 308}"#, 309 ParseOptions::gfm(), 310 ) 311} 312 313#[test] 314fn serde_emphasis() -> Result<(), Error> { 315 assert_serde( 316 "*a*", 317 r#"{ 318 "type": "root", 319 "children": [ 320 { 321 "type": "paragraph", 322 "children": [ 323 { 324 "type": "emphasis", 325 "children": [{"type": "text","value": "a"}] 326 } 327 ] 328 } 329 ] 330}"#, 331 ParseOptions::default(), 332 ) 333} 334 335#[test] 336fn serde_mdx_text_expression() -> Result<(), Error> { 337 let options = ParseOptions { 338 mdx_esm_parse: Some(Box::new(parse_esm)), 339 mdx_expression_parse: Some(Box::new(parse_expression)), 340 ..ParseOptions::mdx() 341 }; 342 343 assert_serde( 344 "a {b}", 345 r#"{ 346 "type": "root", 347 "children": [ 348 { 349 "type": "paragraph", 350 "children": [ 351 {"type": "text","value": "a "}, 352 { 353 "_markdownRsStops": [[0,3]], 354 "type": "mdxTextExpression", 355 "value": "b" 356 } 357 ] 358 } 359 ] 360}"#, 361 options, 362 ) 363} 364 365#[test] 366fn serde_footnote_reference() -> Result<(), Error> { 367 assert_serde( 368 "[^a]\n\n[^a]: b", 369 r#"{ 370 "type": "root", 371 "children": [ 372 { 373 "type": "paragraph", 374 "children": [ 375 {"type": "footnoteReference", "identifier": "a", "label": "a"} 376 ] 377 }, 378 { 379 "type": "footnoteDefinition", 380 "identifier": "a", 381 "label": "a", 382 "children": [ 383 { 384 "type": "paragraph", 385 "children": [{"type": "text", "value": "b"}] 386 } 387 ] 388 } 389 ] 390}"#, 391 ParseOptions::gfm(), 392 ) 393} 394 395#[test] 396fn serde_html() -> Result<(), Error> { 397 assert_serde( 398 "<a>", 399 r#"{ 400 "type": "root", 401 "children": [{"type": "html", "value": "<a>"}] 402}"#, 403 ParseOptions::gfm(), 404 ) 405} 406 407#[test] 408fn serde_image() -> Result<(), Error> { 409 assert_serde( 410 "![a](b)", 411 r#"{ 412 "type": "root", 413 "children": [ 414 { 415 "type": "paragraph", 416 "children": [{"type": "image", "url": "b", "alt": "a"}] 417 } 418 ] 419}"#, 420 ParseOptions::default(), 421 ) 422} 423 424#[test] 425fn serde_image_reference() -> Result<(), Error> { 426 assert_serde( 427 "![a]\n\n[a]: b", 428 r#"{ 429 "type": "root", 430 "children": [ 431 { 432 "type": "paragraph", 433 "children": [ 434 { 435 "type": "imageReference", 436 "alt": "a", 437 "label": "a", 438 "identifier": "a", 439 "referenceType": "shortcut" 440 } 441 ] 442 }, 443 {"type": "definition", "url": "b", "identifier": "a", "label": "a"} 444 ] 445}"#, 446 ParseOptions::default(), 447 ) 448} 449 450#[test] 451fn serde_mdx_jsx_text_element() -> Result<(), Error> { 452 assert_serde( 453 "a <b c />", 454 r#"{ 455 "type": "root", 456 "children": [ 457 { 458 "type": "paragraph", 459 "children": [ 460 {"type": "text", "value": "a "}, 461 { 462 "type": "mdxJsxTextElement", 463 "name": "b", 464 "attributes": [{"type": "mdxJsxAttribute", "name": "c"}], 465 "children": [] 466 } 467 ] 468 } 469 ] 470}"#, 471 ParseOptions::mdx(), 472 ) 473} 474 475#[test] 476fn serde_link() -> Result<(), Error> { 477 assert_serde( 478 "[a](b)", 479 r#"{ 480 "type": "root", 481 "children": [ 482 { 483 "type": "paragraph", 484 "children": [ 485 { 486 "type": "link", 487 "url": "b", 488 "children": [{"type": "text", "value": "a"}] 489 } 490 ] 491 } 492 ] 493}"#, 494 ParseOptions::default(), 495 ) 496} 497 498#[test] 499fn serde_link_reference() -> Result<(), Error> { 500 assert_serde( 501 "[a]\n\n[a]: b", 502 r#"{ 503 "type": "root", 504 "children": [ 505 { 506 "type": "paragraph", 507 "children": [ 508 { 509 "type": "linkReference", 510 "identifier": "a", 511 "label": "a", 512 "referenceType": "shortcut", 513 "children": [{"type": "text", "value": "a"}] 514 } 515 ] 516 }, 517 { 518 "type": "definition", 519 "url": "b", 520 "identifier": "a", 521 "label": "a" 522 } 523 ] 524}"#, 525 ParseOptions::default(), 526 ) 527} 528 529#[test] 530fn serde_strong() -> Result<(), Error> { 531 assert_serde( 532 "**a**", 533 r#"{ 534 "type": "root", 535 "children": [ 536 { 537 "type": "paragraph", 538 "children": [ 539 { 540 "type": "strong", 541 "children": [{"type": "text", "value": "a"}] 542 } 543 ] 544 } 545 ] 546}"#, 547 ParseOptions::default(), 548 ) 549} 550 551#[test] 552fn serde_text() -> Result<(), Error> { 553 assert_serde( 554 "a", 555 r#"{ 556 "type": "root", 557 "children": [ 558 {"type": "paragraph", "children": [{"type": "text", "value": "a"}]} 559 ] 560}"#, 561 ParseOptions::default(), 562 ) 563} 564 565#[test] 566fn serde_code() -> Result<(), Error> { 567 assert_serde( 568 "~~~js eval\nconsole.log(1)\n~~~", 569 r#"{ 570 "type": "root", 571 "children": [ 572 {"type": "code", "lang": "js", "meta": "eval", "value": "console.log(1)"} 573 ] 574}"#, 575 ParseOptions::default(), 576 )?; 577 578 assert_serde( 579 "```\nconsole.log(1)\n```", 580 r#"{ 581 "type": "root", 582 "children": [{"type": "code", "value": "console.log(1)"}] 583}"#, 584 ParseOptions::default(), 585 ) 586} 587 588#[test] 589fn serde_math() -> Result<(), Error> { 590 assert_serde( 591 "$$\n1 + 1 = 2\n$$", 592 r#"{ 593 "type": "root", 594 "children": [{"type": "math", "value": "1 + 1 = 2"}] 595}"#, 596 ParseOptions { 597 constructs: Constructs { 598 math_flow: true, 599 ..Constructs::default() 600 }, 601 ..ParseOptions::default() 602 }, 603 ) 604} 605 606#[test] 607fn serde_mdx_flow_expression() -> Result<(), Error> { 608 let options = ParseOptions { 609 mdx_esm_parse: Some(Box::new(parse_esm)), 610 mdx_expression_parse: Some(Box::new(parse_expression)), 611 ..ParseOptions::mdx() 612 }; 613 614 assert_serde( 615 "{a}", 616 r#"{ 617 "type": "root", 618 "children": [ 619 {"_markdownRsStops": [[0, 1]], "type": "mdxFlowExpression", "value": "a"} 620 ] 621}"#, 622 options, 623 ) 624} 625 626#[test] 627fn serde_heading() -> Result<(), Error> { 628 assert_serde( 629 "# a", 630 r#"{ 631 "type": "root", 632 "children": [ 633 { 634 "type": "heading", 635 "depth": 1, 636 "children": [{"type": "text", "value": "a"}] 637 } 638 ] 639}"#, 640 ParseOptions::default(), 641 ) 642} 643 644#[test] 645fn serde_table() -> Result<(), Error> { 646 // To do: `"none"` should serialize in serde as `null`. 647 assert_serde( 648 "| a | b | c | d |\n| - | :- | -: | :-: |\n| 1 | 2 | 3 | 4 |", 649 r#"{ 650 "type": "root", 651 "children": [ 652 { 653 "type": "table", 654 "align": [null, "left", "right", "center"], 655 "children": [ 656 { 657 "type": "tableRow", 658 "children": [ 659 {"type": "tableCell", "children": [{"type": "text", "value": "a"}]}, 660 {"type": "tableCell", "children": [{"type": "text", "value": "b"}]}, 661 {"type": "tableCell", "children": [{"type": "text", "value": "c"}]}, 662 {"type": "tableCell", "children": [{"type": "text", "value": "d"}]} 663 ] 664 }, 665 { 666 "type": "tableRow", 667 "children": [ 668 {"type": "tableCell","children": [{"type": "text", "value": "1"}]}, 669 {"type": "tableCell","children": [{"type": "text", "value": "2"}]}, 670 {"type": "tableCell","children": [{"type": "text", "value": "3"}]}, 671 {"type": "tableCell","children": [{"type": "text", "value": "4"}]} 672 ] 673 } 674 ] 675 } 676 ] 677}"#, 678 ParseOptions::gfm(), 679 ) 680} 681 682#[test] 683fn serde_thematic_break() -> Result<(), Error> { 684 assert_serde( 685 "***", 686 r#"{"type": "root", "children": [{"type": "thematicBreak"}]}"#, 687 ParseOptions::default(), 688 ) 689} 690 691#[test] 692fn serde_definition() -> Result<(), Error> { 693 assert_serde( 694 "[a]: b", 695 r###"{ 696 "type": "root", 697 "children": [ 698 {"type": "definition", "url": "b", "identifier": "a", "label": "a"} 699 ] 700}"###, 701 ParseOptions::default(), 702 ) 703} 704 705#[test] 706fn serde_paragraph() -> Result<(), Error> { 707 assert_serde( 708 "a", 709 r#"{ 710 "type": "root", 711 "children": [ 712 { 713 "type": "paragraph", 714 "children": [{"type": "text", "value": "a"}] 715 } 716 ] 717}"#, 718 ParseOptions::default(), 719 ) 720} 721 722/// Assert serde of mdast constructs. 723/// 724/// Refer below links for the mdast JSON construct types. 725/// * <https://github.com/syntax-tree/mdast#nodes> 726/// * <https://github.com/syntax-tree/mdast-util-mdx#syntax-tree> 727/// * <https://github.com/syntax-tree/mdast-util-frontmatter#syntax-tree> 728#[cfg(feature = "serde")] 729fn assert_serde(input: &str, expected: &str, options: ParseOptions) -> Result<(), Error> { 730 use pretty_assertions::assert_eq; 731 732 let mut source = markdown::to_mdast(input, &options).map_err(Error::Mdast)?; 733 734 remove_position(&mut source); 735 // Serialize to JSON 736 let actual_value: serde_json::Value = serde_json::to_value(&source).map_err(Error::Serde)?; 737 let expected_value: serde_json::Value = serde_json::from_str(expected).map_err(Error::Serde)?; 738 739 // Assert serialization. 740 assert_eq!(actual_value, expected_value); 741 742 // Assert deserialization. 743 assert_eq!( 744 source, 745 serde_json::from_value(actual_value).map_err(Error::Serde)? 746 ); 747 748 Ok(()) 749} 750 751#[cfg(not(feature = "serde"))] 752#[allow(unused_variables)] 753fn assert_serde(input: &str, expected: &str, options: ParseOptions) -> Result<(), Error> { 754 Ok(()) 755} 756 757#[allow(dead_code)] 758fn remove_position(node: &mut Node) { 759 if let Some(children) = node.children_mut() { 760 for child in children { 761 remove_position(child); 762 } 763 } 764 765 node.position_set(None); 766}