Repository: raphlinus/pulldown-cmark (rust)

Issue:
[fuzz result] `![^x]` footnote reference eats the `!`
Consider this markdown:

```markdown
![^1]

[^1]: foo
```

Naturally, it should render as ![^1]. The `!` should show up, but it doesn't in pulldown-cmark.

[^1]: foo

<details>

Events from pulldown-cmark:

```rust
"![^+\\&~p+]\n[^+\\&~p+]:\n" -> [
  Start(Paragraph)
    FootnoteReference(Boxed("1"))
    Start(FootnoteDefinition(Boxed("1")))
    End(FootnoteDefinition)
  End(Paragraph)
]
```

Events from pandoc:

```rust
"![^+\\&~p+]\n[^+\\&~p+]:\n" -> [
  Start(Paragraph)
    Text(Boxed("!"))
    FootnoteReference(Boxed("1"))
    Start(FootnoteDefinition(Boxed("1")))
    End(FootnoteDefinition)
  End(Paragraph)
]
```

</details>


Pull request:
Don't eat `!` on image-like footnote
Fixes #817

Patch:
diff --git a/specs/regression.txt b/specs/regression.txt
index 5187b99..b2bc45b 100644
--- a/specs/regression.txt
+++ b/specs/regression.txt
@@ -2170,6 +2170,7 @@ ISSUE 813
 -|- *</p>
 ````````````````````````````````
 
+
 ISSUE 715
 ```````````````````````````````` example
  A paragraph is a paragraph and spaces must be removed. 
@@ -2179,3 +2180,27 @@ ISSUE 715
 <p>A paragraph is a paragraph and spaces must be removed.</p>
 <p>Another paragraph whose spaces must be removed.</p>
 ````````````````````````````````
+
+ISSUE 817
+
+```````````````````````````````` example
+![^1]
+
+[^1]: foo
+.
+<p>!<sup class="footnote-reference"><a href="#1">1</a></sup></p>
+<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
+<p>foo</p>
+</div>
+````````````````````````````````
+
+```````````````````````````````` example
+First ![^1][] Second
+
+[^1]: foo
+.
+<p>First !<sup class="footnote-reference"><a href="#1">1</a></sup>[] Second</p>
+<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
+<p>foo</p>
+</div>
+````````````````````````````````
diff --git a/src/parse.rs b/src/parse.rs
index 657bc60..d9dd8ef 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -514,15 +514,26 @@ impl<'input, F: BrokenLinkCallback<'input>> Parser<'input, F> {
                                 if !self.options.has_gfm_footnotes()
                                     || self.allocs.footdefs.contains(&self.allocs.cows[footref.0])
                                 {
+                                    // If this came from a MaybeImage, then the `!` prefix
+                                    // isn't part of the footnote reference.
+                                    let footnote_ix = if tos.ty == LinkStackTy::Image {
+                                        self.tree[tos.node].next = Some(cur_ix);
+                                        self.tree[tos.node].child = None;
+                                        self.tree[tos.node].item.body =
+                                            ItemBody::SynthesizeChar('!');
+                                        cur_ix
+                                    } else {
+                                        tos.node
+                                    };
                                     // use `next` instead of `node_after_link` because
                                     // node_after_link is calculated for a [collapsed][] link,
                                     // which footnotes don't support.
-                                    self.tree[tos.node].next = next;
-                                    self.tree[tos.node].child = None;
-                                    self.tree[tos.node].item.body =
+                                    self.tree[footnote_ix].next = next;
+                                    self.tree[footnote_ix].child = None;
+                                    self.tree[footnote_ix].item.body =
                                         ItemBody::FootnoteReference(footref);
-                                    self.tree[tos.node].item.end = end;
-                                    prev = Some(tos.node);
+                                    self.tree[footnote_ix].item.end = end;
+                                    prev = Some(footnote_ix);
                                     cur = next;
                                     self.link_stack.clear();
                                     continue;


Test patch:
diff --git a/tests/suite/regression.rs b/tests/suite/regression.rs
index 45eceab..4324178 100644
--- a/tests/suite/regression.rs
+++ b/tests/suite/regression.rs
@@ -2571,3 +2571,33 @@ fn regression_test_159() {
 
     test_markdown_html(original, expected, false, false, false);
 }
+  
+#[test]
+fn regression_test_160() {
+    let original = r##"![^1]
+
+[^1]: foo
+"##;
+    let expected = r##"<p>!<sup class="footnote-reference"><a href="#1">1</a></sup></p>
+<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
+<p>foo</p>
+</div>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn regression_test_161() {
+    let original = r##"First ![^1][] Second
+
+[^1]: foo
+"##;
+    let expected = r##"<p>First !<sup class="footnote-reference"><a href="#1">1</a></sup>[] Second</p>
+<div class="footnote-definition" id="1"><sup class="footnote-definition-label">1</sup>
+<p>foo</p>
+</div>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}