Repository: raphlinus/pulldown-cmark (rust)

Issue:
] inside inline code inside link text gets parsed incorrectly if corresponding reference exists
For this input:
```markdown
[`]: xx:

[`]`]
```

It should be parsed like this, since there is no link reference `` `]` ``
```html
<p>[<code>]</code>]</p>
```

But it's currently parsed as a link:
```html
<p><a href="xx:"><code>]</code></a></p>
```

From the [commonmark spec](https://spec.commonmark.org/0.30/#example-341):
> Code span backticks have higher precedence than any other inline constructs except HTML tags and autolinks.

---

Additionally, the `OffsetIter`'s range of this link is only the front part up to the `]`, i.e. the this part:
```text
[`]`]
^^^
```

This is currently causing rustdoc nightly to panic in some cases, see https://github.com/rust-lang/rust/issues/111117.

<details>
<summary>demo program that shows this behavior</summary>

```rust
use pulldown_cmark::{Parser, html};

/// [`]: xx:
///
/// [`]`]
fn main() {
    let text = "\
        [`]: xx:\n\
        \n\
        [`]`]\n\
    ";

    for (event, span) in Parser::new(text).into_offset_iter() {
        println!("{:?}: {:?}", event, &text[span]);
    }

    let mut parsed = String::new();
    html::push_html(&mut parsed, Parser::new(text));
    println!("{parsed}");
}
```

output:
```text
Start(Paragraph): "[`]`]\n"
Start(Link(Shortcut, Borrowed("xx:"), Borrowed(""))): "[`]"
Code(Borrowed("]")): "`]`"
End(Link(Shortcut, Borrowed("xx:"), Borrowed(""))): "[`]"
End(Paragraph): "[`]`]\n"
<p><a href="xx:"><code>]</code></a></p>
```

This shouldn't be parsed as a link in the first place, but even if it is, the range should be `` "[`]`]" ``, not `` "[`]" ``.

If you run `cargo +nightly doc` it crashes.

</details>

Pull request description:
Fix parser bug where link label gets broken by ] in code span
```text
 [before `]` after]
 ^        ^       ^
 |        |       |
(A)      (B)     (C)
```

When the parser encounters the `]` at (C), it tries to parse a [link label](https://spec.commonmark.org/0.30/#link-label) starting from the previous `[` at (A) until it encounters the first `]` at (B). This is fine, because
> A link label begins with a left bracket (`[`) and ends with the first right bracket (`]`) that is not backslash-escaped.

However, due to
> Code span backticks have higher precedence than any other inline constructs except HTML tags and autolinks.

the code span `` `]` `` takes precedence over the link closing.

Since a link label must not contain a `]` that is not immediately preceded by a `\`, the above must not be parsed as a link label.

This patch fixes #642, a bug in the parser, where the range (A)...(C) was previously parsed as a shortcut link with the label (A)...(B) if the text between (A) and (B) [here: `` before ` ``] is a valid link reference. We do this by simply comparing the found end (B) and supposed end (C) to ensure that the link label closed by (C) actually ends at (C).

---

As a bonus I've included a `#[derive(Debug)]` that helped me debug this and removed an unneeded `&`. But I can drop the unrelated changes if you don't want them.

Patch:
diff --git a/specs/regression.txt b/specs/regression.txt
index 2c388a4..fbd529d 100644
--- a/specs/regression.txt
+++ b/specs/regression.txt
@@ -874,3 +874,13 @@ ISSUE 567
 </li>
 </ul>
 ````````````````````````````````
+
+ISSUE 642
+
+```````````````````````````````` example
+[`]: xx:
+
+[`]`]
+.
+<p>[<code>]</code>]</p>
+````````````````````````````````
diff --git a/src/linklabel.rs b/src/linklabel.rs
index 23b4b82..19c4964 100644
--- a/src/linklabel.rs
+++ b/src/linklabel.rs
@@ -25,6 +25,7 @@ use unicase::UniCase;
 use crate::scanners::{is_ascii_whitespace, scan_eol};
 use crate::strings::CowStr;
 
+#[derive(Debug)]
 pub(crate) enum ReferenceLabel<'a> {
     Link(CowStr<'a>),
     Footnote(CowStr<'a>),
diff --git a/src/parse.rs b/src/parse.rs
index 1592684..59eb076 100644
--- a/src/parse.rs
+++ b/src/parse.rs
@@ -438,12 +438,14 @@ impl<'input, 'callback> Parser<'input, 'callback> {
                                 RefScan::Collapsed(..) | RefScan::Failed => {
                                     // No label? maybe it is a shortcut reference
                                     let label_start = self.tree[tos.node].item.end - 1;
+                                    let label_end = self.tree[cur_ix].item.end;
                                     scan_link_label(
                                         &self.tree,
-                                        &self.text[label_start..self.tree[cur_ix].item.end],
+                                        &self.text[label_start..label_end],
                                         self.options.contains(Options::ENABLE_FOOTNOTES),
                                     )
                                     .map(|(ix, label)| (label, label_start + ix))
+                                    .filter(|(_, end)| *end == label_end)
                                 }
                             };
 
@@ -1072,7 +1074,7 @@ fn scan_link_label<'text, 'tree>(
     text: &'text str,
     allow_footnote_refs: bool,
 ) -> Option<(usize, ReferenceLabel<'text>)> {
-    let bytes = &text.as_bytes();
+    let bytes = text.as_bytes();
     if bytes.len() < 2 || bytes[0] != b'[' {
         return None;
     }


Test patch:
diff --git a/tests/suite/regression.rs b/tests/suite/regression.rs
index b3457e7..9a4790a 100644
--- a/tests/suite/regression.rs
+++ b/tests/suite/regression.rs
@@ -1019,3 +1019,15 @@ fn regression_test_70() {
 
     test_markdown_html(original, expected, false);
 }
+
+#[test]
+fn regression_test_71() {
+    let original = r##"[`]: xx:
+
+[`]`]
+"##;
+    let expected = r##"<p>[<code>]</code>]</p>
+"##;
+
+    test_markdown_html(original, expected, false);
+}