Repository: raphlinus/pulldown-cmark (rust)

Issue:
[fuzz result] blank line after blockquote with unclosed code block doesn't split
[The spec](https://spec.commonmark.org/0.30/#example-242):

> A blank line always separates block quotes:

Events from pulldown-cmark:

```rust
">~~~\n\n>\n" -> [
  Start(BlockQuote)
    Start(CodeBlock(Fenced(Borrowed(""))))
    End(CodeBlock)
  End(BlockQuote)
]
```

Events from pandoc:

```rust
">~~~\n\n>\n" -> [
  Start(BlockQuote)
    Start(CodeBlock(Fenced(Borrowed(""))))
    End(CodeBlock)
  End(BlockQuote)
  Start(BlockQuote)
  End(BlockQuote)
]
```

Events from commonmark.js:

```rust
">~~~\n\n>\n" -> [
  Start(BlockQuote)
    Start(CodeBlock(Fenced(Borrowed(""))))
    End(CodeBlock)
  End(BlockQuote)
  Start(BlockQuote)
  End(BlockQuote)
]
```


Pull request description:
Don't eat blank line when parsing unclosed fenced code
Fixes #795

Patch:
diff --git a/specs/regression.txt b/specs/regression.txt
index 9b67e58..e3c3e2f 100644
--- a/specs/regression.txt
+++ b/specs/regression.txt
@@ -1871,6 +1871,7 @@ List can interrupt the paragraph at the start of a link definition if it starts
 <p>List can interrupt the paragraph at the start of a link definition if it starts with three spaces, but not four.</p>
 ````````````````````````````````
 
+
 ISSUE 789
 
 ```````````````````````````````` example
@@ -1940,6 +1941,22 @@ second</a></p>
 <h2>"</h2>
 <p>"</p>
 <p><a href="https://example.com">first</a></p>
+````````````````````````````````
+
+ISSUE 795
+
+```````````````````````````````` example
+> ```
+> code
+
+> text
+.
+<blockquote>
+<pre><code>code
+</code></pre>
+</blockquote>
+<blockquote>
+<p>text</p>
 </blockquote>
 ````````````````````````````````
 
@@ -2022,3 +2039,22 @@ a]: https://example.com
 .
 <p><a href="https://example.com">a</a> <a href="https://example.com">b</a></p>
 ````````````````````````````````
+
+ISSUE 795
+
+```````````````````````````````` example
+> > ```
+> > code
+>
+> > text
+.
+<blockquote>
+<blockquote>
+<pre><code>code
+</code></pre>
+</blockquote>
+<blockquote>
+<p>text</p>
+</blockquote>
+</blockquote>
+````````````````````````````````
\ No newline at end of file
diff --git a/src/firstpass.rs b/src/firstpass.rs
index 18ce268..b3d59e9 100644
--- a/src/firstpass.rs
+++ b/src/firstpass.rs
@@ -1036,7 +1036,10 @@ impl<'a, 'b> FirstPass<'a, 'b> {
                 self.options.has_gfm_footnotes(),
             );
             if n_containers < self.tree.spine_len() {
-                break;
+                // this line will get parsed again as not being part of the code
+                // if it's blank, it should be parsed as a blank line
+                self.pop(ix);
+                return ix;
             }
             line_start.scan_space(indent);
             let mut close_line_start = line_start.clone();
@@ -1045,7 +1048,9 @@ impl<'a, 'b> FirstPass<'a, 'b> {
                 if let Some(n) = scan_closing_code_fence(&bytes[close_ix..], fence_ch, n_fence_char)
                 {
                     ix = close_ix + n;
-                    break;
+                    self.pop(ix);
+                    // try to read trailing whitespace or it will register as a completely blank line
+                    return ix + scan_blank_line(&bytes[ix..]).unwrap_or(0);
                 }
             }
             let remaining_space = line_start.remaining_space();
@@ -1054,11 +1059,6 @@ impl<'a, 'b> FirstPass<'a, 'b> {
             self.append_code_text(remaining_space, ix, next_ix);
             ix = next_ix;
         }
-
-        self.pop(ix);
-
-        // try to read trailing whitespace or it will register as a completely blank line
-        ix + scan_blank_line(&bytes[ix..]).unwrap_or(0)
     }
 
     fn parse_metadata_block(


Test patch:
diff --git a/tests/suite/regression.rs b/tests/suite/regression.rs
index 66fb308..23a161d 100644
--- a/tests/suite/regression.rs
+++ b/tests/suite/regression.rs
@@ -2219,7 +2219,6 @@ second]
     test_markdown_html(original, expected, false, false, false);
 }
 
-  
 #[test]
 fn regression_test_139() {
     let original = r##"[first
@@ -2285,7 +2284,6 @@ fn regression_test_142() {
 <h2>"</h2>
 <p>"</p>
 <p><a href="https://example.com">first</a></p>
-</blockquote>
 "##;
 
     test_markdown_html(original, expected, false, false, false);
@@ -2293,6 +2291,25 @@ fn regression_test_142() {
 
 #[test]
 fn regression_test_143() {
+    let original = r##"> ```
+> code
+
+> text
+"##;
+    let expected = r##"<blockquote>
+<pre><code>code
+</code></pre>
+</blockquote>
+<blockquote>
+<p>text</p>
+</blockquote>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn regression_test_144() {
     let original = r##"> [first]: https://example.com
 > "
 >     -
@@ -2311,7 +2328,7 @@ fn regression_test_143() {
 }
 
 #[test]
-fn regression_test_144() {
+fn regression_test_145() {
     let original = r##"[first]: https://example.com
 "
 \
@@ -2328,7 +2345,7 @@ fn regression_test_144() {
 }
 
 #[test]
-fn regression_test_145() {
+fn regression_test_146() {
     let original = r##"[first]: https://example.com
 "
 \
@@ -2347,18 +2364,18 @@ fn regression_test_145() {
 }
 
 #[test]
-fn regression_test_146() {
+fn regression_test_147() {
     let original = r##"[foo]: https://example.com
 '[foo]'bar
 "##;
     let expected = r##"<p>'<a href="https://example.com">foo</a>'bar</p>
 "##;
 
-  test_markdown_html(original, expected, false, false, false);
+    test_markdown_html(original, expected, false, false, false);
 }
-  
+
 #[test]
-fn regression_test_147() {
+fn regression_test_148() {
     let original = r##"- [foo]: https://example.com
 '[foo]'
 [foo]
@@ -2369,11 +2386,34 @@ fn regression_test_147() {
 </li>
 </ul>
 "##;
+
     test_markdown_html(original, expected, false, false, false);
 }
 
 #[test]
-fn regression_test_148() {
+fn regression_test_149() {
+    let original = r##"> > ```
+> > code
+>
+> > text
+"##;
+    let expected = r##"<blockquote>
+<blockquote>
+<pre><code>code
+</code></pre>
+</blockquote>
+<blockquote>
+<p>text</p>
+</blockquote>
+</blockquote>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+
+#[test]
+fn regression_test_150() {
       let original = r##"[
 a]: https://example.com
 
@@ -2386,4 +2426,4 @@ a]: https://example.com
 "##;
 
     test_markdown_html(original, expected, false, false, false);
-}
\ No newline at end of file
+}