1use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, string::String, vec::Vec};
24use core::{
25 cmp::{max, min},
26 iter::FusedIterator,
27 num::NonZeroUsize,
28 ops::{Index, Range},
29};
30use rustc_hash::FxHashMap;
31use unicase::UniCase;
32
33use crate::{
34 firstpass::run_first_pass,
35 linklabel::{scan_link_label_rest, FootnoteLabel, LinkLabel, ReferenceLabel},
36 mdx::*,
37 scanners::*,
38 strings::CowStr,
39 tree::{Tree, TreeIndex},
40 Alignment, BlockQuoteKind, CodeBlockKind, ContainerKind, Event, HeadingLevel, LinkType,
41 MetadataBlockKind, Options, Tag, TagEnd,
42};
43
44pub(crate) const LINK_MAX_NESTED_PARENS: usize = 32;
50
51#[derive(Debug, Default, Clone, Copy)]
52pub(crate) struct Item {
53 pub start: usize,
54 pub end: usize,
55 pub body: ItemBody,
56}
57
58#[derive(Debug, PartialEq, Clone, Copy, Default)]
59pub(crate) enum ItemBody {
60 MaybeEmphasis(usize, bool, bool),
64 MaybeMath(bool, bool, u8),
66 MaybeSmartQuote(u8, bool, bool),
68 MaybeCode(usize, bool), MaybeHtml,
70 MaybeLinkOpen,
71 MaybeLinkClose(bool),
73 MaybeImage,
74
75 Emphasis,
77 Strong,
78 Strikethrough,
79 Superscript,
80 Subscript,
81 Math(CowIndex, bool), Code(CowIndex),
83 Link(LinkIndex),
84 Image(LinkIndex),
85 FootnoteReference(CowIndex),
86 TaskListMarker(bool), InlineHtml,
90 OwnedInlineHtml(CowIndex),
91 SynthesizeText(CowIndex),
92 SynthesizeChar(char),
93 Html,
94 Text {
95 backslash_escaped: bool,
96 },
97 SoftBreak,
98 HardBreak(bool),
100
101 #[default]
103 Root,
104
105 Paragraph,
107 TightParagraph,
108 Rule,
109 Heading(HeadingLevel, Option<HeadingIndex>), FencedCodeBlock(CowIndex),
111 IndentCodeBlock,
112 HtmlBlock,
113 BlockQuote(Option<BlockQuoteKind>),
114 Container(u8, ContainerKind, CowIndex), List(bool, u8, u64), ListItem(usize), FootnoteDefinition(CowIndex),
118 MetadataBlock(MetadataBlockKind),
119
120 DefinitionList(bool), MaybeDefinitionListTitle,
125 DefinitionListTitle,
126 DefinitionListDefinition(usize),
127
128 Table(AlignmentIndex),
130 TableHead,
131 TableRow,
132 TableCell,
133
134 MdxJsxFlowElement(JsxElementIndex),
136 MdxJsxTextElement(JsxElementIndex),
137 MdxFlowExpression(CowIndex),
138 MdxTextExpression(CowIndex),
139 MdxEsm(CowIndex),
140}
141
142impl ItemBody {
143 pub(crate) fn is_maybe_inline(&self) -> bool {
144 use ItemBody::*;
145 matches!(
146 *self,
147 MaybeEmphasis(..)
148 | MaybeMath(..)
149 | MaybeSmartQuote(..)
150 | MaybeCode(..)
151 | MaybeHtml
152 | MaybeLinkOpen
153 | MaybeLinkClose(..)
154 | MaybeImage
155 )
156 }
157 fn is_inline(&self) -> bool {
158 use ItemBody::*;
159 matches!(
160 *self,
161 MaybeEmphasis(..)
162 | MaybeMath(..)
163 | MaybeSmartQuote(..)
164 | MaybeCode(..)
165 | MaybeHtml
166 | MaybeLinkOpen
167 | MaybeLinkClose(..)
168 | MaybeImage
169 | Emphasis
170 | Strong
171 | Strikethrough
172 | Math(..)
173 | Code(..)
174 | Link(..)
175 | Image(..)
176 | FootnoteReference(..)
177 | TaskListMarker(..)
178 | InlineHtml
179 | OwnedInlineHtml(..)
180 | SynthesizeText(..)
181 | SynthesizeChar(..)
182 | Html
183 | Text { .. }
184 | SoftBreak
185 | HardBreak(..)
186 )
187 }
188}
189
190#[derive(Debug)]
191pub struct BrokenLink<'a> {
192 pub span: core::ops::Range<usize>,
193 pub link_type: LinkType,
194 pub reference: CowStr<'a>,
195}
196
197pub struct Parser<'input, CB = DefaultParserCallbacks> {
199 callbacks: CB,
200 inner: ParserInner<'input>,
201}
202
203pub(crate) struct ParserInner<'input> {
206 pub(crate) text: &'input str,
207 pub(crate) options: Options,
208 pub(crate) tree: Tree<Item>,
209 pub(crate) allocs: Allocations<'input>,
210 html_scan_guard: HtmlScanGuard,
211
212 link_ref_expansion_limit: usize,
229
230 pub(crate) mdx_errors: Vec<(usize, String)>,
232
233 inline_stack: InlineStack,
235 link_stack: LinkStack,
236 wikilink_stack: LinkStack,
237 code_delims: CodeDelims,
238 math_delims: MathDelims,
239}
240
241impl<'input, CB> core::fmt::Debug for Parser<'input, CB> {
242 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243 f.debug_struct("Parser")
245 .field("text", &self.inner.text)
246 .field("options", &self.inner.options)
247 .field("callbacks", &..)
248 .finish()
249 }
250}
251
252impl<'a> BrokenLink<'a> {
253 pub fn into_static(self) -> BrokenLink<'static> {
257 BrokenLink {
258 span: self.span.clone(),
259 link_type: self.link_type,
260 reference: self.reference.into_string().into(),
261 }
262 }
263}
264
265impl<'input> Parser<'input, DefaultParserCallbacks> {
266 pub fn new(text: &'input str) -> Self {
268 Self::new_ext(text, Options::empty())
269 }
270
271 pub fn new_ext(text: &'input str, options: Options) -> Self {
273 Self::new_with_callbacks(text, options, DefaultParserCallbacks)
274 }
275}
276
277impl<'input, CB: ParserCallbacks<'input>> Parser<'input, CB> {
278 pub fn new_with_callbacks(text: &'input str, options: Options, callbacks: CB) -> Self {
303 let (mut tree, allocs, _firstpass_mdx_errors) = run_first_pass(text, options);
304 tree.reset();
305 let inline_stack = Default::default();
306 let link_stack = Default::default();
307 let wikilink_stack = Default::default();
308 let html_scan_guard = Default::default();
309 Parser {
310 callbacks,
311
312 inner: ParserInner {
313 text,
314 options,
315 tree,
316 allocs,
317 inline_stack,
318 link_stack,
319 wikilink_stack,
320 html_scan_guard,
321 link_ref_expansion_limit: text.len().max(100_000),
323 mdx_errors: Vec::new(),
324 code_delims: CodeDelims::new(),
325 math_delims: MathDelims::new(),
326 },
327 }
328 }
329
330 pub fn reference_definitions(&self) -> &RefDefs<'_> {
333 &self.inner.allocs.refdefs
334 }
335
336 pub fn mdx_errors(&self) -> &[(usize, String)] {
339 &self.inner.mdx_errors
340 }
341
342 pub fn into_offset_iter(self) -> OffsetIter<'input, CB> {
346 OffsetIter { parser: self }
347 }
348}
349
350impl<'input, F> Parser<'input, BrokenLinkCallback<F>> {
351 pub fn new_with_broken_link_callback(
360 text: &'input str,
361 options: Options,
362 broken_link_callback: Option<F>,
363 ) -> Self
364 where
365 F: FnMut(BrokenLink<'input>) -> Option<(CowStr<'input>, CowStr<'input>)>,
366 {
367 Self::new_with_callbacks(text, options, BrokenLinkCallback(broken_link_callback))
368 }
369}
370
371impl<'input> ParserInner<'input> {
372 pub(crate) fn new(text: &'input str, options: Options) -> Self {
373 let (mut tree, allocs, firstpass_mdx_errors) = run_first_pass(text, options);
374 tree.reset();
375 ParserInner {
376 text,
377 options,
378 tree,
379 allocs,
380 inline_stack: Default::default(),
381 link_stack: Default::default(),
382 wikilink_stack: Default::default(),
383 html_scan_guard: Default::default(),
384 link_ref_expansion_limit: text.len().max(100_000),
385 mdx_errors: firstpass_mdx_errors,
386 code_delims: CodeDelims::new(),
387 math_delims: MathDelims::new(),
388 }
389 }
390
391 fn fetch_link_type_url_title(
410 &mut self,
411 link_label: CowStr<'input>,
412 span: Range<usize>,
413 link_type: LinkType,
414 callbacks: &mut dyn ParserCallbacks<'input>,
415 ) -> Option<(LinkType, CowStr<'input>, CowStr<'input>)> {
416 if self.link_ref_expansion_limit == 0 {
417 return None;
418 }
419
420 let (link_type, url, title) = self
421 .allocs
422 .refdefs
423 .get(link_label.as_ref())
424 .map(|matching_def| {
425 let title = matching_def
427 .title
428 .as_ref()
429 .cloned()
430 .unwrap_or_else(|| "".into());
431 let url = matching_def.dest.clone();
432 (link_type, url, title)
433 })
434 .or_else(|| {
435 let broken_link = BrokenLink {
437 span,
438 link_type,
439 reference: link_label,
440 };
441
442 callbacks
443 .handle_broken_link(broken_link)
444 .map(|(url, title)| (link_type.to_unknown(), url, title))
445 })?;
446
447 self.link_ref_expansion_limit = self
451 .link_ref_expansion_limit
452 .saturating_sub(url.len() + title.len());
453
454 Some((link_type, url, title))
455 }
456
457 pub(crate) fn handle_inline(&mut self, callbacks: &mut dyn ParserCallbacks<'input>) {
464 self.handle_inline_pass1(callbacks);
465 self.handle_emphasis_and_hard_break();
466 }
467
468 fn handle_inline_pass1(&mut self, callbacks: &mut dyn ParserCallbacks<'input>) {
474 let mut cur = self.tree.cur();
475 let mut prev = None;
476
477 let block_end = self.tree[self.tree.peek_up().unwrap()].item.end;
478 let block_text = &self.text[..block_end];
479
480 while let Some(mut cur_ix) = cur {
481 match self.tree[cur_ix].item.body {
482 ItemBody::MaybeHtml => {
483 if self.options.contains(Options::ENABLE_MDX) {
485 let start = self.tree[cur_ix].item.start;
486 let next_byte = block_text.as_bytes().get(start + 1).copied();
487
488 if next_byte == Some(b'!') {
490 self.mdx_errors.push((
491 start,
492 "Unexpected character `!` (U+0021) before name, expected a \
493 character that can start a name, such as a letter, `$`, or `_` \
494 (note: to create a comment in MDX, use `{/* text */}`)"
495 .to_string(),
496 ));
497 self.tree[cur_ix].item.body = ItemBody::Text {
498 backslash_escaped: false,
499 };
500 prev = cur;
501 cur = self.tree[cur_ix].next;
502 continue;
503 }
504
505 if let Some(total_len) =
506 scan_mdx_inline_jsx(&block_text.as_bytes()[start..])
507 {
508 let end = start + total_len;
509 let node = scan_nodes_to_ix(&self.tree, self.tree[cur_ix].next, end);
510 let raw = &block_text[start..end];
511 let jsx_data = crate::mdx::parse_jsx_tag(raw);
512 let jsx_ix = self.allocs.allocate_jsx_element(jsx_data);
513 self.tree[cur_ix].item.body = ItemBody::MdxJsxTextElement(jsx_ix);
514 self.tree[cur_ix].item.end = end;
515 self.tree[cur_ix].next = node;
516 prev = cur;
517 cur = node;
518 if let Some(node_ix) = cur {
519 self.tree[node_ix].item.start =
520 max(self.tree[node_ix].item.start, end);
521 }
522 continue;
523 }
524
525 if matches!(next_byte, Some(b'a'..=b'z' | b'A'..=b'Z' | b'/' | b'>')) {
528 self.mdx_errors.push((
529 start,
530 "Unexpected character after `<`, expected a valid JSX tag \
531 (note: to create a link in MDX, use `[text](url)`)"
532 .to_string(),
533 ));
534 }
535
536 self.tree[cur_ix].item.body = ItemBody::Text {
537 backslash_escaped: false,
538 };
539 prev = cur;
540 cur = self.tree[cur_ix].next;
541 continue;
542 }
543
544 let next = self.tree[cur_ix].next;
545 let autolink = if let Some(next_ix) = next {
546 scan_autolink(block_text, self.tree[next_ix].item.start)
547 } else {
548 None
549 };
550
551 if let Some((ix, uri, link_type)) = autolink {
552 let node = scan_nodes_to_ix(&self.tree, next, ix);
553 let text_node = self.tree.create_node(Item {
554 start: self.tree[cur_ix].item.start + 1,
555 end: ix - 1,
556 body: ItemBody::Text {
557 backslash_escaped: false,
558 },
559 });
560 let link_ix =
561 self.allocs
562 .allocate_link(link_type, uri, "".into(), "".into());
563 self.tree[cur_ix].item.body = ItemBody::Link(link_ix);
564 self.tree[cur_ix].item.end = ix;
565 self.tree[cur_ix].next = node;
566 self.tree[cur_ix].child = Some(text_node);
567 prev = cur;
568 cur = node;
569 if let Some(node_ix) = cur {
570 self.tree[node_ix].item.start = max(self.tree[node_ix].item.start, ix);
571 }
572 continue;
573 } else {
574 let inline_html = next.and_then(|next_ix| {
575 self.scan_inline_html(
576 block_text.as_bytes(),
577 self.tree[next_ix].item.start,
578 )
579 });
580 if let Some((span, ix)) = inline_html {
581 let node = scan_nodes_to_ix(&self.tree, next, ix);
582 self.tree[cur_ix].item.body = if !span.is_empty() {
583 let converted_string =
584 String::from_utf8(span).expect("invalid utf8");
585 ItemBody::OwnedInlineHtml(
586 self.allocs.allocate_cow(converted_string.into()),
587 )
588 } else {
589 ItemBody::InlineHtml
590 };
591 self.tree[cur_ix].item.end = ix;
592 self.tree[cur_ix].next = node;
593 prev = cur;
594 cur = node;
595 if let Some(node_ix) = cur {
596 self.tree[node_ix].item.start =
597 max(self.tree[node_ix].item.start, ix);
598 }
599 continue;
600 }
601 }
602 self.tree[cur_ix].item.body = ItemBody::Text {
603 backslash_escaped: false,
604 };
605 }
606 ItemBody::MaybeMath(can_open, _can_close, brace_context) => {
607 if !can_open {
608 self.tree[cur_ix].item.body = ItemBody::Text {
609 backslash_escaped: false,
610 };
611 prev = cur;
612 cur = self.tree[cur_ix].next;
613 continue;
614 }
615 let is_display = self.tree[cur_ix].next.is_some_and(|next_ix| {
616 matches!(
617 self.tree[next_ix].item.body,
618 ItemBody::MaybeMath(_can_open, _can_close, _brace_context)
619 )
620 });
621 let result = if self.math_delims.is_populated() {
622 self.math_delims
625 .find(&self.tree, cur_ix, is_display, brace_context)
626 } else {
627 let mut scan = self.tree[cur_ix].next;
630 if is_display {
631 scan = self.tree[scan.unwrap()].next;
634 }
635 let mut invalid = false;
636 while let Some(scan_ix) = scan {
637 if let ItemBody::MaybeMath(_can_open, can_close, delim_brace_context) =
638 self.tree[scan_ix].item.body
639 {
640 let delim_is_display =
641 self.tree[scan_ix].next.is_some_and(|next_ix| {
642 matches!(
643 self.tree[next_ix].item.body,
644 ItemBody::MaybeMath(
645 _can_open,
646 _can_close,
647 _brace_context
648 )
649 )
650 });
651 if !invalid && delim_brace_context == brace_context {
652 if (!is_display && can_close)
653 || (is_display && delim_is_display)
654 {
655 self.math_delims.clear();
659 break;
660 } else {
661 invalid = true;
664 }
665 }
666 self.math_delims.insert(
667 delim_is_display,
668 delim_brace_context,
669 scan_ix,
670 can_close,
671 );
672 }
673 scan = self.tree[scan_ix].next;
674 }
675 scan
676 };
677
678 if let Some(scan_ix) = result {
679 self.make_math_span(cur_ix, scan_ix);
680 } else {
681 self.tree[cur_ix].item.body = ItemBody::Text {
682 backslash_escaped: false,
683 };
684 }
685 }
686 ItemBody::MaybeCode(mut search_count, preceded_by_backslash) => {
687 if preceded_by_backslash {
688 search_count -= 1;
689 if search_count == 0 {
690 self.tree[cur_ix].item.body = ItemBody::Text {
691 backslash_escaped: false,
692 };
693 prev = cur;
694 cur = self.tree[cur_ix].next;
695 continue;
696 }
697 }
698
699 if self.code_delims.is_populated() {
700 if let Some(scan_ix) = self.code_delims.find(cur_ix, search_count) {
703 self.make_code_span(cur_ix, scan_ix, preceded_by_backslash);
704 } else {
705 self.tree[cur_ix].item.body = ItemBody::Text {
706 backslash_escaped: false,
707 };
708 }
709 } else {
710 let mut scan = if search_count > 0 {
713 self.tree[cur_ix].next
714 } else {
715 None
716 };
717 while let Some(scan_ix) = scan {
718 if let ItemBody::MaybeCode(delim_count, _) =
719 self.tree[scan_ix].item.body
720 {
721 if search_count == delim_count {
722 self.make_code_span(cur_ix, scan_ix, preceded_by_backslash);
723 self.code_delims.clear();
724 break;
725 } else {
726 self.code_delims.insert(delim_count, scan_ix);
727 }
728 }
729 scan = self.tree[scan_ix].next;
730 }
731 if scan.is_none() {
732 self.tree[cur_ix].item.body = ItemBody::Text {
733 backslash_escaped: false,
734 };
735 }
736 }
737 }
738 ItemBody::MaybeLinkOpen => {
739 self.tree[cur_ix].item.body = ItemBody::Text {
740 backslash_escaped: false,
741 };
742 let link_open_doubled = self.tree[cur_ix]
743 .next
744 .map(|ix| self.tree[ix].item.body == ItemBody::MaybeLinkOpen)
745 .unwrap_or(false);
746 if self.options.contains(Options::ENABLE_WIKILINKS) && link_open_doubled {
747 self.wikilink_stack.push(LinkStackEl {
748 node: cur_ix,
749 ty: LinkStackTy::Link,
750 });
751 }
752 self.link_stack.push(LinkStackEl {
753 node: cur_ix,
754 ty: LinkStackTy::Link,
755 });
756 }
757 ItemBody::MaybeImage => {
758 self.tree[cur_ix].item.body = ItemBody::Text {
759 backslash_escaped: false,
760 };
761 let link_open_doubled = self.tree[cur_ix]
762 .next
763 .map(|ix| self.tree[ix].item.body == ItemBody::MaybeLinkOpen)
764 .unwrap_or(false);
765 if self.options.contains(Options::ENABLE_WIKILINKS) && link_open_doubled {
766 self.wikilink_stack.push(LinkStackEl {
767 node: cur_ix,
768 ty: LinkStackTy::Image,
769 });
770 }
771 self.link_stack.push(LinkStackEl {
772 node: cur_ix,
773 ty: LinkStackTy::Image,
774 });
775 }
776 ItemBody::MaybeLinkClose(could_be_ref) => {
777 self.tree[cur_ix].item.body = ItemBody::Text {
778 backslash_escaped: false,
779 };
780 let tos_link = self.link_stack.pop();
781 if self.options.contains(Options::ENABLE_WIKILINKS)
782 && self.tree[cur_ix]
783 .next
784 .map(|ix| {
785 matches!(self.tree[ix].item.body, ItemBody::MaybeLinkClose(..))
786 })
787 .unwrap_or(false)
788 {
789 if let Some(node) = self.handle_wikilink(block_text, cur_ix, prev) {
790 cur = self.tree[node].next;
791 continue;
792 }
793 }
794 if let Some(tos) = tos_link {
795 if tos.ty != LinkStackTy::Image
798 && matches!(
799 self.tree[self.tree.peek_up().unwrap()].item.body,
800 ItemBody::Link(..)
801 )
802 {
803 continue;
804 }
805 if tos.ty == LinkStackTy::Disabled {
806 continue;
807 }
808 let next = self.tree[cur_ix].next;
809 if let Some((next_ix, url, title)) =
810 self.scan_inline_link(block_text, self.tree[cur_ix].item.end, next)
811 {
812 let next_node = scan_nodes_to_ix(&self.tree, next, next_ix);
813 if let Some(prev_ix) = prev {
814 self.tree[prev_ix].next = None;
815 }
816 cur = Some(tos.node);
817 cur_ix = tos.node;
818 let link_ix =
819 self.allocs
820 .allocate_link(LinkType::Inline, url, title, "".into());
821 self.tree[cur_ix].item.body = if tos.ty == LinkStackTy::Image {
822 ItemBody::Image(link_ix)
823 } else {
824 ItemBody::Link(link_ix)
825 };
826 self.tree[cur_ix].child = self.tree[cur_ix].next;
827 self.tree[cur_ix].next = next_node;
828 self.tree[cur_ix].item.end = next_ix;
829 if let Some(next_node_ix) = next_node {
830 self.tree[next_node_ix].item.start =
831 max(self.tree[next_node_ix].item.start, next_ix);
832 }
833
834 if tos.ty == LinkStackTy::Link {
835 self.disable_all_links();
836 }
837 } else {
838 let scan_result =
841 scan_reference(&self.tree, block_text, next, self.options);
842 let (node_after_link, link_type) = match scan_result {
843 RefScan::LinkLabel(_, end_ix) => {
845 let reference_close_node = if let Some(node) =
850 scan_nodes_to_ix(&self.tree, next, end_ix - 1)
851 {
852 node
853 } else {
854 continue;
855 };
856 self.tree[reference_close_node].item.body =
857 ItemBody::MaybeLinkClose(false);
858 let next_node = self.tree[reference_close_node].next;
859
860 (next_node, LinkType::Reference)
861 }
862 RefScan::Collapsed(next_node) => {
864 if !could_be_ref {
867 continue;
868 }
869 (next_node, LinkType::Collapsed)
870 }
871 RefScan::Failed | RefScan::UnexpectedFootnote => {
875 if !could_be_ref {
876 continue;
877 }
878 (next, LinkType::Shortcut)
879 }
880 };
881
882 let label: Option<(ReferenceLabel<'input>, usize)> = match scan_result {
887 RefScan::LinkLabel(l, end_ix) => {
888 Some((ReferenceLabel::Link(l), end_ix))
889 }
890 RefScan::Collapsed(..)
891 | RefScan::Failed
892 | RefScan::UnexpectedFootnote => {
893 let label_start = self.tree[tos.node].item.end - 1;
895 let label_end = self.tree[cur_ix].item.end;
896 scan_link_label(
897 &self.tree,
898 &self.text[label_start..label_end],
899 self.options,
900 )
901 .map(|(ix, label)| (label, label_start + ix))
902 .filter(|(_, end)| *end == label_end)
903 }
904 };
905
906 let id = match &label {
907 Some(
908 (ReferenceLabel::Link(l), _) | (ReferenceLabel::Footnote(l), _),
909 ) => l.clone(),
910 None => "".into(),
911 };
912
913 if let Some((ReferenceLabel::Footnote(l), end)) = label {
915 let footref = self.allocs.allocate_cow(l);
916 if let Some(def) = self
917 .allocs
918 .footdefs
919 .get_mut(self.allocs.cows[footref.0].to_owned())
920 {
921 def.use_count += 1;
922 }
923 if !self.options.has_gfm_footnotes()
924 || self.allocs.footdefs.contains(&self.allocs.cows[footref.0])
925 {
926 let footnote_ix = if tos.ty == LinkStackTy::Image {
929 self.tree[tos.node].next = Some(cur_ix);
930 self.tree[tos.node].child = None;
931 self.tree[tos.node].item.body =
932 ItemBody::SynthesizeChar('!');
933 self.tree[cur_ix].item.start =
934 self.tree[tos.node].item.start + 1;
935 self.tree[tos.node].item.end =
936 self.tree[tos.node].item.start + 1;
937 cur_ix
938 } else {
939 tos.node
940 };
941 self.tree[footnote_ix].next = next;
945 self.tree[footnote_ix].child = None;
946 self.tree[footnote_ix].item.body =
947 ItemBody::FootnoteReference(footref);
948 self.tree[footnote_ix].item.end = end;
949 prev = Some(footnote_ix);
950 cur = next;
951 self.link_stack.clear();
952 continue;
953 }
954 } else if let Some((ReferenceLabel::Link(link_label), end)) = label {
955 if let Some((def_link_type, url, title)) = self
956 .fetch_link_type_url_title(
957 link_label,
958 (self.tree[tos.node].item.start)..end,
959 link_type,
960 callbacks,
961 )
962 {
963 let link_ix =
964 self.allocs.allocate_link(def_link_type, url, title, id);
965 self.tree[tos.node].item.body = if tos.ty == LinkStackTy::Image
966 {
967 ItemBody::Image(link_ix)
968 } else {
969 ItemBody::Link(link_ix)
970 };
971 let label_node = self.tree[tos.node].next;
972
973 self.tree[tos.node].next = node_after_link;
976
977 if label_node != cur {
979 self.tree[tos.node].child = label_node;
980
981 if let Some(prev_ix) = prev {
983 self.tree[prev_ix].next = None;
984 }
985 }
986
987 self.tree[tos.node].item.end = end;
988
989 cur = Some(tos.node);
991 cur_ix = tos.node;
992
993 if tos.ty == LinkStackTy::Link {
994 self.disable_all_links();
995 }
996 }
997 }
998 }
999 }
1000 }
1001 _ => {}
1002 }
1003 prev = cur;
1004 cur = self.tree[cur_ix].next;
1005 }
1006 self.link_stack.clear();
1007 self.wikilink_stack.clear();
1008 self.code_delims.clear();
1009 self.math_delims.clear();
1010 }
1011
1012 fn handle_wikilink(
1018 &mut self,
1019 block_text: &'input str,
1020 cur_ix: TreeIndex,
1021 prev: Option<TreeIndex>,
1022 ) -> Option<TreeIndex> {
1023 let next_ix = self.tree[cur_ix].next.unwrap();
1024 if let Some(tos) = self.wikilink_stack.pop() {
1027 if tos.ty == LinkStackTy::Disabled {
1028 return None;
1029 }
1030 let Some(body_node) = self.tree[tos.node].next.and_then(|ix| self.tree[ix].next) else {
1032 return None;
1034 };
1035 let start_ix = self.tree[body_node].item.start;
1036 let end_ix = self.tree[cur_ix].item.start;
1037 let wikilink = match scan_wikilink_pipe(
1038 block_text,
1039 start_ix, end_ix - start_ix,
1041 ) {
1042 Some((rest, wikitext)) => {
1043 if wikitext.is_empty() {
1045 return None;
1046 }
1047 let body_node = scan_nodes_to_ix(&self.tree, Some(body_node), rest);
1049 if let Some(body_node) = body_node {
1050 self.tree[body_node].item.start = rest;
1053 Some((true, body_node, wikitext))
1054 } else {
1055 None
1056 }
1057 }
1058 None => {
1059 let wikitext = &block_text[start_ix..end_ix];
1060 if wikitext.is_empty() {
1062 return None;
1063 }
1064 let body_node = self.tree.create_node(Item {
1065 start: start_ix,
1066 end: end_ix,
1067 body: ItemBody::Text {
1068 backslash_escaped: false,
1069 },
1070 });
1071 Some((false, body_node, wikitext))
1072 }
1073 };
1074
1075 if let Some((has_pothole, body_node, wikiname)) = wikilink {
1076 let link_ix = self.allocs.allocate_link(
1077 LinkType::WikiLink { has_pothole },
1078 wikiname.into(),
1079 "".into(),
1080 "".into(),
1081 );
1082 if let Some(prev_ix) = prev {
1083 self.tree[prev_ix].next = None;
1084 }
1085 if tos.ty == LinkStackTy::Image {
1086 self.tree[tos.node].item.body = ItemBody::Image(link_ix);
1087 } else {
1088 self.tree[tos.node].item.body = ItemBody::Link(link_ix);
1089 }
1090 self.tree[tos.node].child = Some(body_node);
1091 self.tree[tos.node].next = self.tree[next_ix].next;
1092 self.tree[tos.node].item.end = end_ix + 2;
1093 self.disable_all_links();
1094 return Some(tos.node);
1095 }
1096 }
1097
1098 None
1099 }
1100
1101 fn handle_emphasis_and_hard_break(&mut self) {
1102 let mut prev = None;
1103 let mut prev_ix: TreeIndex;
1104 let mut cur = self.tree.cur();
1105
1106 let mut single_quote_open: Option<TreeIndex> = None;
1107 let mut double_quote_open: bool = false;
1108
1109 while let Some(mut cur_ix) = cur {
1110 match self.tree[cur_ix].item.body {
1111 ItemBody::MaybeEmphasis(mut count, can_open, can_close) => {
1112 let run_length = count;
1113 let c = self.text.as_bytes()[self.tree[cur_ix].item.start];
1114 let both = can_open && can_close;
1115 if can_close {
1116 while let Some(el) =
1117 self.inline_stack
1118 .find_match(&mut self.tree, c, run_length, both)
1119 {
1120 if let Some(prev_ix) = prev {
1122 self.tree[prev_ix].next = None;
1123 }
1124 let match_count = min(count, el.count);
1125 let mut end = cur_ix - 1;
1127 let mut start = el.start + el.count;
1128
1129 while start > el.start + el.count - match_count {
1131 let inc = if start > el.start + el.count - match_count + 1 {
1132 2
1133 } else {
1134 1
1135 };
1136 let ty = if c == b'~' {
1137 if inc == 2 {
1138 if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1139 ItemBody::Strikethrough
1140 } else {
1141 ItemBody::Text {
1142 backslash_escaped: false,
1143 }
1144 }
1145 } else if self.options.contains(Options::ENABLE_SUBSCRIPT) {
1146 ItemBody::Subscript
1147 } else if self.options.contains(Options::ENABLE_STRIKETHROUGH) {
1148 ItemBody::Strikethrough
1149 } else {
1150 ItemBody::Text {
1151 backslash_escaped: false,
1152 }
1153 }
1154 } else if c == b'^' {
1155 if self.options.contains(Options::ENABLE_SUPERSCRIPT) {
1156 ItemBody::Superscript
1157 } else {
1158 ItemBody::Text {
1159 backslash_escaped: false,
1160 }
1161 }
1162 } else if inc == 2 {
1163 ItemBody::Strong
1164 } else {
1165 ItemBody::Emphasis
1166 };
1167
1168 let root = start - inc;
1169 end = end + inc;
1170 self.tree[root].item.body = ty;
1171 self.tree[root].item.end = self.tree[end].item.end;
1172 self.tree[root].child = Some(start);
1173 self.tree[root].next = None;
1174 start = root;
1175 }
1176
1177 prev_ix = el.start + el.count - match_count;
1179 prev = Some(prev_ix);
1180 cur = self.tree[cur_ix + match_count - 1].next;
1181 self.tree[prev_ix].next = cur;
1182
1183 if el.count > match_count {
1184 self.inline_stack.push(InlineEl {
1185 start: el.start,
1186 count: el.count - match_count,
1187 run_length: el.run_length,
1188 c: el.c,
1189 both: el.both,
1190 })
1191 }
1192 count -= match_count;
1193 if count > 0 {
1194 cur_ix = cur.unwrap();
1195 } else {
1196 break;
1197 }
1198 }
1199 }
1200 if count > 0 {
1201 if can_open {
1202 self.inline_stack.push(InlineEl {
1203 start: cur_ix,
1204 run_length,
1205 count,
1206 c,
1207 both,
1208 });
1209 } else {
1210 for i in 0..count {
1211 self.tree[cur_ix + i].item.body = ItemBody::Text {
1212 backslash_escaped: false,
1213 };
1214 }
1215 }
1216 prev_ix = cur_ix + count - 1;
1217 prev = Some(prev_ix);
1218 cur = self.tree[prev_ix].next;
1219 }
1220 }
1221 ItemBody::MaybeSmartQuote(c, can_open, can_close) => {
1222 self.tree[cur_ix].item.body = match c {
1223 b'\'' => {
1224 if let (Some(open_ix), true) = (single_quote_open, can_close) {
1225 self.tree[open_ix].item.body = ItemBody::SynthesizeChar('‘');
1226 single_quote_open = None;
1227 } else if can_open {
1228 single_quote_open = Some(cur_ix);
1229 }
1230 ItemBody::SynthesizeChar('’')
1231 }
1232 _ => {
1233 if can_close && double_quote_open {
1234 double_quote_open = false;
1235 ItemBody::SynthesizeChar('”')
1236 } else {
1237 if can_open && !double_quote_open {
1238 double_quote_open = true;
1239 }
1240 ItemBody::SynthesizeChar('“')
1241 }
1242 }
1243 };
1244 prev = cur;
1245 cur = self.tree[cur_ix].next;
1246 }
1247 ItemBody::HardBreak(true) => {
1248 if self.tree[cur_ix].next.is_none() {
1249 self.tree[cur_ix].item.body = ItemBody::SynthesizeChar('\\');
1250 }
1251 prev = cur;
1252 cur = self.tree[cur_ix].next;
1253 }
1254 _ => {
1255 prev = cur;
1256 cur = self.tree[cur_ix].next;
1257 }
1258 }
1259 }
1260 self.inline_stack.pop_all(&mut self.tree);
1261 }
1262
1263 fn disable_all_links(&mut self) {
1264 self.link_stack.disable_all_links();
1265 self.wikilink_stack.disable_all_links();
1266 }
1267
1268 fn scan_inline_link(
1270 &self,
1271 underlying: &'input str,
1272 mut ix: usize,
1273 node: Option<TreeIndex>,
1274 ) -> Option<(usize, CowStr<'input>, CowStr<'input>)> {
1275 if underlying.as_bytes().get(ix) != Some(&b'(') {
1276 return None;
1277 }
1278 ix += 1;
1279
1280 let scan_separator = |ix: &mut usize| {
1281 *ix += scan_while(&underlying.as_bytes()[*ix..], is_ascii_whitespace_no_nl);
1282 if let Some(bl) = scan_eol(&underlying.as_bytes()[*ix..]) {
1283 *ix += bl;
1284 *ix += skip_container_prefixes(
1285 &self.tree,
1286 &underlying.as_bytes()[*ix..],
1287 self.options,
1288 );
1289 }
1290 *ix += scan_while(&underlying.as_bytes()[*ix..], is_ascii_whitespace_no_nl);
1291 };
1292
1293 scan_separator(&mut ix);
1294
1295 let (dest_length, dest) = scan_link_dest(underlying, ix, LINK_MAX_NESTED_PARENS)?;
1296 let dest = unescape(dest, self.tree.is_in_table());
1297 ix += dest_length;
1298
1299 scan_separator(&mut ix);
1300
1301 let title = if let Some((bytes_scanned, t)) = self.scan_link_title(underlying, ix, node) {
1302 ix += bytes_scanned;
1303 scan_separator(&mut ix);
1304 t
1305 } else {
1306 "".into()
1307 };
1308 if underlying.as_bytes().get(ix) != Some(&b')') {
1309 return None;
1310 }
1311 ix += 1;
1312
1313 Some((ix, dest, title))
1314 }
1315
1316 fn scan_link_title(
1318 &self,
1319 text: &'input str,
1320 start_ix: usize,
1321 node: Option<TreeIndex>,
1322 ) -> Option<(usize, CowStr<'input>)> {
1323 let bytes = text.as_bytes();
1324 let open = match bytes.get(start_ix) {
1325 Some(b @ b'\'') | Some(b @ b'\"') | Some(b @ b'(') => *b,
1326 _ => return None,
1327 };
1328 let close = if open == b'(' { b')' } else { open };
1329
1330 let mut title = String::new();
1331 let mut mark = start_ix + 1;
1332 let mut i = start_ix + 1;
1333
1334 while i < bytes.len() {
1335 let c = bytes[i];
1336
1337 if c == close {
1338 let cow = if mark == 1 {
1339 (i - start_ix + 1, text[mark..i].into())
1340 } else {
1341 title.push_str(&text[mark..i]);
1342 (i - start_ix + 1, title.into())
1343 };
1344
1345 return Some(cow);
1346 }
1347 if c == open {
1348 return None;
1349 }
1350
1351 if c == b'\n' || c == b'\r' {
1352 if let Some(node_ix) = scan_nodes_to_ix(&self.tree, node, i + 1) {
1353 if self.tree[node_ix].item.start > i {
1354 title.push_str(&text[mark..i]);
1355 title.push('\n');
1356 i = self.tree[node_ix].item.start;
1357 mark = i;
1358 continue;
1359 }
1360 }
1361 }
1362 if c == b'&' {
1363 if let (n, Some(value)) = scan_entity(&bytes[i..]) {
1364 title.push_str(&text[mark..i]);
1365 title.push_str(&value);
1366 i += n;
1367 mark = i;
1368 continue;
1369 }
1370 }
1371 if self.tree.is_in_table()
1372 && c == b'\\'
1373 && i + 2 < bytes.len()
1374 && bytes[i + 1] == b'\\'
1375 && bytes[i + 2] == b'|'
1376 {
1377 title.push_str(&text[mark..i]);
1380 i += 2;
1381 mark = i;
1382 }
1383 if c == b'\\' && i + 1 < bytes.len() && is_ascii_punctuation(bytes[i + 1]) {
1384 title.push_str(&text[mark..i]);
1385 i += 1;
1386 mark = i;
1387 }
1388
1389 i += 1;
1390 }
1391
1392 None
1393 }
1394
1395 fn make_math_span(&mut self, open: TreeIndex, mut close: TreeIndex) {
1396 let start_is_display = self.tree[open].next.filter(|&next_ix| {
1397 next_ix != close
1398 && matches!(
1399 self.tree[next_ix].item.body,
1400 ItemBody::MaybeMath(_can_open, _can_close, _brace_context)
1401 )
1402 });
1403 let end_is_display = self.tree[close].next.filter(|&next_ix| {
1404 matches!(
1405 self.tree[next_ix].item.body,
1406 ItemBody::MaybeMath(_can_open, _can_close, _brace_context)
1407 )
1408 });
1409 let is_display = start_is_display.is_some() && end_is_display.is_some();
1410 if is_display {
1411 close = self.tree[close].next.unwrap();
1413 self.tree[open].next = Some(close);
1414 self.tree[open].item.end += 1;
1415 self.tree[close].item.start -= 1;
1416 } else {
1417 if self.tree[open].item.end == self.tree[close].item.start {
1418 self.tree[open].item.body = ItemBody::Text {
1420 backslash_escaped: false,
1421 };
1422 return;
1423 }
1424 self.tree[open].next = Some(close);
1425 }
1426 let span_start = self.tree[open].item.end;
1427 let span_end = self.tree[close].item.start;
1428
1429 let spanned_text = &self.text[span_start..span_end];
1430 let spanned_bytes = spanned_text.as_bytes();
1431 let mut buf: Option<String> = None;
1432
1433 let mut start_ix = 0;
1434 let mut ix = 0;
1435 while ix < spanned_bytes.len() {
1436 let c = spanned_bytes[ix];
1437 if c == b'\r' || c == b'\n' {
1438 ix += 1;
1439 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
1440 buf.push_str(&spanned_text[start_ix..ix]);
1441 ix += skip_container_prefixes(&self.tree, &spanned_bytes[ix..], self.options);
1442 start_ix = ix;
1443 } else if c == b'\\'
1444 && spanned_bytes.get(ix + 1) == Some(&b'|')
1445 && self.tree.is_in_table()
1446 {
1447 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
1448 buf.push_str(&spanned_text[start_ix..ix]);
1449 buf.push('|');
1450 ix += 2;
1451 start_ix = ix;
1452 } else {
1453 ix += 1;
1454 }
1455 }
1456
1457 let cow = if let Some(mut buf) = buf {
1458 buf.push_str(&spanned_text[start_ix..]);
1459 buf.into()
1460 } else {
1461 spanned_text.into()
1462 };
1463
1464 self.tree[open].item.body = ItemBody::Math(self.allocs.allocate_cow(cow), is_display);
1465 self.tree[open].item.end = self.tree[close].item.end;
1466 self.tree[open].next = self.tree[close].next;
1467 }
1468
1469 fn make_code_span(&mut self, open: TreeIndex, close: TreeIndex, preceding_backslash: bool) {
1473 let span_start = self.tree[open].item.end;
1474 let span_end = self.tree[close].item.start;
1475 let mut buf: Option<String> = None;
1476
1477 let spanned_text = &self.text[span_start..span_end];
1478 let spanned_bytes = spanned_text.as_bytes();
1479 let mut start_ix = 0;
1480 let mut ix = 0;
1481 while ix < spanned_bytes.len() {
1482 let c = spanned_bytes[ix];
1483 if c == b'\r' || c == b'\n' {
1484 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
1485 buf.push_str(&spanned_text[start_ix..ix]);
1486 buf.push(' ');
1487 ix += 1;
1488 ix += skip_container_prefixes(&self.tree, &spanned_bytes[ix..], self.options);
1489 start_ix = ix;
1490 } else if c == b'\\'
1491 && spanned_bytes.get(ix + 1) == Some(&b'|')
1492 && self.tree.is_in_table()
1493 {
1494 let buf = buf.get_or_insert_with(|| String::with_capacity(spanned_bytes.len()));
1495 buf.push_str(&spanned_text[start_ix..ix]);
1496 buf.push('|');
1497 ix += 2;
1498 start_ix = ix;
1499 } else {
1500 ix += 1;
1501 }
1502 }
1503
1504 let (opening, closing, all_spaces) = {
1505 let s = if let Some(buf) = &mut buf {
1506 buf.push_str(&spanned_text[start_ix..]);
1507 &buf[..]
1508 } else {
1509 spanned_text
1510 };
1511 (
1512 s.as_bytes().first() == Some(&b' '),
1513 s.as_bytes().last() == Some(&b' '),
1514 s.bytes().all(|b| b == b' '),
1515 )
1516 };
1517
1518 let cow: CowStr<'input> = if !all_spaces && opening && closing {
1519 if let Some(mut buf) = buf {
1520 if !buf.is_empty() {
1521 buf.remove(0);
1522 buf.pop();
1523 }
1524 buf.into()
1525 } else {
1526 spanned_text[1..(spanned_text.len() - 1).max(1)].into()
1527 }
1528 } else if let Some(buf) = buf {
1529 buf.into()
1530 } else {
1531 spanned_text.into()
1532 };
1533
1534 if preceding_backslash {
1535 self.tree[open].item.body = ItemBody::Text {
1536 backslash_escaped: true,
1537 };
1538 self.tree[open].item.end = self.tree[open].item.start + 1;
1539 self.tree[open].next = Some(close);
1540 self.tree[close].item.body = ItemBody::Code(self.allocs.allocate_cow(cow));
1541 self.tree[close].item.start = self.tree[open].item.start + 1;
1542 } else {
1543 self.tree[open].item.body = ItemBody::Code(self.allocs.allocate_cow(cow));
1544 self.tree[open].item.end = self.tree[close].item.end;
1545 self.tree[open].next = self.tree[close].next;
1546 }
1547 }
1548
1549 fn scan_inline_html(&mut self, bytes: &[u8], ix: usize) -> Option<(Vec<u8>, usize)> {
1553 let c = *bytes.get(ix)?;
1554 if c == b'!' {
1555 Some((
1556 vec![],
1557 scan_inline_html_comment(bytes, ix + 1, &mut self.html_scan_guard)?,
1558 ))
1559 } else if c == b'?' {
1560 Some((
1561 vec![],
1562 scan_inline_html_processing(bytes, ix + 1, &mut self.html_scan_guard)?,
1563 ))
1564 } else {
1565 let (span, i) = scan_html_block_inner(
1566 &bytes[(ix - 1)..],
1568 Some(&|bytes| skip_container_prefixes(&self.tree, bytes, self.options)),
1569 )?;
1570 Some((span, i + ix - 1))
1571 }
1572 }
1573}
1574
1575pub(crate) fn scan_containers(
1577 tree: &Tree<Item>,
1578 line_start: &mut LineStart<'_>,
1579 options: Options,
1580) -> usize {
1581 let mut i = 0;
1582 for &node_ix in tree.walk_spine() {
1583 match tree[node_ix].item.body {
1584 ItemBody::BlockQuote(..) => {
1585 let save = line_start.clone();
1586 let _ = line_start.scan_space(3);
1587 if !line_start.scan_blockquote_marker() {
1588 *line_start = save;
1589 break;
1590 }
1591 }
1592 ItemBody::ListItem(indent) => {
1593 let save = line_start.clone();
1594 if !line_start.scan_space(indent) && !line_start.is_at_eol() {
1595 *line_start = save;
1596 break;
1597 }
1598 }
1599 ItemBody::DefinitionListDefinition(indent) => {
1600 let save = line_start.clone();
1601 if !line_start.scan_space(indent) && !line_start.is_at_eol() {
1602 *line_start = save;
1603 break;
1604 }
1605 }
1606 ItemBody::FootnoteDefinition(..) if options.has_gfm_footnotes() => {
1607 let save = line_start.clone();
1608 if !line_start.scan_space(4) && !line_start.is_at_eol() {
1609 *line_start = save;
1610 break;
1611 }
1612 }
1613 _ => (),
1614 }
1615 i += 1;
1616 }
1617 i
1618}
1619
1620pub(crate) fn skip_container_prefixes(tree: &Tree<Item>, bytes: &[u8], options: Options) -> usize {
1621 let mut line_start = LineStart::new(bytes);
1622 let _ = scan_containers(tree, &mut line_start, options);
1623 line_start.bytes_scanned()
1624}
1625
1626impl Tree<Item> {
1627 pub(crate) fn append_text(&mut self, start: usize, end: usize, backslash_escaped: bool) {
1628 if end > start {
1629 if let Some(ix) = self.cur() {
1630 if matches!(self[ix].item.body, ItemBody::Text { .. }) && self[ix].item.end == start
1631 {
1632 self[ix].item.end = end;
1633 return;
1634 }
1635 }
1636 self.append(Item {
1637 start,
1638 end,
1639 body: ItemBody::Text { backslash_escaped },
1640 });
1641 }
1642 }
1643 pub(crate) fn is_in_table(&self) -> bool {
1650 fn might_be_in_table(item: &Item) -> bool {
1651 item.body.is_inline()
1652 || matches!(item.body, |ItemBody::TableHead| ItemBody::TableRow
1653 | ItemBody::TableCell)
1654 }
1655 for &ix in self.walk_spine().rev() {
1656 if matches!(self[ix].item.body, ItemBody::Table(_)) {
1657 return true;
1658 }
1659 if !might_be_in_table(&self[ix].item) {
1660 return false;
1661 }
1662 }
1663 false
1664 }
1665}
1666
1667#[derive(Copy, Clone, Debug)]
1668struct InlineEl {
1669 start: TreeIndex,
1671 count: usize,
1673 run_length: usize,
1675 c: u8,
1677 both: bool,
1679}
1680
1681#[derive(Debug, Clone, Default)]
1682struct InlineStack {
1683 stack: Vec<InlineEl>,
1684 lower_bounds: [usize; 10],
1689}
1690
1691impl InlineStack {
1692 const UNDERSCORE_NOT_BOTH: usize = 0;
1696 const ASTERISK_NOT_BOTH: usize = 1;
1697 const ASTERISK_BASE: usize = 2;
1698 const TILDES: usize = 5;
1699 const UNDERSCORE_BASE: usize = 6;
1700 const CIRCUMFLEXES: usize = 9;
1701
1702 fn pop_all(&mut self, tree: &mut Tree<Item>) {
1703 for el in self.stack.drain(..) {
1704 for i in 0..el.count {
1705 tree[el.start + i].item.body = ItemBody::Text {
1706 backslash_escaped: false,
1707 };
1708 }
1709 }
1710 self.lower_bounds = [0; 10];
1711 }
1712
1713 fn get_lowerbound(&self, c: u8, count: usize, both: bool) -> usize {
1714 if c == b'_' {
1715 let mod3_lower = self.lower_bounds[InlineStack::UNDERSCORE_BASE + count % 3];
1716 if both {
1717 mod3_lower
1718 } else {
1719 min(
1720 mod3_lower,
1721 self.lower_bounds[InlineStack::UNDERSCORE_NOT_BOTH],
1722 )
1723 }
1724 } else if c == b'*' {
1725 let mod3_lower = self.lower_bounds[InlineStack::ASTERISK_BASE + count % 3];
1726 if both {
1727 mod3_lower
1728 } else {
1729 min(
1730 mod3_lower,
1731 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH],
1732 )
1733 }
1734 } else if c == b'^' {
1735 self.lower_bounds[InlineStack::CIRCUMFLEXES]
1736 } else {
1737 self.lower_bounds[InlineStack::TILDES]
1738 }
1739 }
1740
1741 fn set_lowerbound(&mut self, c: u8, count: usize, both: bool, new_bound: usize) {
1742 if c == b'_' {
1743 if both {
1744 self.lower_bounds[InlineStack::UNDERSCORE_BASE + count % 3] = new_bound;
1745 } else {
1746 self.lower_bounds[InlineStack::UNDERSCORE_NOT_BOTH] = new_bound;
1747 }
1748 } else if c == b'*' {
1749 self.lower_bounds[InlineStack::ASTERISK_BASE + count % 3] = new_bound;
1750 if !both {
1751 self.lower_bounds[InlineStack::ASTERISK_NOT_BOTH] = new_bound;
1752 }
1753 } else if c == b'^' {
1754 self.lower_bounds[InlineStack::CIRCUMFLEXES] = new_bound;
1755 } else {
1756 self.lower_bounds[InlineStack::TILDES] = new_bound;
1757 }
1758 }
1759
1760 fn truncate(&mut self, new_bound: usize) {
1761 self.stack.truncate(new_bound);
1762 for lower_bound in &mut self.lower_bounds {
1763 if *lower_bound > new_bound {
1764 *lower_bound = new_bound;
1765 }
1766 }
1767 }
1768
1769 fn find_match(
1770 &mut self,
1771 tree: &mut Tree<Item>,
1772 c: u8,
1773 run_length: usize,
1774 both: bool,
1775 ) -> Option<InlineEl> {
1776 let lowerbound = min(self.stack.len(), self.get_lowerbound(c, run_length, both));
1777 let res = self.stack[lowerbound..]
1778 .iter()
1779 .cloned()
1780 .enumerate()
1781 .rfind(|(_, el)| {
1782 if (c == b'~' || c == b'^') && run_length != el.run_length {
1783 return false;
1784 }
1785 el.c == c
1786 && (!both && !el.both
1787 || !(run_length + el.run_length).is_multiple_of(3)
1788 || run_length.is_multiple_of(3))
1789 });
1790
1791 if let Some((matching_ix, matching_el)) = res {
1792 let matching_ix = matching_ix + lowerbound;
1793 for el in &self.stack[(matching_ix + 1)..] {
1794 for i in 0..el.count {
1795 tree[el.start + i].item.body = ItemBody::Text {
1796 backslash_escaped: false,
1797 };
1798 }
1799 }
1800 self.truncate(matching_ix);
1801 Some(matching_el)
1802 } else {
1803 self.set_lowerbound(c, run_length, both, self.stack.len());
1804 None
1805 }
1806 }
1807
1808 fn trim_lower_bound(&mut self, ix: usize) {
1809 self.lower_bounds[ix] = self.lower_bounds[ix].min(self.stack.len());
1810 }
1811
1812 fn push(&mut self, el: InlineEl) {
1813 if el.c == b'~' {
1814 self.trim_lower_bound(InlineStack::TILDES);
1815 } else if el.c == b'^' {
1816 self.trim_lower_bound(InlineStack::CIRCUMFLEXES);
1817 }
1818 self.stack.push(el)
1819 }
1820}
1821
1822#[derive(Debug, Clone)]
1823enum RefScan<'a> {
1824 LinkLabel(CowStr<'a>, usize),
1826 Collapsed(Option<TreeIndex>),
1828 UnexpectedFootnote,
1829 Failed,
1830}
1831
1832fn scan_nodes_to_ix(
1835 tree: &Tree<Item>,
1836 mut node: Option<TreeIndex>,
1837 ix: usize,
1838) -> Option<TreeIndex> {
1839 while let Some(node_ix) = node {
1840 if tree[node_ix].item.end <= ix {
1841 node = tree[node_ix].next;
1842 } else {
1843 break;
1844 }
1845 }
1846 node
1847}
1848
1849fn scan_link_label<'text>(
1852 tree: &Tree<Item>,
1853 text: &'text str,
1854 options: Options,
1855) -> Option<(usize, ReferenceLabel<'text>)> {
1856 let bytes = text.as_bytes();
1857 if bytes.len() < 2 || bytes[0] != b'[' {
1858 return None;
1859 }
1860 let linebreak_handler = |bytes: &[u8]| Some(skip_container_prefixes(tree, bytes, options));
1861 if options.contains(Options::ENABLE_FOOTNOTES)
1862 && b'^' == bytes[1]
1863 && bytes.get(2) != Some(&b']')
1864 {
1865 let linebreak_handler: &dyn Fn(&[u8]) -> Option<usize> = if options.has_gfm_footnotes() {
1866 &|_| None
1867 } else {
1868 &linebreak_handler
1869 };
1870 if let Some((byte_index, cow)) =
1871 scan_link_label_rest(&text[2..], linebreak_handler, tree.is_in_table())
1872 {
1873 return Some((byte_index + 2, ReferenceLabel::Footnote(cow)));
1874 }
1875 }
1876 let (byte_index, cow) =
1877 scan_link_label_rest(&text[1..], &linebreak_handler, tree.is_in_table())?;
1878 Some((byte_index + 1, ReferenceLabel::Link(cow)))
1879}
1880
1881fn scan_reference<'b>(
1882 tree: &Tree<Item>,
1883 text: &'b str,
1884 cur: Option<TreeIndex>,
1885 options: Options,
1886) -> RefScan<'b> {
1887 let cur_ix = match cur {
1888 None => return RefScan::Failed,
1889 Some(cur_ix) => cur_ix,
1890 };
1891 let start = tree[cur_ix].item.start;
1892 let tail = &text.as_bytes()[start..];
1893
1894 if tail.starts_with(b"[]") {
1895 let closing_node = tree[cur_ix].next.unwrap();
1897 RefScan::Collapsed(tree[closing_node].next)
1898 } else {
1899 let label = scan_link_label(tree, &text[start..], options);
1900 match label {
1901 Some((ix, ReferenceLabel::Link(label))) => RefScan::LinkLabel(label, start + ix),
1902 Some((_ix, ReferenceLabel::Footnote(_label))) => RefScan::UnexpectedFootnote,
1903 None => RefScan::Failed,
1904 }
1905 }
1906}
1907
1908#[derive(Clone, Default)]
1909struct LinkStack {
1910 inner: Vec<LinkStackEl>,
1911 disabled_ix: usize,
1912}
1913
1914impl LinkStack {
1915 fn push(&mut self, el: LinkStackEl) {
1916 self.inner.push(el);
1917 }
1918
1919 fn pop(&mut self) -> Option<LinkStackEl> {
1920 let el = self.inner.pop();
1921 self.disabled_ix = core::cmp::min(self.disabled_ix, self.inner.len());
1922 el
1923 }
1924
1925 fn clear(&mut self) {
1926 self.inner.clear();
1927 self.disabled_ix = 0;
1928 }
1929
1930 fn disable_all_links(&mut self) {
1931 for el in &mut self.inner[self.disabled_ix..] {
1932 if el.ty == LinkStackTy::Link {
1933 el.ty = LinkStackTy::Disabled;
1934 }
1935 }
1936 self.disabled_ix = self.inner.len();
1937 }
1938}
1939
1940#[derive(Clone, Debug)]
1941struct LinkStackEl {
1942 node: TreeIndex,
1943 ty: LinkStackTy,
1944}
1945
1946#[derive(PartialEq, Clone, Debug)]
1947enum LinkStackTy {
1948 Link,
1949 Image,
1950 Disabled,
1951}
1952
1953#[derive(Clone, Debug)]
1955pub struct LinkDef<'a> {
1956 pub dest: CowStr<'a>,
1957 pub title: Option<CowStr<'a>>,
1958 pub span: Range<usize>,
1959}
1960
1961impl<'a> LinkDef<'a> {
1962 pub fn into_static(self) -> LinkDef<'static> {
1963 LinkDef {
1964 dest: self.dest.into_static(),
1965 title: self.title.map(|s| s.into_static()),
1966 span: self.span,
1967 }
1968 }
1969}
1970
1971#[derive(Clone, Debug)]
1973pub struct FootnoteDef {
1974 pub use_count: usize,
1975}
1976
1977struct CodeDelims {
1980 inner: FxHashMap<usize, VecDeque<TreeIndex>>,
1981 seen_first: bool,
1982}
1983
1984impl CodeDelims {
1985 fn new() -> Self {
1986 Self {
1987 inner: Default::default(),
1988 seen_first: false,
1989 }
1990 }
1991
1992 fn insert(&mut self, count: usize, ix: TreeIndex) {
1993 if self.seen_first {
1994 self.inner.entry(count).or_default().push_back(ix);
1995 } else {
1996 self.seen_first = true;
1999 }
2000 }
2001
2002 fn is_populated(&self) -> bool {
2003 !self.inner.is_empty()
2004 }
2005
2006 fn find(&mut self, open_ix: TreeIndex, count: usize) -> Option<TreeIndex> {
2007 while let Some(ix) = self.inner.get_mut(&count)?.pop_front() {
2008 if ix > open_ix {
2009 return Some(ix);
2010 }
2011 }
2012 None
2013 }
2014
2015 fn clear(&mut self) {
2016 self.inner.clear();
2017 self.seen_first = false;
2018 }
2019}
2020
2021struct MathDelims {
2024 inner: FxHashMap<u8, VecDeque<(TreeIndex, bool, bool)>>,
2025}
2026
2027impl MathDelims {
2028 fn new() -> Self {
2029 Self {
2030 inner: Default::default(),
2031 }
2032 }
2033
2034 fn insert(
2035 &mut self,
2036 delim_is_display: bool,
2037 brace_context: u8,
2038 ix: TreeIndex,
2039 can_close: bool,
2040 ) {
2041 self.inner
2042 .entry(brace_context)
2043 .or_default()
2044 .push_back((ix, can_close, delim_is_display));
2045 }
2046
2047 fn is_populated(&self) -> bool {
2048 !self.inner.is_empty()
2049 }
2050
2051 fn find(
2052 &mut self,
2053 tree: &Tree<Item>,
2054 open_ix: TreeIndex,
2055 is_display: bool,
2056 brace_context: u8,
2057 ) -> Option<TreeIndex> {
2058 while let Some((ix, can_close, delim_is_display)) =
2059 self.inner.get_mut(&brace_context)?.pop_front()
2060 {
2061 if ix <= open_ix || (is_display && tree[open_ix].next == Some(ix)) {
2062 continue;
2063 }
2064 let can_close = can_close && tree[open_ix].item.end != tree[ix].item.start;
2065 if (!is_display && can_close) || (is_display && delim_is_display) {
2066 return Some(ix);
2067 }
2068 self.inner
2071 .get_mut(&brace_context)?
2072 .push_front((ix, can_close, delim_is_display));
2073 break;
2074 }
2075 None
2076 }
2077
2078 fn clear(&mut self) {
2079 self.inner.clear();
2080 }
2081}
2082
2083#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2084pub(crate) struct LinkIndex(usize);
2085
2086#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2087pub(crate) struct CowIndex(usize);
2088
2089#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2090pub(crate) struct AlignmentIndex(usize);
2091
2092#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2093pub(crate) struct HeadingIndex(NonZeroUsize);
2094
2095#[derive(Copy, Clone, PartialEq, Eq, Debug)]
2096pub(crate) struct JsxElementIndex(usize);
2097
2098#[derive(Debug, Clone)]
2100pub(crate) enum JsxAttr<'a> {
2101 Boolean(CowStr<'a>),
2102 Literal(CowStr<'a>, CowStr<'a>),
2103 Expression(CowStr<'a>, CowStr<'a>),
2104 Spread(CowStr<'a>),
2105}
2106
2107#[derive(Debug, Clone)]
2109pub(crate) struct JsxElementData<'a> {
2110 pub name: CowStr<'a>,
2111 pub attrs: Vec<JsxAttr<'a>>,
2112 pub raw: CowStr<'a>,
2113 pub is_closing: bool,
2114 pub is_self_closing: bool,
2115}
2116
2117#[derive(Clone)]
2118pub(crate) struct Allocations<'a> {
2119 pub refdefs: RefDefs<'a>,
2120 pub footdefs: FootnoteDefs<'a>,
2121 links: Vec<(LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>)>,
2122 cows: Vec<CowStr<'a>>,
2123 alignments: Vec<Vec<Alignment>>,
2124 headings: Vec<HeadingAttributes<'a>>,
2125 jsx_elements: Vec<JsxElementData<'a>>,
2126}
2127
2128#[derive(Clone)]
2130pub(crate) struct HeadingAttributes<'a> {
2131 pub id: Option<CowStr<'a>>,
2132 pub classes: Vec<CowStr<'a>>,
2133 pub attrs: Vec<(CowStr<'a>, Option<CowStr<'a>>)>,
2134}
2135
2136#[derive(Clone, Default, Debug)]
2138pub struct RefDefs<'input>(pub(crate) FxHashMap<LinkLabel<'input>, LinkDef<'input>>);
2139
2140#[derive(Clone, Default, Debug)]
2142pub struct FootnoteDefs<'input>(pub(crate) FxHashMap<FootnoteLabel<'input>, FootnoteDef>);
2143
2144impl<'input, 'b, 's> RefDefs<'input>
2145where
2146 's: 'b,
2147{
2148 pub fn get(&'s self, key: &'b str) -> Option<&'b LinkDef<'input>> {
2150 self.0.get(&UniCase::new(key.into()))
2151 }
2152
2153 pub fn iter(&'s self) -> impl Iterator<Item = (&'s str, &'s LinkDef<'input>)> {
2155 self.0.iter().map(|(k, v)| (k.as_ref(), v))
2156 }
2157}
2158
2159impl<'input, 'b, 's> FootnoteDefs<'input>
2160where
2161 's: 'b,
2162{
2163 pub fn contains(&'s self, key: &'b str) -> bool {
2165 self.0.contains_key(&UniCase::new(key.into()))
2166 }
2167 pub fn get_mut(&'s mut self, key: CowStr<'input>) -> Option<&'s mut FootnoteDef> {
2169 self.0.get_mut(&UniCase::new(key))
2170 }
2171}
2172
2173impl<'a> Allocations<'a> {
2174 pub fn new() -> Self {
2175 Self {
2176 refdefs: RefDefs::default(),
2177 footdefs: FootnoteDefs::default(),
2178 links: Vec::with_capacity(128),
2179 cows: Vec::new(),
2180 alignments: Vec::new(),
2181 headings: Vec::new(),
2182 jsx_elements: Vec::new(),
2183 }
2184 }
2185
2186 pub fn allocate_cow(&mut self, cow: CowStr<'a>) -> CowIndex {
2187 let ix = self.cows.len();
2188 self.cows.push(cow);
2189 CowIndex(ix)
2190 }
2191
2192 pub fn allocate_link(
2193 &mut self,
2194 ty: LinkType,
2195 url: CowStr<'a>,
2196 title: CowStr<'a>,
2197 id: CowStr<'a>,
2198 ) -> LinkIndex {
2199 let ix = self.links.len();
2200 self.links.push((ty, url, title, id));
2201 LinkIndex(ix)
2202 }
2203
2204 pub fn allocate_alignment(&mut self, alignment: Vec<Alignment>) -> AlignmentIndex {
2205 let ix = self.alignments.len();
2206 self.alignments.push(alignment);
2207 AlignmentIndex(ix)
2208 }
2209
2210 pub fn allocate_heading(&mut self, attrs: HeadingAttributes<'a>) -> HeadingIndex {
2211 let ix = self.headings.len();
2212 self.headings.push(attrs);
2213 let ix_nonzero = NonZeroUsize::new(ix.wrapping_add(1)).expect("too many headings");
2216 HeadingIndex(ix_nonzero)
2217 }
2218
2219 pub fn take_cow(&mut self, ix: CowIndex) -> CowStr<'a> {
2220 core::mem::replace(&mut self.cows[ix.0], "".into())
2221 }
2222
2223 pub fn take_link(&mut self, ix: LinkIndex) -> (LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>) {
2224 let default_link = (LinkType::ShortcutUnknown, "".into(), "".into(), "".into());
2225 core::mem::replace(&mut self.links[ix.0], default_link)
2226 }
2227
2228 pub fn take_alignment(&mut self, ix: AlignmentIndex) -> Vec<Alignment> {
2229 core::mem::take(&mut self.alignments[ix.0])
2230 }
2231
2232 pub fn allocate_jsx_element(&mut self, data: JsxElementData<'a>) -> JsxElementIndex {
2233 let ix = self.jsx_elements.len();
2234 self.jsx_elements.push(data);
2235 JsxElementIndex(ix)
2236 }
2237
2238 pub fn take_jsx_element(&mut self, ix: JsxElementIndex) -> JsxElementData<'a> {
2239 core::mem::replace(
2240 &mut self.jsx_elements[ix.0],
2241 JsxElementData {
2242 name: "".into(),
2243 attrs: Vec::new(),
2244 raw: "".into(),
2245 is_closing: false,
2246 is_self_closing: false,
2247 },
2248 )
2249 }
2250}
2251
2252impl<'a> Index<CowIndex> for Allocations<'a> {
2253 type Output = CowStr<'a>;
2254
2255 fn index(&self, ix: CowIndex) -> &Self::Output {
2256 self.cows.index(ix.0)
2257 }
2258}
2259
2260impl<'a> Index<LinkIndex> for Allocations<'a> {
2261 type Output = (LinkType, CowStr<'a>, CowStr<'a>, CowStr<'a>);
2262
2263 fn index(&self, ix: LinkIndex) -> &Self::Output {
2264 self.links.index(ix.0)
2265 }
2266}
2267
2268impl<'a> Index<AlignmentIndex> for Allocations<'a> {
2269 type Output = Vec<Alignment>;
2270
2271 fn index(&self, ix: AlignmentIndex) -> &Self::Output {
2272 self.alignments.index(ix.0)
2273 }
2274}
2275
2276impl<'a> Index<HeadingIndex> for Allocations<'a> {
2277 type Output = HeadingAttributes<'a>;
2278
2279 fn index(&self, ix: HeadingIndex) -> &Self::Output {
2280 self.headings.index(ix.0.get() - 1)
2281 }
2282}
2283
2284#[derive(Clone, Default)]
2290pub(crate) struct HtmlScanGuard {
2291 pub cdata: usize,
2292 pub processing: usize,
2293 pub declaration: usize,
2294 pub comment: usize,
2295}
2296
2297pub trait ParserCallbacks<'input> {
2301 fn handle_broken_link(
2309 &mut self,
2310 #[allow(unused_variables)] link: BrokenLink<'input>,
2311 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
2312 None
2313 }
2314}
2315
2316#[allow(missing_debug_implementations)]
2320pub struct BrokenLinkCallback<F>(Option<F>);
2321
2322impl<'input, F> ParserCallbacks<'input> for BrokenLinkCallback<F>
2323where
2324 F: FnMut(BrokenLink<'input>) -> Option<(CowStr<'input>, CowStr<'input>)>,
2325{
2326 fn handle_broken_link(
2327 &mut self,
2328 link: BrokenLink<'input>,
2329 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
2330 self.0.as_mut().and_then(|cb| cb(link))
2331 }
2332}
2333
2334impl<'input> ParserCallbacks<'input> for Box<dyn ParserCallbacks<'input>> {
2335 fn handle_broken_link(
2336 &mut self,
2337 link: BrokenLink<'input>,
2338 ) -> Option<(CowStr<'input>, CowStr<'input>)> {
2339 (**self).handle_broken_link(link)
2340 }
2341}
2342
2343#[allow(missing_debug_implementations)]
2347pub struct DefaultParserCallbacks;
2348
2349impl<'input> ParserCallbacks<'input> for DefaultParserCallbacks {}
2350
2351#[derive(Debug)]
2359pub struct OffsetIter<'a, CB> {
2360 parser: Parser<'a, CB>,
2361}
2362
2363impl<'a, CB: ParserCallbacks<'a>> OffsetIter<'a, CB> {
2364 pub fn reference_definitions(&self) -> &RefDefs<'_> {
2366 self.parser.reference_definitions()
2367 }
2368
2369 pub fn mdx_errors(&self) -> &[(usize, String)] {
2371 self.parser.mdx_errors()
2372 }
2373}
2374
2375impl<'a, CB: ParserCallbacks<'a>> Iterator for OffsetIter<'a, CB> {
2376 type Item = (Event<'a>, Range<usize>);
2377
2378 fn next(&mut self) -> Option<Self::Item> {
2379 self.parser
2380 .inner
2381 .next_event_range(&mut self.parser.callbacks)
2382 }
2383}
2384
2385impl<'a, CB: ParserCallbacks<'a>> Iterator for Parser<'a, CB> {
2386 type Item = Event<'a>;
2387
2388 fn next(&mut self) -> Option<Event<'a>> {
2389 self.inner
2390 .next_event_range(&mut self.callbacks)
2391 .map(|(event, _range)| event)
2392 }
2393}
2394
2395impl<'a, CB: ParserCallbacks<'a>> FusedIterator for Parser<'a, CB> {}
2396
2397impl<'input> ParserInner<'input> {
2398 fn next_event_range(
2399 &mut self,
2400 callbacks: &mut dyn ParserCallbacks<'input>,
2401 ) -> Option<(Event<'input>, Range<usize>)> {
2402 match self.tree.cur() {
2403 None => {
2404 let ix = self.tree.pop()?;
2405 let ix = if matches!(self.tree[ix].item.body, ItemBody::TightParagraph) {
2406 self.tree.next_sibling(ix);
2408 return self.next_event_range(callbacks);
2409 } else {
2410 ix
2411 };
2412 let tag_end = body_to_tag_end(&self.tree[ix].item.body);
2413 self.tree.next_sibling(ix);
2414 let span = self.tree[ix].item.start..self.tree[ix].item.end;
2415 debug_assert!(span.start <= span.end);
2416 Some((Event::End(tag_end), span))
2417 }
2418 Some(cur_ix) => {
2419 let cur_ix = if matches!(self.tree[cur_ix].item.body, ItemBody::TightParagraph) {
2420 self.tree.push();
2422 self.tree.cur().unwrap()
2423 } else {
2424 cur_ix
2425 };
2426 if self.tree[cur_ix].item.body.is_maybe_inline() {
2427 self.handle_inline(callbacks);
2428 }
2429
2430 let node = self.tree[cur_ix];
2431 let item = node.item;
2432 let event = item_to_event(item, self.text, &mut self.allocs);
2433 if let Event::Start(..) = event {
2434 self.tree.push();
2435 } else {
2436 self.tree.next_sibling(cur_ix);
2437 }
2438 debug_assert!(item.start <= item.end);
2439 Some((event, item.start..item.end))
2440 }
2441 }
2442 }
2443}
2444
2445fn body_to_tag_end(body: &ItemBody) -> TagEnd {
2446 match *body {
2447 ItemBody::Paragraph => TagEnd::Paragraph,
2448 ItemBody::Emphasis => TagEnd::Emphasis,
2449 ItemBody::Superscript => TagEnd::Superscript,
2450 ItemBody::Subscript => TagEnd::Subscript,
2451 ItemBody::Strong => TagEnd::Strong,
2452 ItemBody::Strikethrough => TagEnd::Strikethrough,
2453 ItemBody::Link(..) => TagEnd::Link,
2454 ItemBody::Image(..) => TagEnd::Image,
2455 ItemBody::Heading(level, _) => TagEnd::Heading(level),
2456 ItemBody::IndentCodeBlock | ItemBody::FencedCodeBlock(..) => TagEnd::CodeBlock,
2457 ItemBody::Container(_, kind, _) => TagEnd::ContainerBlock(kind),
2458 ItemBody::BlockQuote(kind) => TagEnd::BlockQuote(kind),
2459 ItemBody::HtmlBlock => TagEnd::HtmlBlock,
2460 ItemBody::List(_, c, _) => {
2461 let is_ordered = c == b'.' || c == b')';
2462 TagEnd::List(is_ordered)
2463 }
2464 ItemBody::ListItem(_) => TagEnd::Item,
2465 ItemBody::TableHead => TagEnd::TableHead,
2466 ItemBody::TableCell => TagEnd::TableCell,
2467 ItemBody::TableRow => TagEnd::TableRow,
2468 ItemBody::Table(..) => TagEnd::Table,
2469 ItemBody::FootnoteDefinition(..) => TagEnd::FootnoteDefinition,
2470 ItemBody::MetadataBlock(kind) => TagEnd::MetadataBlock(kind),
2471 ItemBody::DefinitionList(_) => TagEnd::DefinitionList,
2472 ItemBody::DefinitionListTitle => TagEnd::DefinitionListTitle,
2473 ItemBody::DefinitionListDefinition(_) => TagEnd::DefinitionListDefinition,
2474 ItemBody::MdxJsxFlowElement(..) => TagEnd::MdxJsxFlowElement,
2475 ItemBody::MdxJsxTextElement(..) => TagEnd::MdxJsxTextElement,
2476 _ => panic!("unexpected item body {:?}", body),
2477 }
2478}
2479
2480fn item_to_event<'a>(item: Item, text: &'a str, allocs: &mut Allocations<'a>) -> Event<'a> {
2481 let tag = match item.body {
2482 ItemBody::Text { .. } => return Event::Text(text[item.start..item.end].into()),
2483 ItemBody::Code(cow_ix) => return Event::Code(allocs.take_cow(cow_ix)),
2484 ItemBody::SynthesizeText(cow_ix) => return Event::Text(allocs.take_cow(cow_ix)),
2485 ItemBody::SynthesizeChar(c) => return Event::Text(c.into()),
2486 ItemBody::HtmlBlock => Tag::HtmlBlock,
2487 ItemBody::Html => return Event::Html(text[item.start..item.end].into()),
2488 ItemBody::InlineHtml => return Event::InlineHtml(text[item.start..item.end].into()),
2489 ItemBody::OwnedInlineHtml(cow_ix) => return Event::InlineHtml(allocs.take_cow(cow_ix)),
2490 ItemBody::SoftBreak => return Event::SoftBreak,
2491 ItemBody::HardBreak(_) => return Event::HardBreak,
2492 ItemBody::FootnoteReference(cow_ix) => {
2493 return Event::FootnoteReference(allocs.take_cow(cow_ix))
2494 }
2495 ItemBody::TaskListMarker(checked) => return Event::TaskListMarker(checked),
2496 ItemBody::Rule => return Event::Rule,
2497 ItemBody::Paragraph => Tag::Paragraph,
2498 ItemBody::Emphasis => Tag::Emphasis,
2499 ItemBody::Superscript => Tag::Superscript,
2500 ItemBody::Subscript => Tag::Subscript,
2501 ItemBody::Strong => Tag::Strong,
2502 ItemBody::Strikethrough => Tag::Strikethrough,
2503 ItemBody::Link(link_ix) => {
2504 let (link_type, dest_url, title, id) = allocs.take_link(link_ix);
2505 Tag::Link {
2506 link_type,
2507 dest_url,
2508 title,
2509 id,
2510 }
2511 }
2512 ItemBody::Image(link_ix) => {
2513 let (link_type, dest_url, title, id) = allocs.take_link(link_ix);
2514 Tag::Image {
2515 link_type,
2516 dest_url,
2517 title,
2518 id,
2519 }
2520 }
2521 ItemBody::Heading(level, Some(heading_ix)) => {
2522 let HeadingAttributes { id, classes, attrs } = allocs.index(heading_ix);
2523 Tag::Heading {
2524 level,
2525 id: id.clone(),
2526 classes: classes.clone(),
2527 attrs: attrs.clone(),
2528 }
2529 }
2530 ItemBody::Heading(level, None) => Tag::Heading {
2531 level,
2532 id: None,
2533 classes: Vec::new(),
2534 attrs: Vec::new(),
2535 },
2536 ItemBody::FencedCodeBlock(cow_ix) => {
2537 Tag::CodeBlock(CodeBlockKind::Fenced(allocs.take_cow(cow_ix)))
2538 }
2539 ItemBody::IndentCodeBlock => Tag::CodeBlock(CodeBlockKind::Indented),
2540 ItemBody::Container(_, kind, cow_ix) => Tag::ContainerBlock(kind, allocs.take_cow(cow_ix)),
2541 ItemBody::BlockQuote(kind) => Tag::BlockQuote(kind),
2542 ItemBody::List(is_tight, c, listitem_start) => {
2543 if c == b'.' || c == b')' {
2544 Tag::List(Some(listitem_start), is_tight)
2545 } else {
2546 Tag::List(None, is_tight)
2547 }
2548 }
2549 ItemBody::ListItem(_) => Tag::Item,
2550 ItemBody::TableHead => Tag::TableHead,
2551 ItemBody::TableCell => Tag::TableCell,
2552 ItemBody::TableRow => Tag::TableRow,
2553 ItemBody::Table(alignment_ix) => Tag::Table(allocs.take_alignment(alignment_ix)),
2554 ItemBody::FootnoteDefinition(cow_ix) => Tag::FootnoteDefinition(allocs.take_cow(cow_ix)),
2555 ItemBody::MetadataBlock(kind) => Tag::MetadataBlock(kind),
2556 ItemBody::Math(cow_ix, is_display) => {
2557 return if is_display {
2558 Event::DisplayMath(allocs.take_cow(cow_ix))
2559 } else {
2560 Event::InlineMath(allocs.take_cow(cow_ix))
2561 }
2562 }
2563 ItemBody::DefinitionList(_) => Tag::DefinitionList,
2564 ItemBody::DefinitionListTitle => Tag::DefinitionListTitle,
2565 ItemBody::DefinitionListDefinition(_) => Tag::DefinitionListDefinition,
2566 ItemBody::MdxJsxFlowElement(jsx_ix) => {
2567 let jsx = allocs.take_jsx_element(jsx_ix);
2568 Tag::MdxJsxFlowElement(jsx.raw)
2569 }
2570 ItemBody::MdxJsxTextElement(jsx_ix) => {
2571 let jsx = allocs.take_jsx_element(jsx_ix);
2572 Tag::MdxJsxTextElement(jsx.raw)
2573 }
2574 ItemBody::MdxFlowExpression(cow_ix) => {
2575 return Event::MdxFlowExpression(allocs.take_cow(cow_ix))
2576 }
2577 ItemBody::MdxTextExpression(cow_ix) => {
2578 return Event::MdxTextExpression(allocs.take_cow(cow_ix))
2579 }
2580 ItemBody::MdxEsm(cow_ix) => return Event::MdxEsm(allocs.take_cow(cow_ix)),
2581 _ => panic!("unexpected item body {:?}", item.body),
2582 };
2583
2584 Event::Start(tag)
2585}
2586
2587#[cfg(test)]
2588mod test {
2589 use alloc::{borrow::ToOwned, string::ToString, vec::Vec};
2590
2591 use super::*;
2592 use crate::tree::Node;
2593
2594 fn parser_with_extensions(text: &str) -> Parser<'_> {
2597 let mut opts = Options::empty();
2598 opts.insert(Options::ENABLE_TABLES);
2599 opts.insert(Options::ENABLE_FOOTNOTES);
2600 opts.insert(Options::ENABLE_STRIKETHROUGH);
2601 opts.insert(Options::ENABLE_SUPERSCRIPT);
2602 opts.insert(Options::ENABLE_SUBSCRIPT);
2603 opts.insert(Options::ENABLE_TASKLISTS);
2604
2605 Parser::new_ext(text, opts)
2606 }
2607
2608 #[test]
2609 #[cfg(target_pointer_width = "64")]
2610 fn node_size() {
2611 let node_size = core::mem::size_of::<Node<Item>>();
2612 assert_eq!(48, node_size);
2613 }
2614
2615 #[test]
2616 #[cfg(target_pointer_width = "64")]
2617 fn body_size() {
2618 let body_size = core::mem::size_of::<ItemBody>();
2619 assert_eq!(16, body_size);
2620 }
2621
2622 #[test]
2623 fn single_open_fish_bracket() {
2624 assert_eq!(3, Parser::new("<").count());
2626 }
2627
2628 #[test]
2629 fn lone_hashtag() {
2630 assert_eq!(2, Parser::new("#").count());
2632 }
2633
2634 #[test]
2635 fn lots_of_backslashes() {
2636 Parser::new("\\\\\r\r").count();
2638 Parser::new("\\\r\r\\.\\\\\r\r\\.\\").count();
2639 }
2640
2641 #[test]
2642 fn issue_1030() {
2643 let mut opts = Options::empty();
2644 opts.insert(Options::ENABLE_WIKILINKS);
2645
2646 let parser = Parser::new_ext("For a new ferrari, [[Wikientry|click here]]!", opts);
2647
2648 let offsets = parser
2649 .into_offset_iter()
2650 .map(|(_ev, range)| range)
2651 .collect::<Vec<_>>();
2652 let expected_offsets = vec![
2653 (0..44), (0..19), (19..43), (31..41), (19..43), (43..44), (0..44), ];
2661 assert_eq!(offsets, expected_offsets);
2662 }
2663
2664 #[test]
2665 fn issue_320() {
2666 parser_with_extensions(":\r\t> |\r:\r\t> |\r").count();
2668 }
2669
2670 #[test]
2671 fn issue_319() {
2672 parser_with_extensions("|\r-]([^|\r-]([^").count();
2674 parser_with_extensions("|\r\r=][^|\r\r=][^car").count();
2675 }
2676
2677 #[test]
2678 fn issue_303() {
2679 parser_with_extensions("[^\r\ra]").count();
2681 parser_with_extensions("\r\r]Z[^\x00\r\r]Z[^\x00").count();
2682 }
2683
2684 #[test]
2685 fn issue_313() {
2686 parser_with_extensions("*]0[^\r\r*]0[^").count();
2688 parser_with_extensions("[^\r> `][^\r> `][^\r> `][").count();
2689 }
2690
2691 #[test]
2692 fn issue_311() {
2693 parser_with_extensions("\\\u{0d}-\u{09}\\\u{0d}-\u{09}").count();
2695 }
2696
2697 #[test]
2698 fn issue_283() {
2699 let input = core::str::from_utf8(b"\xf0\x9b\xb2\x9f<td:^\xf0\x9b\xb2\x9f").unwrap();
2700 parser_with_extensions(input).count();
2702 }
2703
2704 #[test]
2705 fn issue_289() {
2706 parser_with_extensions("> - \\\n> - ").count();
2708 parser_with_extensions("- \n\n").count();
2709 }
2710
2711 #[test]
2712 fn issue_306() {
2713 parser_with_extensions("*\r_<__*\r_<__*\r_<__*\r_<__").count();
2715 }
2716
2717 #[test]
2718 fn issue_305() {
2719 parser_with_extensions("_6**6*_*").count();
2721 }
2722
2723 #[test]
2724 fn another_emphasis_panic() {
2725 parser_with_extensions("*__#_#__*").count();
2726 }
2727
2728 #[test]
2729 fn offset_iter() {
2730 let event_offsets: Vec<_> = Parser::new("*hello* world")
2731 .into_offset_iter()
2732 .map(|(_ev, range)| range)
2733 .collect();
2734 let expected_offsets = vec![(0..13), (0..7), (1..6), (0..7), (7..13), (0..13)];
2735 assert_eq!(expected_offsets, event_offsets);
2736 }
2737
2738 #[test]
2739 fn reference_link_offsets() {
2740 let range =
2741 Parser::new("# H1\n[testing][Some reference]\n\n[Some reference]: https://github.com")
2742 .into_offset_iter()
2743 .filter_map(|(ev, range)| match ev {
2744 Event::Start(
2745 Tag::Link {
2746 link_type: LinkType::Reference,
2747 ..
2748 },
2749 ..,
2750 ) => Some(range),
2751 _ => None,
2752 })
2753 .next()
2754 .unwrap();
2755 assert_eq!(5..30, range);
2756 }
2757
2758 #[test]
2759 fn footnote_offsets() {
2760 let range = parser_with_extensions("Testing this[^1] out.\n\n[^1]: Footnote.")
2761 .into_offset_iter()
2762 .filter_map(|(ev, range)| match ev {
2763 Event::FootnoteReference(..) => Some(range),
2764 _ => None,
2765 })
2766 .next()
2767 .unwrap();
2768 assert_eq!(12..16, range);
2769 }
2770
2771 #[test]
2772 fn footnote_offsets_exclamation() {
2773 let mut immediately_before_footnote = None;
2774 let range = parser_with_extensions("Testing this![^1] out.\n\n[^1]: Footnote.")
2775 .into_offset_iter()
2776 .filter_map(|(ev, range)| match ev {
2777 Event::FootnoteReference(..) => Some(range),
2778 _ => {
2779 immediately_before_footnote = Some((ev, range));
2780 None
2781 }
2782 })
2783 .next()
2784 .unwrap();
2785 assert_eq!(13..17, range);
2786 if let (Event::Text(exclamation), range_exclamation) =
2787 immediately_before_footnote.as_ref().unwrap()
2788 {
2789 assert_eq!("!", &exclamation[..]);
2790 assert_eq!(&(12..13), range_exclamation);
2791 } else {
2792 panic!("what came first, then? {immediately_before_footnote:?}");
2793 }
2794 }
2795
2796 #[test]
2797 fn table_offset() {
2798 let markdown = "a\n\nTesting|This|Outtt\n--|:--:|--:\nSome Data|Other data|asdf";
2799 let event_offset = parser_with_extensions(markdown)
2800 .into_offset_iter()
2801 .map(|(_ev, range)| range)
2802 .nth(3)
2803 .unwrap();
2804 let expected_offset = 3..59;
2805 assert_eq!(expected_offset, event_offset);
2806 }
2807
2808 #[test]
2809 fn table_cell_span() {
2810 let markdown = "a|b|c\n--|--|--\na| |c";
2811 let event_offset = parser_with_extensions(markdown)
2812 .into_offset_iter()
2813 .filter_map(|(ev, span)| match ev {
2814 Event::Start(Tag::TableCell) => Some(span),
2815 _ => None,
2816 })
2817 .nth(4)
2818 .unwrap();
2819 let expected_offset_start = "a|b|c\n--|--|--\na|".len();
2820 assert_eq!(
2821 expected_offset_start..(expected_offset_start + 2),
2822 event_offset
2823 );
2824 }
2825
2826 #[test]
2827 fn offset_iter_issue_378() {
2828 let event_offsets: Vec<_> = Parser::new("a [b](c) d")
2829 .into_offset_iter()
2830 .map(|(_ev, range)| range)
2831 .collect();
2832 let expected_offsets = vec![(0..10), (0..2), (2..8), (3..4), (2..8), (8..10), (0..10)];
2833 assert_eq!(expected_offsets, event_offsets);
2834 }
2835
2836 #[test]
2837 fn offset_iter_issue_404() {
2838 let event_offsets: Vec<_> = Parser::new("###\n")
2839 .into_offset_iter()
2840 .map(|(_ev, range)| range)
2841 .collect();
2842 let expected_offsets = vec![(0..4), (0..4)];
2843 assert_eq!(expected_offsets, event_offsets);
2844 }
2845
2846 #[cfg(feature = "html")]
2848 #[test]
2849 fn link_def_at_eof() {
2850 let test_str = "[My site][world]\n\n[world]: https://vincentprouillet.com";
2851 let expected = "<p><a href=\"https://vincentprouillet.com\">My site</a></p>\n";
2852
2853 let mut buf = String::new();
2854 crate::html::push_html(&mut buf, Parser::new(test_str));
2855 assert_eq!(expected, buf);
2856 }
2857
2858 #[cfg(feature = "html")]
2859 #[test]
2860 fn no_footnote_refs_without_option() {
2861 let test_str = "a [^a]\n\n[^a]: yolo";
2862 let expected = "<p>a <a href=\"yolo\">^a</a></p>\n";
2863
2864 let mut buf = String::new();
2865 crate::html::push_html(&mut buf, Parser::new(test_str));
2866 assert_eq!(expected, buf);
2867 }
2868
2869 #[cfg(feature = "html")]
2870 #[test]
2871 fn ref_def_at_eof() {
2872 let test_str = "[test]:\\";
2873 let expected = "";
2874
2875 let mut buf = String::new();
2876 crate::html::push_html(&mut buf, Parser::new(test_str));
2877 assert_eq!(expected, buf);
2878 }
2879
2880 #[cfg(feature = "html")]
2881 #[test]
2882 fn ref_def_cr_lf() {
2883 let test_str = "[a]: /u\r\n\n[a]";
2884 let expected = "<p><a href=\"/u\">a</a></p>\n";
2885
2886 let mut buf = String::new();
2887 crate::html::push_html(&mut buf, Parser::new(test_str));
2888 assert_eq!(expected, buf);
2889 }
2890
2891 #[cfg(feature = "html")]
2892 #[test]
2893 fn no_dest_refdef() {
2894 let test_str = "[a]:";
2895 let expected = "<p>[a]:</p>\n";
2896
2897 let mut buf = String::new();
2898 crate::html::push_html(&mut buf, Parser::new(test_str));
2899 assert_eq!(expected, buf);
2900 }
2901
2902 #[test]
2903 fn broken_links_called_only_once() {
2904 for &(markdown, expected) in &[
2905 ("See also [`g()`][crate::g].", 1),
2906 ("See also [`g()`][crate::g][].", 1),
2907 ("[brokenlink1] some other node [brokenlink2]", 2),
2908 ] {
2909 let mut times_called = 0;
2910 let callback = &mut |_broken_link: BrokenLink| {
2911 times_called += 1;
2912 None
2913 };
2914 let parser =
2915 Parser::new_with_broken_link_callback(markdown, Options::empty(), Some(callback));
2916 for _ in parser {}
2917 assert_eq!(times_called, expected);
2918 }
2919 }
2920
2921 #[test]
2922 fn simple_broken_link_callback() {
2923 let test_str = "This is a link w/o def: [hello][world]";
2924 let mut callback = |broken_link: BrokenLink| {
2925 assert_eq!("world", broken_link.reference.as_ref());
2926 assert_eq!(&test_str[broken_link.span], "[hello][world]");
2927 let url = "YOLO".into();
2928 let title = "SWAG".to_owned().into();
2929 Some((url, title))
2930 };
2931 let parser =
2932 Parser::new_with_broken_link_callback(test_str, Options::empty(), Some(&mut callback));
2933 let mut link_tag_count = 0;
2934 for (typ, url, title, id) in parser.filter_map(|event| match event {
2935 Event::Start(Tag::Link {
2936 link_type,
2937 dest_url,
2938 title,
2939 id,
2940 }) => Some((link_type, dest_url, title, id)),
2941 _ => None,
2942 }) {
2943 link_tag_count += 1;
2944 assert_eq!(typ, LinkType::ReferenceUnknown);
2945 assert_eq!(url.as_ref(), "YOLO");
2946 assert_eq!(title.as_ref(), "SWAG");
2947 assert_eq!(id.as_ref(), "world");
2948 }
2949 assert!(link_tag_count > 0);
2950 }
2951
2952 #[test]
2953 fn code_block_kind_check_fenced() {
2954 let parser = Parser::new("hello\n```test\ntadam\n```");
2955 let mut found = 0;
2956 for (ev, _range) in parser.into_offset_iter() {
2957 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(syntax))) = ev {
2958 assert_eq!(syntax.as_ref(), "test");
2959 found += 1;
2960 }
2961 }
2962 assert_eq!(found, 1);
2963 }
2964
2965 #[test]
2966 fn code_block_kind_check_indented() {
2967 let parser = Parser::new("hello\n\n ```test\n tadam\nhello");
2968 let mut found = 0;
2969 for (ev, _range) in parser.into_offset_iter() {
2970 if let Event::Start(Tag::CodeBlock(CodeBlockKind::Indented)) = ev {
2971 found += 1;
2972 }
2973 }
2974 assert_eq!(found, 1);
2975 }
2976
2977 #[test]
2978 fn ref_defs() {
2979 let input = r###"[a B c]: http://example.com
2980[another]: https://google.com
2981
2982text
2983
2984[final ONE]: http://wikipedia.org
2985"###;
2986 let mut parser = Parser::new(input);
2987
2988 assert!(parser.reference_definitions().get("a b c").is_some());
2989 assert!(parser.reference_definitions().get("nope").is_none());
2990
2991 if let Some(_event) = parser.next() {
2992 let s = "final one".to_owned();
2994 let link_def = parser.reference_definitions().get(&s).unwrap();
2995 let span = &input[link_def.span.clone()];
2996 assert_eq!(span, "[final ONE]: http://wikipedia.org");
2997 }
2998 }
2999
3000 #[test]
3001 #[allow(clippy::extra_unused_lifetimes)]
3002 fn common_lifetime_patterns_allowed<'b>() {
3003 let temporary_str = String::from("xyz");
3004
3005 let mut closure = |link: BrokenLink<'b>| Some(("#".into(), link.reference));
3009
3010 fn function(link: BrokenLink<'_>) -> Option<(CowStr<'_>, CowStr<'_>)> {
3011 Some(("#".into(), link.reference))
3012 }
3013
3014 for _ in Parser::new_with_broken_link_callback(
3015 "static lifetime",
3016 Options::empty(),
3017 Some(&mut closure),
3018 ) {}
3019 for _ in Parser::new_with_broken_link_callback(
3028 "static lifetime",
3029 Options::empty(),
3030 Some(&mut function),
3031 ) {}
3032 for _ in Parser::new_with_broken_link_callback(
3033 &temporary_str,
3034 Options::empty(),
3035 Some(&mut function),
3036 ) {}
3037 }
3038
3039 #[test]
3040 fn inline_html_inside_blockquote() {
3041 let input = "> <foo\n> bar>";
3043 let events: Vec<_> = Parser::new(input).collect();
3044 let expected = [
3045 Event::Start(Tag::BlockQuote(None)),
3046 Event::Start(Tag::Paragraph),
3047 Event::InlineHtml(CowStr::Boxed("<foo\nbar>".to_string().into())),
3048 Event::End(TagEnd::Paragraph),
3049 Event::End(TagEnd::BlockQuote(None)),
3050 ];
3051 assert_eq!(&events, &expected);
3052 }
3053
3054 #[test]
3055 fn wikilink_has_pothole() {
3056 let input = "[[foo]] [[bar|baz]]";
3057 let events: Vec<_> = Parser::new_ext(input, Options::ENABLE_WIKILINKS).collect();
3058 let expected = [
3059 Event::Start(Tag::Paragraph),
3060 Event::Start(Tag::Link {
3061 link_type: LinkType::WikiLink { has_pothole: false },
3062 dest_url: CowStr::Borrowed("foo"),
3063 title: CowStr::Borrowed(""),
3064 id: CowStr::Borrowed(""),
3065 }),
3066 Event::Text(CowStr::Borrowed("foo")),
3067 Event::End(TagEnd::Link),
3068 Event::Text(CowStr::Borrowed(" ")),
3069 Event::Start(Tag::Link {
3070 link_type: LinkType::WikiLink { has_pothole: true },
3071 dest_url: CowStr::Borrowed("bar"),
3072 title: CowStr::Borrowed(""),
3073 id: CowStr::Borrowed(""),
3074 }),
3075 Event::Text(CowStr::Borrowed("baz")),
3076 Event::End(TagEnd::Link),
3077 Event::End(TagEnd::Paragraph),
3078 ];
3079 assert_eq!(&events, &expected);
3080 }
3081
3082 fn mdx_parser(text: &str) -> Parser<'_> {
3083 Parser::new_ext(text, Options::ENABLE_MDX)
3084 }
3085
3086 #[test]
3087 fn mdx_esm_import() {
3088 let events: Vec<_> = mdx_parser("import {Chart} from './chart.js'\n").collect();
3089 assert_eq!(events.len(), 1);
3090 assert!(matches!(&events[0], Event::MdxEsm(s) if s.contains("import")));
3091 }
3092
3093 #[test]
3094 fn mdx_esm_export() {
3095 let events: Vec<_> = mdx_parser("export const meta = {}\n").collect();
3096 assert_eq!(events.len(), 1);
3097 assert!(matches!(&events[0], Event::MdxEsm(s) if s.contains("export")));
3098 }
3099
3100 #[test]
3101 fn mdx_flow_expression() {
3102 let events: Vec<_> = mdx_parser("{1 + 1}\n").collect();
3103 assert_eq!(events.len(), 1);
3104 assert!(matches!(&events[0], Event::MdxFlowExpression(s) if s.as_ref() == "1 + 1"));
3105 }
3106
3107 #[test]
3108 fn mdx_jsx_flow_self_closing() {
3109 let events: Vec<_> = mdx_parser("<Chart values={[1,2,3]} />\n").collect();
3110 assert!(!events.is_empty());
3111 assert!(
3112 matches!(&events[0], Event::Start(Tag::MdxJsxFlowElement(s)) if s.contains("Chart"))
3113 );
3114 }
3115
3116 #[test]
3117 fn mdx_jsx_flow_fragment() {
3118 let events: Vec<_> = mdx_parser("<>\n").collect();
3119 assert!(!events.is_empty());
3120 assert!(matches!(
3121 &events[0],
3122 Event::Start(Tag::MdxJsxFlowElement(_))
3123 ));
3124 }
3125
3126 #[test]
3127 fn mdx_inline_expression() {
3128 let events: Vec<_> = mdx_parser("hello {name} world\n").collect();
3129 let has_expr = events
3130 .iter()
3131 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "name"));
3132 assert!(
3133 has_expr,
3134 "Expected inline MDX expression, got: {:?}",
3135 events
3136 );
3137 }
3138
3139 #[test]
3140 fn mdx_inline_jsx() {
3141 let events: Vec<_> = mdx_parser("hello <Badge /> world\n").collect();
3142 let has_jsx = events
3143 .iter()
3144 .any(|e| matches!(e, Event::Start(Tag::MdxJsxTextElement(s)) if s.contains("Badge")));
3145 assert!(has_jsx, "Expected inline MDX JSX, got: {:?}", events);
3146 }
3147
3148 #[test]
3149 fn mdx_all_tags_are_jsx() {
3150 let events: Vec<_> = mdx_parser("hello <em>world</em>\n").collect();
3152 let has_jsx = events
3153 .iter()
3154 .any(|e| matches!(e, Event::Start(Tag::MdxJsxTextElement(_))));
3155 assert!(has_jsx, "In MDX mode, <em> should be JSX: {:?}", events);
3156 }
3157
3158 #[test]
3159 fn mdx_does_not_interfere_without_flag() {
3160 let events: Vec<_> = Parser::new("import foo from 'bar'\n").collect();
3162 assert!(events
3164 .iter()
3165 .any(|e| matches!(e, Event::Start(Tag::Paragraph))));
3166 }
3167
3168 #[test]
3169 fn mdx_expression_in_heading() {
3170 let events: Vec<_> = mdx_parser("# {title}\n").collect();
3171 let has_heading = events
3172 .iter()
3173 .any(|e| matches!(e, Event::Start(Tag::Heading { .. })));
3174 assert!(has_heading, "Should have a heading");
3175 let has_expr = events
3176 .iter()
3177 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "title"));
3178 assert!(
3179 has_expr,
3180 "Heading should contain MdxTextExpression, got: {:?}",
3181 events
3182 );
3183 }
3184
3185 #[test]
3186 fn mdx_expression_mixed_text_in_heading() {
3187 let events: Vec<_> = mdx_parser("## Hello {name}\n").collect();
3188 let has_text = events
3189 .iter()
3190 .any(|e| matches!(e, Event::Text(s) if s.contains("Hello")));
3191 let has_expr = events
3192 .iter()
3193 .any(|e| matches!(e, Event::MdxTextExpression(s) if s.as_ref() == "name"));
3194 assert!(has_text, "Should have text, got: {:?}", events);
3195 assert!(has_expr, "Should have expression, got: {:?}", events);
3196 }
3197}