Repository: pulldown-cmark/pulldown-cmark
Pull request #962 (2024-09-23 17:45:12)

Complete the list of block item bodies
This fixes a few remaining bugs in tight lists, where you can wind up with block items nested within inlines.

The ordering of ItemBody items is designed to have a tight ASM [in godbolt](https://rust.godbolt.org/z/xza85GfG6).


Patch:
diff --git a/pulldown-cmark/specs/regression.txt b/pulldown-cmark/specs/regression.txt
index 362d181..44442e6 100644
--- a/pulldown-cmark/specs/regression.txt
+++ b/pulldown-cmark/specs/regression.txt
@@ -2713,3 +2713,16 @@ Some preamble <code>foobar_raz</code>, not <code>barfoo_raz</code></dt>
 <p>This should fix:</p>
 <p>&gt; Something is wrong!</p>
 ````````````````````````````````
+
+```````````````````````````````` example
+- Item definition [it
+  ```rust
+  ```
+  stuff](https://example.com)
+.
+<ul>
+<li>Item definition [it
+<pre><code class="language-rust"></code></pre>
+stuff](https://example.com)</li>
+</ul>
+````````````````````````````````
diff --git a/pulldown-cmark/src/parse.rs b/pulldown-cmark/src/parse.rs
index 9a6728f..161dc62 100644
--- a/pulldown-cmark/src/parse.rs
+++ b/pulldown-cmark/src/parse.rs
@@ -54,14 +54,6 @@ pub(crate) struct Item {
 
 #[derive(Debug, PartialEq, Clone, Copy, Default)]
 pub(crate) enum ItemBody {
-    Paragraph,
-    Text {
-        backslash_escaped: bool,
-    },
-    SoftBreak,
-    // true = is backlash
-    HardBreak(bool),
-
     // These are possible inline items, need to be resolved in second pass.
 
     // repeats, can_open, can_close
@@ -88,19 +80,33 @@ pub(crate) enum ItemBody {
     FootnoteReference(CowIndex),
     TaskListMarker(bool), // true for checked
 
+    // These are also inline items.
+    InlineHtml,
+    OwnedInlineHtml(CowIndex),
+    SynthesizeText(CowIndex),
+    SynthesizeChar(char),
+    Html,
+    Text {
+        backslash_escaped: bool,
+    },
+    SoftBreak,
+    // true = is backlash
+    HardBreak(bool),
+
+    // Dummy node at the top of the tree - should not be used otherwise!
+    #[default]
+    Root,
+
+    // These are block items.
+    Paragraph,
     Rule,
     Heading(HeadingLevel, Option<HeadingIndex>), // heading level
     FencedCodeBlock(CowIndex),
     IndentCodeBlock,
     HtmlBlock,
-    InlineHtml,
-    Html,
-    OwnedInlineHtml(CowIndex),
     BlockQuote(Option<BlockQuoteKind>),
     List(bool, u8, u64), // is_tight, list character, list start index
     ListItem(usize),     // indent level
-    SynthesizeText(CowIndex),
-    SynthesizeChar(char),
     FootnoteDefinition(CowIndex),
     MetadataBlock(MetadataBlockKind),
 
@@ -117,42 +123,57 @@ pub(crate) enum ItemBody {
     TableHead,
     TableRow,
     TableCell,
-
-    // Dummy node at the top of the tree - should not be used otherwise!
-    #[default]
-    Root,
 }
 
 impl ItemBody {
-    fn is_inline(&self) -> bool {
+    fn is_maybe_inline(&self) -> bool {
+        use ItemBody::*;
         matches!(
             *self,
-            ItemBody::MaybeEmphasis(..)
-                | ItemBody::MaybeMath(..)
-                | ItemBody::MaybeSmartQuote(..)
-                | ItemBody::MaybeHtml
-                | ItemBody::MaybeCode(..)
-                | ItemBody::MaybeLinkOpen
-                | ItemBody::MaybeLinkClose(..)
-                | ItemBody::MaybeImage
+            MaybeEmphasis(..)
+                | MaybeMath(..)
+                | MaybeSmartQuote(..)
+                | MaybeCode(..)
+                | MaybeHtml
+                | MaybeLinkOpen
+                | MaybeLinkClose(..)
+                | MaybeImage
         )
     }
-    fn is_block(&self) -> bool {
+    fn is_inline(&self) -> bool {
+        use ItemBody::*;
         matches!(
             *self,
-            ItemBody::Paragraph
-                | ItemBody::BlockQuote(..)
-                | ItemBody::List(..)
-                | ItemBody::ListItem(..)
-                | ItemBody::HtmlBlock
-                | ItemBody::Table(..)
-                | ItemBody::TableHead
-                | ItemBody::TableRow
-                | ItemBody::TableCell
-                | ItemBody::Heading(..)
-                | ItemBody::Rule
+            MaybeEmphasis(..)
+                | MaybeMath(..)
+                | MaybeSmartQuote(..)
+                | MaybeCode(..)
+                | MaybeHtml
+                | MaybeLinkOpen
+                | MaybeLinkClose(..)
+                | MaybeImage
+                | Emphasis
+                | Strong
+                | Strikethrough
+                | Math(..)
+                | Code(..)
+                | Link(..)
+                | Image(..)
+                | FootnoteReference(..)
+                | TaskListMarker(..)
+                | InlineHtml
+                | OwnedInlineHtml(..)
+                | SynthesizeText(..)
+                | SynthesizeChar(..)
+                | Html
+                | Text { .. }
+                | SoftBreak
+                | HardBreak(..)
         )
     }
+    fn is_block(&self) -> bool {
+        !self.is_inline()
+    }
 }
 
 #[derive(Debug)]
@@ -2027,7 +2048,7 @@ impl<'a, F: BrokenLinkCallback<'a>> Iterator for OffsetIter<'a, F> {
                 Some((Event::End(tag_end), span))
             }
             Some(cur_ix) => {
-                if self.inner.tree[cur_ix].item.body.is_inline() {
+                if self.inner.tree[cur_ix].item.body.is_maybe_inline() {
                     self.inner.handle_inline();
                 }
 
@@ -2177,7 +2198,7 @@ impl<'a, F: BrokenLinkCallback<'a>> Iterator for Parser<'a, F> {
                 Some(Event::End(tag_end))
             }
             Some(cur_ix) => {
-                if self.tree[cur_ix].item.body.is_inline() {
+                if self.tree[cur_ix].item.body.is_maybe_inline() {
                     self.handle_inline();
                 }
 


Test patch:
diff --git a/pulldown-cmark/tests/suite/regression.rs b/pulldown-cmark/tests/suite/regression.rs
index 3611992..9a8b1aa 100644
--- a/pulldown-cmark/tests/suite/regression.rs
+++ b/pulldown-cmark/tests/suite/regression.rs
@@ -3236,3 +3236,20 @@ Some preamble <code>foobar_raz</code>, not <code>barfoo_raz</code></dt>
 
     test_markdown_html(original, expected, false, false, false);
 }
+
+#[test]
+fn regression_test_205() {
+    let original = r##"- Item definition [it
+  ```rust
+  ```
+  stuff](https://example.com)
+"##;
+    let expected = r##"<ul>
+<li>Item definition [it
+<pre><code class="language-rust"></code></pre>
+stuff](https://example.com)</li>
+</ul>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}