From 2712cbf747287470c17c9a39512b3a1f51dc7ed3 Mon Sep 17 00:00:00 2001 From: Jayesh Date: Tue, 4 Aug 2026 19:28:48 +0530 Subject: [PATCH] fix: keep the em/strong mask the same length as the source emStrong and del align maskedSrc with src by slicing the mask from the end, so every mask must keep the length of the text it replaces. The escape mask substituted a fixed two-character '++', but anyPunctuation is /\\([\p{P}\p{S}])/gu and matches astral code points, so an escaped character above U+FFFF is three code units masked as two. The mask then ended up shorter than the source, the clip reached one code unit too far back, and the resulting match.index was applied to src: the closing delimiter was consumed a code unit late, leaving a stray delimiter inside the emphasis and, for some inputs, dropping a character of the document. Substitute one '+' per code unit instead. '+' is punctuation, so the delimiter flanking of the masked region is unchanged, and the ASCII case still produces '++'. --- src/Lexer.ts | 7 +++++-- .../specs/new/em_escaped_astral_punctuation.html | 7 +++++++ test/specs/new/em_escaped_astral_punctuation.md | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 test/specs/new/em_escaped_astral_punctuation.html create mode 100644 test/specs/new/em_escaped_astral_punctuation.md diff --git a/src/Lexer.ts b/src/Lexer.ts index 89e291f19c..64cbe8101c 100644 --- a/src/Lexer.ts +++ b/src/Lexer.ts @@ -316,8 +316,11 @@ export class _Lexer { } } - // Mask out escaped characters - maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.anyPunctuation, '++'); + // Mask out escaped characters. + // Every mask must keep the length it replaces: emStrong and del line + // maskedSrc up with src by slicing from the end. `anyPunctuation` matches + // unicode punctuation, so an escaped astral character is 3 code units. + maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.anyPunctuation, match0 => '+'.repeat(match0.length)); // Mask out other blocks maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.blockSkip, (match0, _link, context) => { diff --git a/test/specs/new/em_escaped_astral_punctuation.html b/test/specs/new/em_escaped_astral_punctuation.html new file mode 100644 index 0000000000..588d3bab26 --- /dev/null +++ b/test/specs/new/em_escaped_astral_punctuation.html @@ -0,0 +1,7 @@ +

a\πŸ™‚

+

a \πŸ™‚ b

+

xab\πŸ™‚y

+

aa\𐄀b

+

a\πŸ™‚b

+

a\πŸ™‚

+

foo\πŸ™‚bar

diff --git a/test/specs/new/em_escaped_astral_punctuation.md b/test/specs/new/em_escaped_astral_punctuation.md new file mode 100644 index 0000000000..541830ca73 --- /dev/null +++ b/test/specs/new/em_escaped_astral_punctuation.md @@ -0,0 +1,16 @@ +--- +gfm: true +--- +*a*\πŸ™‚ + +*a* \πŸ™‚ *b* + +x*ab*\πŸ™‚y + +*a*a\𐄀*b* + +__a__\πŸ™‚b + +~~a~~\πŸ™‚ + +*foo\πŸ™‚bar*