Repository: pulldown-cmark/pulldown-cmark
Pull request #872 (2024-03-25 17:51:08)

[0.11] Adjust strikethrough flanking rule to better fit Rustdoc Crater run
This is https://github.com/pulldown-cmark/pulldown-cmark/pull/864, but forward-ported to 0.11.


Patch:
diff --git a/pulldown-cmark/specs/strikethrough.txt b/pulldown-cmark/specs/strikethrough.txt
new file mode 100644
index 0000000..73c8df6
--- /dev/null
+++ b/pulldown-cmark/specs/strikethrough.txt
@@ -0,0 +1,125 @@
+This is an extension of gfm_strikethrough.txt. Some of these tests are also pulled from commonmark-hs.
+
+# Two tildes
+
+Basic strikethrough is between two tildes:
+
+```````````````````````````````` example
+~~This is *stricken out*~~
+.
+<p><del>This is <em>stricken out</em></del></p>
+````````````````````````````````
+
+Backslash escapes:
+
+```````````````````````````````` example
+~~This is \~\~stricken~~
+.
+<p><del>This is ~~stricken</del></p>
+````````````````````````````````
+
+Intraword strikeout:
+
+```````````````````````````````` example
+This~~is~~stricken
+.
+<p>This<del>is</del>stricken</p>
+````````````````````````````````
+
+```````````````````````````````` example
+~~This~~is~~stricken~~
+.
+<p><del>This</del>is<del>stricken</del></p>
+````````````````````````````````
+
+Punctuation is ignored for purposes of determining
+flankingness on two tildes:
+
+```````````````````````````````` example
+Here I strike out an exclamation point~~!~~.
+.
+<p>Here I strike out an exclamation point<del>!</del>.</p>
+````````````````````````````````
+
+# One tilde
+
+One tilde—and this is where we differ from commonmark-hs—is allowed in certain situations:
+
+```````````````````````````````` example
+~This is stricken out~
+.
+<p><del>This is stricken out</del></p>
+````````````````````````````````
+
+Backslash escapes:
+
+```````````````````````````````` example
+~This is \~stricken~
+.
+<p><del>This is ~stricken</del></p>
+````````````````````````````````
+
+Intraword strikeout requires two tildes:
+
+```````````````````````````````` example
+This~is~nothing
+.
+<p>This~is~nothing</p>
+````````````````````````````````
+
+```````````````````````````````` example
+~This~is~nothing~
+.
+<p><del>This~is~nothing</del></p>
+````````````````````````````````
+
+Punctuation is used for purposes of determining
+flankingness:
+
+```````````````````````````````` example
+Here I fail to strike out an exclamation point~!~.
+.
+<p>Here I fail to strike out an exclamation point~!~.</p>
+````````````````````````````````
+
+Tilde runs can't mix.
+
+```````````````````````````````` example
+Here I fail to strike out a tilde ~~~.
+.
+<p>Here I fail to strike out a tilde ~~~.</p>
+````````````````````````````````
+
+```````````````````````````````` example
+Here I fail to match up ~~tildes~.
+.
+<p>Here I fail to match up ~~tildes~.</p>
+````````````````````````````````
+
+```````````````````````````````` example
+Here I fail to match up ~tildes~~.
+.
+<p>Here I fail to match up ~tildes~~.</p>
+````````````````````````````````
+
+Double tildes are allowed to contain single tildes, and the other way around:
+
+```````````````````````````````` example
+~~This ~is stricken.~~
+.
+<p><del>This ~is stricken.</del></p>
+````````````````````````````````
+
+```````````````````````````````` example
+~This ~~is stricken.~
+.
+<p><del>This ~~is stricken.</del></p>
+````````````````````````````````
+
+The first one wins.
+
+```````````````````````````````` example
+~This ~~is stricken~ but this is not~~
+.
+<p><del>This ~~is stricken</del> but this is not~~</p>
+````````````````````````````````
diff --git a/pulldown-cmark/src/firstpass.rs b/pulldown-cmark/src/firstpass.rs
index ea4ca79..67af8b5 100644
--- a/pulldown-cmark/src/firstpass.rs
+++ b/pulldown-cmark/src/firstpass.rs
@@ -2138,12 +2138,17 @@ fn delim_run_can_open(
         }
     }
     let delim = suffix.chars().next().unwrap();
-    // `*` and `~~` can be intraword, `_` cannot
-    if (delim == '*' || delim == '~') && !is_punctuation(next_char) {
+    // `*` and `~~` can be intraword, `_` and `~` cannot
+    if delim == '*' && !is_punctuation(next_char) {
+        return true;
+    }
+    if delim == '~' && run_len > 1 {
         return true;
     }
-
     let prev_char = s[..ix].chars().last().unwrap();
+    if delim == '~' && prev_char == '~' && !is_punctuation(next_char) {
+        return true;
+    }
 
     prev_char.is_whitespace()
         || is_punctuation(prev_char) && (delim != '\'' || ![']', ')'].contains(&prev_char))
@@ -2180,8 +2185,11 @@ fn delim_run_can_close(
         }
     }
     let delim = suffix.chars().next().unwrap();
