Markdown parser fork with extended syntax for personal use.

Add serde tests

Related-to GH-135.
Related-to GH-137.
Closes GH-140.

authored by

Harsha Teja Kanna and committed by
GitHub
08d6764d 5f781cdb

+1047 -82
+71 -2
src/mdast.rs
··· 448 448 #[cfg_attr( 449 449 feature = "serde", 450 450 derive(serde::Serialize, serde::Deserialize), 451 - serde(untagged, rename = "mdxJsxExpressionAttribute") 451 + serde(untagged) 452 452 )] 453 453 pub enum AttributeContent { 454 454 /// JSX expression. ··· 457 457 /// > | <a {...b} /> 458 458 /// ^^^^^^ 459 459 /// ``` 460 - Expression { value: String, stops: Vec<Stop> }, 460 + Expression(MdxJsxExpressionAttribute), 461 461 /// JSX property. 462 462 /// 463 463 /// ```markdown ··· 515 515 /// Content model. 516 516 pub children: Vec<Node>, 517 517 /// Positional info. 518 + #[serde(skip_serializing_if = "Option::is_none")] 518 519 pub position: Option<Position>, 519 520 } 520 521 ··· 531 532 /// Content model. 532 533 pub children: Vec<Node>, 533 534 /// Positional info. 535 + #[serde(skip_serializing_if = "Option::is_none")] 534 536 pub position: Option<Position>, 535 537 } 536 538 ··· 547 549 /// Content model. 548 550 pub children: Vec<Node>, 549 551 /// Positional info. 552 + #[serde(skip_serializing_if = "Option::is_none")] 550 553 pub position: Option<Position>, 551 554 // Extra. 552 555 /// Rank (between `1` and `6`, both including). ··· 564 567 pub struct ThematicBreak { 565 568 // Void. 566 569 /// Positional info. 570 + #[serde(skip_serializing_if = "Option::is_none")] 567 571 pub position: Option<Position>, 568 572 } 569 573 ··· 580 584 /// Content model. 581 585 pub children: Vec<Node>, 582 586 /// Positional info. 587 + #[serde(skip_serializing_if = "Option::is_none")] 583 588 pub position: Option<Position>, 584 589 } 585 590 ··· 596 601 /// Content model. 597 602 pub children: Vec<Node>, 598 603 /// Positional info. 604 + #[serde(skip_serializing_if = "Option::is_none")] 599 605 pub position: Option<Position>, 600 606 // Extra. 601 607 /// Ordered (`true`) or unordered (`false`). 602 608 pub ordered: bool, 603 609 /// Starting number of the list. 604 610 /// `None` when unordered. 611 + #[serde(skip_serializing_if = "Option::is_none")] 605 612 pub start: Option<u32>, 606 613 /// One or more of its children are separated with a blank line from its 607 614 /// siblings (when `true`), or not (when `false`). ··· 621 628 /// Content model. 622 629 pub children: Vec<Node>, 623 630 /// Positional info. 631 + #[serde(skip_serializing_if = "Option::is_none")] 624 632 pub position: Option<Position>, 625 633 // Extra. 626 634 /// The item contains two or more children separated by a blank line ··· 628 636 pub spread: bool, 629 637 /// GFM: whether the item is done (when `true`), not done (when `false`), 630 638 /// or indeterminate or not applicable (`None`). 639 + #[serde(skip_serializing_if = "Option::is_none")] 631 640 pub checked: Option<bool>, 632 641 } 633 642 ··· 644 653 /// Content model. 645 654 pub value: String, 646 655 /// Positional info. 656 + #[serde(skip_serializing_if = "Option::is_none")] 647 657 pub position: Option<Position>, 648 658 } 649 659 ··· 664 674 /// Content model. 665 675 pub value: String, 666 676 /// Positional info. 677 + #[serde(skip_serializing_if = "Option::is_none")] 667 678 pub position: Option<Position>, 668 679 // Extra. 669 680 /// The language of computer code being marked up. 681 + #[serde(skip_serializing_if = "Option::is_none")] 670 682 pub lang: Option<String>, 671 683 /// Custom info relating to the node. 684 + #[serde(skip_serializing_if = "Option::is_none")] 672 685 pub meta: Option<String>, 673 686 } 674 687 ··· 689 702 /// Content model. 690 703 pub value: String, 691 704 /// Positional info. 705 + #[serde(skip_serializing_if = "Option::is_none")] 692 706 pub position: Option<Position>, 693 707 // Extra. 694 708 /// Custom info relating to the node. 709 + #[serde(skip_serializing_if = "Option::is_none")] 695 710 pub meta: Option<String>, 696 711 } 697 712 ··· 706 721 pub struct Definition { 707 722 // Void. 708 723 /// Positional info. 724 + #[serde(skip_serializing_if = "Option::is_none")] 709 725 pub position: Option<Position>, 710 726 // Resource. 711 727 /// URL to the referenced resource. 712 728 pub url: String, 713 729 /// Advisory info for the resource, such as something that would be 714 730 /// appropriate for a tooltip. 731 + #[serde(skip_serializing_if = "Option::is_none")] 715 732 pub title: Option<String>, 716 733 // Association. 717 734 /// Value that can match another node. ··· 725 742 /// To normalize a value, collapse markdown whitespace (`[\t\n\r ]+`) to a 726 743 /// space, trim the optional initial and/or final space, and perform 727 744 /// case-folding. 745 + #[serde(skip_serializing_if = "Option::is_none")] 728 746 pub label: Option<String>, 729 747 } 730 748 ··· 741 759 /// Content model. 742 760 pub value: String, 743 761 /// Positional info. 762 + #[serde(skip_serializing_if = "Option::is_none")] 744 763 pub position: Option<Position>, 745 764 } 746 765 ··· 757 776 /// Content model. 758 777 pub children: Vec<Node>, 759 778 /// Positional info. 779 + #[serde(skip_serializing_if = "Option::is_none")] 760 780 pub position: Option<Position>, 761 781 } 762 782 ··· 773 793 /// Content model. 774 794 pub children: Vec<Node>, 775 795 /// Positional info. 796 + #[serde(skip_serializing_if = "Option::is_none")] 776 797 pub position: Option<Position>, 777 798 } 778 799 ··· 789 810 /// Content model. 790 811 pub value: String, 791 812 /// Positional info. 813 + #[serde(skip_serializing_if = "Option::is_none")] 792 814 pub position: Option<Position>, 793 815 } 794 816 ··· 805 827 /// Content model. 806 828 pub value: String, 807 829 /// Positional info. 830 + #[serde(skip_serializing_if = "Option::is_none")] 808 831 pub position: Option<Position>, 809 832 } 810 833 ··· 820 843 pub struct Break { 821 844 // Void. 822 845 /// Positional info. 846 + #[serde(skip_serializing_if = "Option::is_none")] 823 847 pub position: Option<Position>, 824 848 } 825 849 ··· 836 860 /// Content model. 837 861 pub children: Vec<Node>, 838 862 /// Positional info. 863 + #[serde(skip_serializing_if = "Option::is_none")] 839 864 pub position: Option<Position>, 840 865 // Resource. 841 866 /// URL to the referenced resource. 842 867 pub url: String, 843 868 /// Advisory info for the resource, such as something that would be 844 869 /// appropriate for a tooltip. 870 + #[serde(skip_serializing_if = "Option::is_none")] 845 871 pub title: Option<String>, 846 872 } 847 873 ··· 856 882 pub struct Image { 857 883 // Void. 858 884 /// Positional info. 885 + #[serde(skip_serializing_if = "Option::is_none")] 859 886 pub position: Option<Position>, 860 887 // Alternative. 861 888 /// Equivalent content for environments that cannot represent the node as ··· 866 893 pub url: String, 867 894 /// Advisory info for the resource, such as something that would be 868 895 /// appropriate for a tooltip. 896 + #[serde(skip_serializing_if = "Option::is_none")] 869 897 pub title: Option<String>, 870 898 } 871 899 ··· 882 910 /// Content model. 883 911 pub children: Vec<Node>, 884 912 /// Positional info. 913 + #[serde(skip_serializing_if = "Option::is_none")] 885 914 pub position: Option<Position>, 886 915 // Reference. 887 916 /// Explicitness of a reference. ··· 899 928 /// To normalize a value, collapse markdown whitespace (`[\t\n\r ]+`) to a 900 929 /// space, trim the optional initial and/or final space, and perform 901 930 /// case-folding. 931 + #[serde(skip_serializing_if = "Option::is_none")] 902 932 pub label: Option<String>, 903 933 } 904 934 ··· 913 943 pub struct ImageReference { 914 944 // Void. 915 945 /// Positional info. 946 + #[serde(skip_serializing_if = "Option::is_none")] 916 947 pub position: Option<Position>, 917 948 // Alternative. 918 949 /// Equivalent content for environments that cannot represent the node as ··· 934 965 /// To normalize a value, collapse markdown whitespace (`[\t\n\r ]+`) to a 935 966 /// space, trim the optional initial and/or final space, and perform 936 967 /// case-folding. 968 + #[serde(skip_serializing_if = "Option::is_none")] 937 969 pub label: Option<String>, 938 970 } 939 971 ··· 950 982 /// Content model. 951 983 pub children: Vec<Node>, 952 984 /// Positional info. 985 + #[serde(skip_serializing_if = "Option::is_none")] 953 986 pub position: Option<Position>, 954 987 // Association. 955 988 /// Value that can match another node. ··· 963 996 /// To normalize a value, collapse markdown whitespace (`[\t\n\r ]+`) to a 964 997 /// space, trim the optional initial and/or final space, and perform 965 998 /// case-folding. 999 + #[serde(skip_serializing_if = "Option::is_none")] 966 1000 pub label: Option<String>, 967 1001 } 968 1002 ··· 977 1011 pub struct FootnoteReference { 978 1012 // Void. 979 1013 /// Positional info. 1014 + #[serde(skip_serializing_if = "Option::is_none")] 980 1015 pub position: Option<Position>, 981 1016 // Association. 982 1017 /// Value that can match another node. ··· 990 1025 /// To normalize a value, collapse markdown whitespace (`[\t\n\r ]+`) to a 991 1026 /// space, trim the optional initial and/or final space, and perform 992 1027 /// case-folding. 1028 + #[serde(skip_serializing_if = "Option::is_none")] 993 1029 pub label: Option<String>, 994 1030 } 995 1031 ··· 1008 1044 /// Content model. 1009 1045 pub children: Vec<Node>, 1010 1046 /// Positional info. 1047 + #[serde(skip_serializing_if = "Option::is_none")] 1011 1048 pub position: Option<Position>, 1012 1049 // Extra. 1013 1050 /// Represents how cells in columns are aligned. ··· 1027 1064 /// Content model. 1028 1065 pub children: Vec<Node>, 1029 1066 /// Positional info. 1067 + #[serde(skip_serializing_if = "Option::is_none")] 1030 1068 pub position: Option<Position>, 1031 1069 } 1032 1070 ··· 1043 1081 /// Content model. 1044 1082 pub children: Vec<Node>, 1045 1083 /// Positional info. 1084 + #[serde(skip_serializing_if = "Option::is_none")] 1046 1085 pub position: Option<Position>, 1047 1086 } 1048 1087 ··· 1059 1098 /// Content model. 1060 1099 pub children: Vec<Node>, 1061 1100 /// Positional info. 1101 + #[serde(skip_serializing_if = "Option::is_none")] 1062 1102 pub position: Option<Position>, 1063 1103 } 1064 1104 ··· 1079 1119 /// Content model. 1080 1120 pub value: String, 1081 1121 /// Positional info. 1122 + #[serde(skip_serializing_if = "Option::is_none")] 1082 1123 pub position: Option<Position>, 1083 1124 } 1084 1125 ··· 1099 1140 /// Content model. 1100 1141 pub value: String, 1101 1142 /// Positional info. 1143 + #[serde(skip_serializing_if = "Option::is_none")] 1102 1144 pub position: Option<Position>, 1103 1145 } 1104 1146 ··· 1115 1157 /// Content model. 1116 1158 pub value: String, 1117 1159 /// Positional info. 1160 + #[serde(skip_serializing_if = "Option::is_none")] 1118 1161 pub position: Option<Position>, 1119 1162 1120 1163 // Custom data on where each slice of `value` came from. ··· 1134 1177 /// Content model. 1135 1178 pub value: String, 1136 1179 /// Positional info. 1180 + #[serde(skip_serializing_if = "Option::is_none")] 1137 1181 pub position: Option<Position>, 1138 1182 1139 1183 // Custom data on where each slice of `value` came from. ··· 1153 1197 /// Content model. 1154 1198 pub value: String, 1155 1199 /// Positional info. 1200 + #[serde(skip_serializing_if = "Option::is_none")] 1156 1201 pub position: Option<Position>, 1157 1202 1158 1203 // Custom data on where each slice of `value` came from. ··· 1176 1221 /// Content model. 1177 1222 pub children: Vec<Node>, 1178 1223 /// Positional info. 1224 + #[serde(skip_serializing_if = "Option::is_none")] 1179 1225 pub position: Option<Position>, 1180 1226 // JSX element. 1181 1227 /// Name. 1182 1228 /// 1183 1229 /// Fragments have no name. 1230 + #[serde(skip_serializing_if = "Option::is_none")] 1184 1231 pub name: Option<String>, 1185 1232 /// Attributes. 1186 1233 pub attributes: Vec<AttributeContent>, ··· 1203 1250 /// Content model. 1204 1251 pub children: Vec<Node>, 1205 1252 /// Positional info. 1253 + #[serde(skip_serializing_if = "Option::is_none")] 1206 1254 pub position: Option<Position>, 1207 1255 // JSX element. 1208 1256 /// Name. 1209 1257 /// 1210 1258 /// Fragments have no name. 1259 + #[serde(skip_serializing_if = "Option::is_none")] 1211 1260 pub name: Option<String>, 1212 1261 /// Attributes. 1213 1262 pub attributes: Vec<AttributeContent>, ··· 1232 1281 /// Key. 1233 1282 pub name: String, 1234 1283 /// Value. 1284 + #[serde(skip_serializing_if = "Option::is_none")] 1235 1285 pub value: Option<AttributeValue>, 1286 + } 1287 + 1288 + /// MDX: JSX expression attribute. 1289 + /// 1290 + /// ```markdown 1291 + /// > | <a {...b} /> 1292 + /// ^ 1293 + /// ``` 1294 + #[derive(Clone, Debug, Eq, PartialEq)] 1295 + #[cfg_attr( 1296 + feature = "serde", 1297 + derive(serde::Serialize, serde::Deserialize), 1298 + serde(tag = "type", rename = "mdxJsxExpressionAttribute") 1299 + )] 1300 + pub struct MdxJsxExpressionAttribute { 1301 + /// Value. 1302 + pub value: String, 1303 + /// Stops 1304 + pub stops: Vec<Stop>, 1236 1305 } 1237 1306 1238 1307 #[cfg(test)]
+7 -4
src/to_mdast.rs
··· 5 5 AttributeContent, AttributeValue, AttributeValueExpression, Blockquote, Break, Code, 6 6 Definition, Delete, Emphasis, FootnoteDefinition, FootnoteReference, Heading, Html, Image, 7 7 ImageReference, InlineCode, InlineMath, Link, LinkReference, List, ListItem, Math, 8 - MdxFlowExpression, MdxJsxAttribute, MdxJsxFlowElement, MdxJsxTextElement, MdxTextExpression, 9 - MdxjsEsm, Node, Paragraph, ReferenceKind, Root, Strong, Table, TableCell, TableRow, Text, 10 - ThematicBreak, Toml, Yaml, 8 + MdxFlowExpression, MdxJsxAttribute, MdxJsxExpressionAttribute, MdxJsxFlowElement, 9 + MdxJsxTextElement, MdxTextExpression, MdxjsEsm, Node, Paragraph, ReferenceKind, Root, Strong, 10 + Table, TableCell, TableRow, Text, ThematicBreak, Toml, Yaml, 11 11 }; 12 12 use crate::message; 13 13 use crate::unist::{Point, Position}; ··· 858 858 .as_mut() 859 859 .expect("expected tag") 860 860 .attributes 861 - .push(AttributeContent::Expression { value, stops }); 861 + .push(AttributeContent::Expression(MdxJsxExpressionAttribute { 862 + value, 863 + stops, 864 + })); 862 865 863 866 context.buffer(); 864 867
+4 -3
tests/mdx_jsx_text.rs
··· 2 2 use markdown::{ 3 3 mdast::{ 4 4 AttributeContent, AttributeValue, AttributeValueExpression, Emphasis, MdxFlowExpression, 5 - MdxJsxAttribute, MdxJsxFlowElement, MdxJsxTextElement, Node, Paragraph, Root, Text, 5 + MdxJsxAttribute, MdxJsxExpressionAttribute, MdxJsxFlowElement, MdxJsxTextElement, Node, 6 + Paragraph, Root, Text, 6 7 }, 7 8 message, to_html_with_options, to_mdast, 8 9 unist::Position, ··· 204 205 children: vec![ 205 206 Node::MdxJsxTextElement(MdxJsxTextElement { 206 207 name: Some("a".into()), 207 - attributes: vec![AttributeContent::Expression { 208 + attributes: vec![AttributeContent::Expression(MdxJsxExpressionAttribute { 208 209 value: "...b".into(), 209 210 stops: vec![(0, 4)] 210 - }], 211 + })], 211 212 children: vec![], 212 213 position: Some(Position::new(1, 1, 0, 1, 13, 12)) 213 214 }),
+965 -73
tests/serde.rs
··· 1 1 use markdown::mdast::Node; 2 - use markdown::message::Message; 2 + 3 + mod test_utils; 3 4 4 5 #[allow(unused)] 5 6 #[derive(Debug)] 6 7 enum Error { 7 - Mdast(Message), 8 + Mdast(markdown::message::Message), 8 9 Serde(serde_json::Error), 9 10 } 10 11 11 12 #[cfg_attr(feature = "serde", test)] 12 - fn serde() -> Result<(), Error> { 13 - let source = markdown::to_mdast( 14 - r#"--- 15 - title: Serde 16 - --- 13 + fn serde_blockquote() -> Result<(), Error> { 14 + assert_serde( 15 + "> a", 16 + r#"{ 17 + "type": "root", 18 + "children": [ 19 + { 20 + "type": "blockquote", 21 + "children": [ 22 + { 23 + "type": "paragraph", 24 + "children": [ 25 + { 26 + "type": "text", 27 + "value": "a" 28 + } 29 + ] 30 + } 31 + ] 32 + } 33 + ] 34 + } 35 + "#, 36 + None, 37 + ) 38 + } 17 39 18 - import Test from 'test'; 19 - import Inner from 'test'; 20 - import {Another, YetAnother} from 'another'; 40 + #[cfg_attr(feature = "serde", test)] 41 + fn serde_footnote_definition() -> Result<(), Error> { 42 + assert_serde( 43 + "[^a]: b", 44 + r#"{ 45 + "type": "root", 46 + "children": [ 47 + { 48 + "type": "footnoteDefinition", 49 + "children": [ 50 + { 51 + "type": "paragraph", 52 + "children": [ 53 + { 54 + "type": "text", 55 + "value": "b" 56 + } 57 + ] 58 + } 59 + ], 60 + "identifier": "a", 61 + "label": "a" 62 + } 63 + ] 64 + }"#, 65 + None, 66 + ) 67 + } 21 68 22 - # <HelloMessage />, {username}! 69 + #[cfg_attr(feature = "serde", test)] 70 + fn serde_mdx_jsx_flow_element() -> Result<(), Error> { 71 + let source = r#"<Test id={id} class="test" {...b} />"#; 72 + assert_serde( 73 + source, 74 + r#"{ 75 + "type": "root", 76 + "children": [ 77 + { 78 + "type": "mdxJsxFlowElement", 79 + "children": [], 80 + "name": "Test", 81 + "attributes": [ 82 + { 83 + "type": "mdxJsxAttribute", 84 + "name": "id", 85 + "value": { 86 + "type": "mdxJsxAttributeValueExpression", 87 + "value": "id", 88 + "stops": [ 89 + [ 90 + 0, 91 + 10 92 + ] 93 + ] 94 + } 95 + }, 96 + { 97 + "type": "mdxJsxAttribute", 98 + "name": "class", 99 + "value": "test" 100 + }, 101 + { 102 + "type": "mdxJsxExpressionAttribute", 103 + "value": "...b", 104 + "stops": [ 105 + [ 106 + 0, 107 + 28 108 + ] 109 + ] 110 + } 111 + ] 112 + } 113 + ] 114 + }"#, 115 + None, 116 + ) 117 + } 23 118 24 - > Blockquote 25 - Add test constructs below! 119 + #[cfg_attr(feature = "serde", test)] 120 + fn serde_list() -> Result<(), Error> { 121 + assert_serde( 122 + "* a", 123 + r#"{ 124 + "type": "root", 125 + "children": [ 126 + { 127 + "type": "list", 128 + "children": [ 129 + { 130 + "type": "listItem", 131 + "children": [ 132 + { 133 + "type": "paragraph", 134 + "children": [ 135 + { 136 + "type": "text", 137 + "value": "a" 138 + } 139 + ] 140 + } 141 + ], 142 + "spread": false 143 + } 144 + ], 145 + "ordered": false, 146 + "spread": false 147 + } 148 + ] 149 + }"#, 150 + None, 151 + ) 152 + } 26 153 27 - ## Test serialization and deserialization of mdast 154 + #[cfg_attr(feature = "serde", test)] 155 + fn serde_mdxjs_esm() -> Result<(), Error> { 156 + assert_serde( 157 + r#" 158 + import Test from 'test'; 159 + "#, 160 + r#"{ 161 + "type": "root", 162 + "children": [ 163 + { 164 + "type": "mdxjsEsm", 165 + "value": "import Test from 'test';", 166 + "stops": [ 167 + [ 168 + 0, 169 + 1 170 + ] 171 + ] 172 + } 173 + ] 174 + }"#, 175 + None, 176 + ) 177 + } 28 178 29 - <Test id={id} name="test"> 30 - <Inner name="inner" id={id}> 31 - ## Inner 32 - [Link](./link.md) 33 - </Inner> 34 - </Test> 179 + #[cfg_attr(feature = "serde", test)] 180 + fn serde_toml() -> Result<(), Error> { 181 + assert_serde( 182 + r#"+++ 183 + a: b 184 + +++ 185 + "#, 186 + r#"{ 187 + "type": "root", 188 + "children": [ 189 + { 190 + "type": "toml", 191 + "value": "a: b" 192 + } 193 + ] 194 + }"#, 195 + None, 196 + ) 197 + } 35 198 36 - <Another id={id} class="test" /> 199 + #[cfg_attr(feature = "serde", test)] 200 + fn serde_yaml() -> Result<(), Error> { 201 + assert_serde( 202 + r#"--- 203 + a: b 204 + --- 205 + "#, 206 + r#"{ 207 + "type": "root", 208 + "children": [ 209 + { 210 + "type": "yaml", 211 + "value": "a: b" 212 + } 213 + ] 214 + }"#, 215 + None, 216 + ) 217 + } 37 218 38 - {test} this is text expression 219 + #[cfg_attr(feature = "serde", test)] 220 + fn serde_break() -> Result<(), Error> { 221 + let source = r#"a\ 222 + b 223 + "#; 224 + assert_serde( 225 + source, 226 + r#"{ 227 + "type": "root", 228 + "children": [ 229 + { 230 + "type": "paragraph", 231 + "children": [ 232 + { 233 + "type": "text", 234 + "value": "a" 235 + }, 236 + { 237 + "type": "break" 238 + }, 239 + { 240 + "type": "text", 241 + "value": "b" 242 + } 243 + ] 244 + } 245 + ] 246 + }"#, 247 + None, 248 + ) 249 + } 39 250 40 - <YetAnother id={id} class="test" /> 251 + #[cfg_attr(feature = "serde", test)] 252 + fn serde_inline_code() -> Result<(), Error> { 253 + assert_serde( 254 + "`a`", 255 + r#"{ 256 + "type": "root", 257 + "children": [ 258 + { 259 + "type": "paragraph", 260 + "children": [ 261 + { 262 + "type": "inlineCode", 263 + "value": "a" 264 + } 265 + ] 266 + } 267 + ] 268 + }"#, 269 + None, 270 + ) 271 + } 41 272 42 - # Text 273 + #[cfg_attr(feature = "serde", test)] 274 + fn serde_inline_math() -> Result<(), Error> { 275 + assert_serde( 276 + "$a$", 277 + r#"{ 278 + "type": "root", 279 + "children": [ 280 + { 281 + "type": "paragraph", 282 + "children": [ 283 + { 284 + "type": "inlineMath", 285 + "value": "a" 286 + } 287 + ] 288 + } 289 + ] 290 + }"#, 291 + None, 292 + ) 293 + } 43 294 44 - ~~The world is flat.~~ We now know that the world is round. 295 + #[cfg_attr(feature = "serde", test)] 296 + fn serde_delete() -> Result<(), Error> { 297 + assert_serde( 298 + "~~a~~", 299 + r#"{ 300 + "type": "root", 301 + "children": [ 302 + { 303 + "type": "paragraph", 304 + "children": [ 305 + { 306 + "type": "delete", 307 + "children": [ 308 + { 309 + "type": "text", 310 + "value": "a" 311 + } 312 + ] 313 + } 314 + ] 315 + } 316 + ] 317 + }"#, 318 + None, 319 + ) 320 + } 45 321 46 - *Emphasis* 322 + #[cfg_attr(feature = "serde", test)] 323 + fn serde_emphasis() -> Result<(), Error> { 324 + assert_serde( 325 + "*a*", 326 + r#"{ 327 + "type": "root", 328 + "children": [ 329 + { 330 + "type": "paragraph", 331 + "children": [ 332 + { 333 + "type": "emphasis", 334 + "children": [ 335 + { 336 + "type": "text", 337 + "value": "a" 338 + } 339 + ] 340 + } 341 + ] 342 + } 343 + ] 344 + }"#, 345 + None, 346 + ) 347 + } 47 348 48 - *Strong Emphasis* 349 + #[cfg_attr(feature = "serde", test)] 350 + fn serde_mdx_text_expression() -> Result<(), Error> { 351 + assert_serde( 352 + "a {b}", 353 + r#"{ 354 + "type": "root", 355 + "children": [ 356 + { 357 + "type": "paragraph", 358 + "children": [ 359 + { 360 + "type": "text", 361 + "value": "a " 362 + }, 363 + { 364 + "type": "mdxTextExpression", 365 + "value": "b", 366 + "stops": [ 367 + [ 368 + 0, 369 + 3 370 + ] 371 + ] 372 + } 373 + ] 374 + } 375 + ] 376 + }"#, 377 + None, 378 + ) 379 + } 49 380 50 - $This is math$ 381 + #[cfg_attr(feature = "serde", test)] 382 + fn serde_footnote_reference() -> Result<(), Error> { 383 + let source = r#"Refer to [^a] 384 + [^a]: b 385 + "#; 386 + assert_serde( 387 + source, 388 + r#"{ 389 + "type": "root", 390 + "children": [ 391 + { 392 + "type": "paragraph", 393 + "children": [ 394 + { 395 + "type": "text", 396 + "value": "Refer to " 397 + }, 398 + { 399 + "type": "footnoteReference", 400 + "identifier": "a", 401 + "label": "a" 402 + } 403 + ] 404 + }, 405 + { 406 + "type": "footnoteDefinition", 407 + "children": [ 408 + { 409 + "type": "paragraph", 410 + "children": [ 411 + { 412 + "type": "text", 413 + "value": "b" 414 + } 415 + ] 416 + } 417 + ], 418 + "identifier": "a", 419 + "label": "a" 420 + } 421 + ] 422 + }"#, 423 + None, 424 + ) 425 + } 51 426 52 - Let's break\ 53 - yes! 427 + #[cfg_attr(feature = "serde", test)] 428 + fn serde_html() -> Result<(), Error> { 429 + assert_serde( 430 + "<a>", 431 + r#"{ 432 + "type": "root", 433 + "children": [ 434 + { 435 + "type": "html", 436 + "value": "<a>" 437 + } 438 + ] 439 + }"#, 440 + Some(markdown::ParseOptions { 441 + constructs: markdown::Constructs::gfm(), 442 + ..markdown::ParseOptions::gfm() 443 + }), 444 + ) 445 + } 54 446 55 - *** 56 - 57 - ## List 58 - 59 - * item1 60 - * item2 61 - * item3 62 - 63 - ## Code block 64 - 65 - ```shell 66 - cargo test --features json 67 - ``` 68 - 69 - ## Inline 447 + #[cfg_attr(feature = "serde", test)] 448 + fn serde_image() -> Result<(), Error> { 449 + assert_serde( 450 + "![a](b)", 451 + r#"{ 452 + "type": "root", 453 + "children": [ 454 + { 455 + "type": "paragraph", 456 + "children": [ 457 + { 458 + "type": "image", 459 + "alt": "a", 460 + "url": "b" 461 + } 462 + ] 463 + } 464 + ] 465 + }"#, 466 + None, 467 + ) 468 + } 70 469 71 - `Inline code` with backticks 470 + #[cfg_attr(feature = "serde", test)] 471 + fn serde_image_reference() -> Result<(), Error> { 472 + let source = r#"[x]: y 473 + a ![x] b 474 + "#; 475 + assert_serde( 476 + source, 477 + r#"{ 478 + "type": "root", 479 + "children": [ 480 + { 481 + "type": "definition", 482 + "url": "y", 483 + "identifier": "x", 484 + "label": "x" 485 + }, 486 + { 487 + "type": "paragraph", 488 + "children": [ 489 + { 490 + "type": "text", 491 + "value": "a " 492 + }, 493 + { 494 + "type": "imageReference", 495 + "alt": "x", 496 + "referenceType": "shortcut", 497 + "identifier": "x", 498 + "label": "x" 499 + }, 500 + { 501 + "type": "text", 502 + "value": " b" 503 + } 504 + ] 505 + } 506 + ] 507 + }"#, 508 + None, 509 + ) 510 + } 72 511 73 - ## Image 512 + #[cfg_attr(feature = "serde", test)] 513 + fn serde_mdx_jsx_text_element() -> Result<(), Error> { 514 + let source = r#"text <Test id={id} class="test" {...b} />"#; 515 + assert_serde( 516 + source, 517 + r#"{ 518 + "type": "root", 519 + "children": [ 520 + { 521 + "type": "paragraph", 522 + "children": [ 523 + { 524 + "type": "text", 525 + "value": "text " 526 + }, 527 + { 528 + "type": "mdxJsxTextElement", 529 + "children": [], 530 + "name": "Test", 531 + "attributes": [ 532 + { 533 + "type": "mdxJsxAttribute", 534 + "name": "id", 535 + "value": { 536 + "type": "mdxJsxAttributeValueExpression", 537 + "value": "id", 538 + "stops": [ 539 + [ 540 + 0, 541 + 15 542 + ] 543 + ] 544 + } 545 + }, 546 + { 547 + "type": "mdxJsxAttribute", 548 + "name": "class", 549 + "value": "test" 550 + }, 551 + { 552 + "type": "mdxJsxExpressionAttribute", 553 + "value": "...b", 554 + "stops": [ 555 + [ 556 + 0, 557 + 33 558 + ] 559 + ] 560 + } 561 + ] 562 + } 563 + ] 564 + } 565 + ] 566 + }"#, 567 + None, 568 + ) 569 + } 74 570 75 - ![Image](http://url/a.png) 571 + #[cfg_attr(feature = "serde", test)] 572 + fn serde_link() -> Result<(), Error> { 573 + assert_serde( 574 + "link [a](b)", 575 + r#"{ 576 + "type": "root", 577 + "children": [ 578 + { 579 + "type": "paragraph", 580 + "children": [ 581 + { 582 + "type": "text", 583 + "value": "link " 584 + }, 585 + { 586 + "type": "link", 587 + "children": [ 588 + { 589 + "type": "text", 590 + "value": "a" 591 + } 592 + ], 593 + "url": "b" 594 + } 595 + ] 596 + } 597 + ] 598 + }"#, 599 + None, 600 + ) 601 + } 76 602 77 - ## Table 603 + #[cfg_attr(feature = "serde", test)] 604 + fn serde_link_reference() -> Result<(), Error> { 605 + let source = r#"[x]: y 606 + a [x] b 607 + "#; 608 + assert_serde( 609 + source, 610 + r#"{ 611 + "type": "root", 612 + "children": [ 613 + { 614 + "type": "definition", 615 + "url": "y", 616 + "identifier": "x", 617 + "label": "x" 618 + }, 619 + { 620 + "type": "paragraph", 621 + "children": [ 622 + { 623 + "type": "text", 624 + "value": "a " 625 + }, 626 + { 627 + "type": "linkReference", 628 + "children": [ 629 + { 630 + "type": "text", 631 + "value": "x" 632 + } 633 + ], 634 + "referenceType": "shortcut", 635 + "identifier": "x", 636 + "label": "x" 637 + }, 638 + { 639 + "type": "text", 640 + "value": " b" 641 + } 642 + ] 643 + } 644 + ] 645 + }"#, 646 + None, 647 + ) 648 + } 78 649 79 - | Syntax | Description | 80 - |-----------|-------------| 81 - | Header | Title | 82 - | Paragraph | Text | 650 + #[cfg_attr(feature = "serde", test)] 651 + fn serde_strong() -> Result<(), Error> { 652 + assert_serde( 653 + "**a**", 654 + r#"{ 655 + "type": "root", 656 + "children": [ 657 + { 658 + "type": "paragraph", 659 + "children": [ 660 + { 661 + "type": "strong", 662 + "children": [ 663 + { 664 + "type": "text", 665 + "value": "a" 666 + } 667 + ] 668 + } 669 + ] 670 + } 671 + ] 672 + }"#, 673 + None, 674 + ) 675 + } 83 676 84 - ## Task lists 677 + #[cfg_attr(feature = "serde", test)] 678 + fn serde_text() -> Result<(), Error> { 679 + assert_serde( 680 + "a", 681 + r#"{ 682 + "type": "root", 683 + "children": [ 684 + { 685 + "type": "paragraph", 686 + "children": [ 687 + { 688 + "type": "text", 689 + "value": "a" 690 + } 691 + ] 692 + } 693 + ] 694 + }"#, 695 + None, 696 + ) 697 + } 85 698 86 - - [x] Write the press release 87 - - [ ] Update the website 88 - - [ ] Contact the media 699 + #[cfg_attr(feature = "serde", test)] 700 + fn serde_code() -> Result<(), Error> { 701 + let source = r#"~~~ 702 + let a = b; 703 + ~~~ 704 + "#; 705 + assert_serde( 706 + source, 707 + r#"{ 708 + "type": "root", 709 + "children": [ 710 + { 711 + "type": "code", 712 + "value": "let a = b;" 713 + } 714 + ] 715 + }"#, 716 + None, 717 + )?; 718 + let source = r#"``` 719 + let a = b; 720 + ``` 721 + "#; 722 + assert_serde( 723 + source, 724 + r#"{ 725 + "type": "root", 726 + "children": [ 727 + { 728 + "type": "code", 729 + "value": "let a = b;" 730 + } 731 + ] 732 + }"#, 733 + None, 734 + ) 735 + } 89 736 90 - ## Footnotes 737 + #[cfg_attr(feature = "serde", test)] 738 + fn serde_math() -> Result<(), Error> { 739 + let source = r#"$$ 740 + 1 + 1 = 2 741 + $$ 742 + "#; 743 + assert_serde( 744 + source, 745 + r#"{ 746 + "type": "root", 747 + "children": [ 748 + { 749 + "type": "math", 750 + "value": "1 + 1 = 2" 751 + } 752 + ] 753 + }"#, 754 + None, 755 + ) 756 + } 91 757 92 - Here's a simple footnote,[^1] and here's a longer one.[^bignote] 758 + #[cfg_attr(feature = "serde", test)] 759 + fn serde_mdx_flow_expression() -> Result<(), Error> { 760 + assert_serde( 761 + "{a}", 762 + r#"{ 763 + "type": "root", 764 + "children": [ 765 + { 766 + "type": "mdxFlowExpression", 767 + "value": "a", 768 + "stops": [ 769 + [ 770 + 0, 771 + 1 772 + ] 773 + ] 774 + } 775 + ] 776 + }"#, 777 + None, 778 + ) 779 + } 93 780 94 - [^1]: This is the first footnote. 781 + #[cfg_attr(feature = "serde", test)] 782 + fn serde_heading() -> Result<(), Error> { 783 + assert_serde( 784 + "# a", 785 + r#"{ 786 + "type": "root", 787 + "children": [ 788 + { 789 + "type": "heading", 790 + "children": [ 791 + { 792 + "type": "text", 793 + "value": "a" 794 + } 795 + ], 796 + "depth": 1 797 + } 798 + ] 799 + }"#, 800 + None, 801 + ) 802 + } 95 803 96 - [^bignote]: Here's one with multiple paragraphs and code. 804 + #[cfg_attr(feature = "serde", test)] 805 + fn serde_table() -> Result<(), Error> { 806 + let source = r#"| a | b | 807 + |-----|-----| 808 + | c11 | c12 | 809 + | c21 | c22 | 810 + "#; 811 + assert_serde( 812 + source, 813 + r#"{ 814 + "type": "root", 815 + "children": [ 816 + { 817 + "type": "table", 818 + "children": [ 819 + { 820 + "type": "tableRow", 821 + "children": [ 822 + { 823 + "type": "tableCell", 824 + "children": [ 825 + { 826 + "type": "text", 827 + "value": "a" 828 + } 829 + ] 830 + }, 831 + { 832 + "type": "tableCell", 833 + "children": [ 834 + { 835 + "type": "text", 836 + "value": "b" 837 + } 838 + ] 839 + } 840 + ] 841 + }, 842 + { 843 + "type": "tableRow", 844 + "children": [ 845 + { 846 + "type": "tableCell", 847 + "children": [ 848 + { 849 + "type": "text", 850 + "value": "c11" 851 + } 852 + ] 853 + }, 854 + { 855 + "type": "tableCell", 856 + "children": [ 857 + { 858 + "type": "text", 859 + "value": "c12" 860 + } 861 + ] 862 + } 863 + ] 864 + }, 865 + { 866 + "type": "tableRow", 867 + "children": [ 868 + { 869 + "type": "tableCell", 870 + "children": [ 871 + { 872 + "type": "text", 873 + "value": "c21" 874 + } 875 + ] 876 + }, 877 + { 878 + "type": "tableCell", 879 + "children": [ 880 + { 881 + "type": "text", 882 + "value": "c22" 883 + } 884 + ] 885 + } 886 + ] 887 + } 888 + ], 889 + "align": [ 890 + "none", 891 + "none" 892 + ] 893 + } 894 + ] 895 + }"#, 896 + None, 897 + ) 898 + } 97 899 98 - Indent paragraphs to include them in the footnote. 900 + #[cfg_attr(feature = "serde", test)] 901 + fn serde_thematic_break() -> Result<(), Error> { 902 + assert_serde( 903 + "***", 904 + r#"{ 905 + "type": "root", 906 + "children": [ 907 + { 908 + "type": "thematicBreak" 909 + } 910 + ] 911 + }"#, 912 + None, 913 + ) 914 + } 99 915 100 - `{ my code }` 916 + #[cfg_attr(feature = "serde", test)] 917 + fn serde_definition() -> Result<(), Error> { 918 + assert_serde( 919 + "[a]: # (b)", 920 + r###"{ 921 + "type": "root", 922 + "children": [ 923 + { 924 + "type": "definition", 925 + "url": "#", 926 + "title": "b", 927 + "identifier": "a", 928 + "label": "a" 929 + } 930 + ] 931 + }"###, 932 + None, 933 + ) 934 + } 101 935 102 - Add as many paragraphs as you like. 936 + #[cfg_attr(feature = "serde", test)] 937 + fn serde_paragraph() -> Result<(), Error> { 938 + assert_serde( 939 + "a", 940 + r#"{ 941 + "type": "root", 942 + "children": [ 943 + { 944 + "type": "paragraph", 945 + "children": [ 946 + { 947 + "type": "text", 948 + "value": "a" 949 + } 950 + ] 951 + } 952 + ] 953 + }"#, 954 + None, 955 + ) 956 + } 103 957 104 - "#, 105 - &markdown::ParseOptions { 958 + /// Assert serde of Mdast constructs. 959 + /// 960 + /// Refer below links for the MDAST JSON construct types. 961 + /// * https://github.com/syntax-tree/mdast#nodes 962 + /// * https://github.com/syntax-tree/mdast-util-mdx-jsx?tab=readme-ov-file#returns-1 963 + #[cfg(feature = "serde")] 964 + fn assert_serde( 965 + input: &str, 966 + expected: &str, 967 + options: Option<markdown::ParseOptions>, 968 + ) -> Result<(), Error> { 969 + // Parse Mdast with default options of MDX and GFM 970 + use test_utils::swc::{parse_esm, parse_expression}; 971 + let mut source = markdown::to_mdast( 972 + input, 973 + &options.unwrap_or(markdown::ParseOptions { 106 974 constructs: markdown::Constructs { 107 975 frontmatter: true, 976 + gfm_autolink_literal: true, 977 + gfm_footnote_definition: true, 978 + gfm_label_start_footnote: true, 979 + gfm_strikethrough: true, 980 + gfm_table: true, 981 + gfm_task_list_item: true, 982 + math_flow: true, 983 + math_text: true, 108 984 ..markdown::Constructs::mdx() 109 985 }, 986 + mdx_esm_parse: Some(Box::new(parse_esm)), 987 + mdx_expression_parse: Some(Box::new(parse_expression)), 110 988 ..markdown::ParseOptions::gfm() 111 - }, 989 + }), 112 990 ) 113 991 .map_err(Error::Mdast)?; 114 992 115 - let value: String = serde_json::to_string(&source).map_err(Error::Serde)?; 116 - 117 - let target: Node = serde_json::from_slice(value.as_bytes()).map_err(Error::Serde)?; 118 - 119 - pretty_assertions::assert_eq!(source, target); 120 - 993 + remove_position(&mut source); 994 + // Serialize to JSON 995 + let actual_value: serde_json::Value = serde_json::to_value(&source).map_err(Error::Serde)?; 996 + let expected_value: serde_json::Value = serde_json::from_str(expected).map_err(Error::Serde)?; 997 + // Assert serialization 998 + pretty_assertions::assert_eq!(actual_value, expected_value); 999 + // Assert deserialization 1000 + pretty_assertions::assert_eq!( 1001 + source, 1002 + serde_json::from_value(actual_value).map_err(Error::Serde)? 1003 + ); 121 1004 Ok(()) 122 1005 } 1006 + 1007 + fn remove_position(node: &mut Node) { 1008 + if let Some(children) = node.children_mut() { 1009 + for child in children { 1010 + remove_position(child); 1011 + } 1012 + } 1013 + node.position_set(None); 1014 + }