-    // `*` and `~~` can be intraword, `_` cannot
-    if (delim == '*' || delim == '~') && !is_punctuation(prev_char) {
+    // `*` and `~~` can be intraword, `_` and `~` cannot
+    if (delim == '*' || (delim == '~' && run_len > 1)) && !is_punctuation(prev_char) {
+        return true;
+    }
+    if delim == '~' && prev_char == '~' {
         return true;
     }
 
diff --git a/pulldown-cmark/src/parse.rs b/pulldown-cmark/src/parse.rs
index a04a946..1423936 100644
--- a/pulldown-cmark/src/parse.rs
+++ b/pulldown-cmark/src/parse.rs
@@ -1455,6 +1455,9 @@ impl InlineStack {
             .cloned()
             .enumerate()
             .rfind(|(_, el)| {
+                if c == b'~' && run_length != el.run_length {
+                    return false;
+                }
                 el.c == c
                     && (!both && !el.both
                         || (run_length + el.run_length) % 3 != 0


Test patch:
diff --git a/pulldown-cmark/tests/suite/mod.rs b/pulldown-cmark/tests/suite/mod.rs
index 045a98e..4339283 100644
--- a/pulldown-cmark/tests/suite/mod.rs
+++ b/pulldown-cmark/tests/suite/mod.rs
@@ -14,4 +14,5 @@ mod old_footnotes;
 mod regression;
 mod smart_punct;
 mod spec;
+mod strikethrough;
 mod table;
diff --git a/pulldown-cmark/tests/suite/strikethrough.rs b/pulldown-cmark/tests/suite/strikethrough.rs
new file mode 100644
index 0000000..a8799a3
--- /dev/null
+++ b/pulldown-cmark/tests/suite/strikethrough.rs
@@ -0,0 +1,164 @@
+// This file is auto-generated by the build script
+// Please, do not modify it manually
+
+use super::test_markdown_html;
+
+#[test]
+fn strikethrough_test_1() {
+    let original = r##"~~This is *stricken out*~~
+"##;
+    let expected = r##"<p><del>This is <em>stricken out</em></del></p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_2() {
+    let original = r##"~~This is \~\~stricken~~
+"##;
+    let expected = r##"<p><del>This is ~~stricken</del></p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_3() {
+    let original = r##"This~~is~~stricken
+"##;
+    let expected = r##"<p>This<del>is</del>stricken</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_4() {
+    let original = r##"~~This~~is~~stricken~~
+"##;
+    let expected = r##"<p><del>This</del>is<del>stricken</del></p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_5() {
+    let original = r##"Here I strike out an exclamation point~~!~~.
+"##;
+    let expected = r##"<p>Here I strike out an exclamation point<del>!</del>.</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_6() {
+    let original = r##"~This is stricken out~
+"##;
+    let expected = r##"<p><del>This is stricken out</del></p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_7() {
+    let original = r##"~This is \~stricken~
+"##;
+    let expected = r##"<p><del>This is ~stricken</del></p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_8() {
+    let original = r##"This~is~nothing
+"##;
+    let expected = r##"<p>This~is~nothing</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_9() {
+    let original = r##"~This~is~nothing~
+"##;
+    let expected = r##"<p><del>This~is~nothing</del></p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_10() {
+    let original = r##"Here I fail to strike out an exclamation point~!~.
+"##;
+    let expected = r##"<p>Here I fail to strike out an exclamation point~!~.</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_11() {
+    let original = r##"Here I fail to strike out a tilde ~~~.
+"##;
+    let expected = r##"<p>Here I fail to strike out a tilde ~~~.</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_12() {
+    let original = r##"Here I fail to match up ~~tildes~.
+"##;
+    let expected = r##"<p>Here I fail to match up ~~tildes~.</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_13() {
+    let original = r##"Here I fail to match up ~tildes~~.
+"##;
+    let expected = r##"<p>Here I fail to match up ~tildes~~.</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_14() {
+    let original = r##"~~This ~is stricken.~~
+"##;
+    let expected = r##"<p><del>This ~is stricken.</del></p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_15() {
+    let original = r##"~This ~~is stricken.~
+"##;
+    let expected = r##"<p><del>This ~~is stricken.</del></p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}
+
+#[test]
+fn strikethrough_test_16() {
+    let original = r##"~This ~~is stricken~ but this is not~~
+"##;
+    let expected = r##"<p><del>This ~~is stricken</del> but this is not~~</p>
+"##;
+
+    test_markdown_html(original, expected, false, false, false);
+}