Skip to content

Commit 1a5b124

Browse files
authored
fix: fix strikethrough flanking rules (#3882)
1 parent 2ba4e73 commit 1a5b124

7 files changed

Lines changed: 142 additions & 10 deletions

File tree

src/Lexer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -408,7 +408,7 @@ export class _Lexer<ParserOutput = string, RendererOutput = string> {
408408
}
409409

410410
// del (gfm)
411-
if (token = this.tokenizer.del(src)) {
411+
if (token = this.tokenizer.del(src, maskedSrc, prevChar)) {
412412
src = src.substring(token.raw.length);
413413
tokens.push(token);
414414
continue;

src/Tokenizer.ts

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -818,15 +818,56 @@ export class _Tokenizer<ParserOutput = string, RendererOutput = string> {
818818
}
819819
}
820820

821-
del(src: string): Tokens.Del | undefined {
822-
const cap = this.rules.inline.del.exec(src);
823-
if (cap) {
824-
return {
825-
type: 'del',
826-
raw: cap[0],
827-
text: cap[2],
828-
tokens: this.lexer.inlineTokens(cap[2]),
829-
};
821+
del(src: string, maskedSrc: string, prevChar = ''): Tokens.Del | undefined {
822+
let match = this.rules.inline.delLDelim.exec(src);
823+
if (!match) return;
824+
825+
const nextChar = match[1] || '';
826+
827+
if (!nextChar || !prevChar || this.rules.inline.punctuation.exec(prevChar)) {
828+
// unicode Regex counts emoji as 1 char; spread into array for proper count
829+
const lLength = [...match[0]].length - 1;
830+
let rDelim, rLength, delimTotal = lLength;
831+
832+
const endReg = this.rules.inline.delRDelim;
833+
endReg.lastIndex = 0;
834+
835+
// Clip maskedSrc to same section of string as src
836+
maskedSrc = maskedSrc.slice(-1 * src.length + lLength);
837+
838+
while ((match = endReg.exec(maskedSrc)) != null) {
839+
rDelim = match[1] || match[2] || match[3] || match[4] || match[5] || match[6];
840+
841+
if (!rDelim) continue;
842+
843+
rLength = [...rDelim].length;
844+
845+
if (rLength !== lLength) continue;
846+
847+
if (match[3] || match[4]) { // found another Left Delim
848+
delimTotal += rLength;
849+
continue;
850+
}
851+
852+
delimTotal -= rLength;
853+
854+
if (delimTotal > 0) continue; // Haven't found enough closing delimiters
855+
856+
// Remove extra characters
857+
rLength = Math.min(rLength, rLength + delimTotal);
858+
// char length can be >1 for unicode characters
859+
const lastCharLength = [...match[0]][0].length;
860+
const raw = src.slice(0, lLength + match.index + lastCharLength + rLength);
861+
862+
// Create del token - only single ~ or double ~~ supported
863+
const text = raw.slice(lLength, -lLength);
864+
return {
865+
type: 'del',
866+
raw,
867+
text,
868+
tokens: this.lexer.inlineTokens(text),
869+
};
870+
}
830871
}
831872
}
832873

src/rules.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,11 @@ const _punctuationGfmStrongEm = /(?!~)[\p{P}\p{S}]/u;
278278
const _punctuationOrSpaceGfmStrongEm = /(?!~)[\s\p{P}\p{S}]/u;
279279
const _notPunctuationOrSpaceGfmStrongEm = /(?:[^\s\p{P}\p{S}]|~)/u;
280280

281+
// GFM allows * and _ inside strikethrough
282+
const _punctuationGfmDel = /(?![*_])[\p{P}\p{S}]/u;
283+
const _punctuationOrSpaceGfmDel = /(?![*_])[\s\p{P}\p{S}]/u;
284+
const _notPunctuationOrSpaceGfmDel = /(?:[^\s\p{P}\p{S}]|[*_])/u;
285+
281286
// sequences em should skip over [title](link), `code`, <html>
282287
const blockSkip = edit(/link|precode-code|html/, 'g')
283288
.replace('link', /\[(?:[^\[\]`]|(?<a>`+)[^`]+\k<a>(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/)
@@ -332,6 +337,27 @@ const emStrongRDelimUnd = edit(
332337
.replace(/punct/g, _punctuation)
333338
.getRegex();
334339

340+
// Tilde left delimiter for strikethrough (similar to emStrongLDelim for asterisk)
341+
const delLDelim = edit(/^~~?(?:((?!~)punct)|[^\s~])/, 'u')
342+
.replace(/punct/g, _punctuationGfmDel)
343+
.getRegex();
344+
345+
// Tilde delimiter patterns for strikethrough (similar to asterisk)
346+
const delRDelimCore =
347+
'^[^~]+(?=[^~])' // Consume to delim
348+
+ '|(?!~)punct(~~?)(?=[\\s]|$)' // (1) #~~ can only be a Right Delimiter
349+
+ '|notPunctSpace(~~?)(?!~)(?=punctSpace|$)' // (2) a~~#, a~~ can only be a Right Delimiter
350+
+ '|(?!~)punctSpace(~~?)(?=notPunctSpace)' // (3) #~~a, ~~a can only be Left Delimiter
351+
+ '|[\\s](~~?)(?!~)(?=punct)' // (4) ~~# can only be Left Delimiter
352+
+ '|(?!~)punct(~~?)(?!~)(?=punct)' // (5) #~~# can be either Left or Right Delimiter
353+
+ '|notPunctSpace(~~?)(?=notPunctSpace)'; // (6) a~~a can be either Left or Right Delimiter
354+
355+
const delRDelim = edit(delRDelimCore, 'gu')
356+
.replace(/notPunctSpace/g, _notPunctuationOrSpaceGfmDel)
357+
.replace(/punctSpace/g, _punctuationOrSpaceGfmDel)
358+
.replace(/punct/g, _punctuationGfmDel)
359+
.getRegex();
360+
335361
const anyPunctuation = edit(/\\(punct)/, 'gu')
336362
.replace(/punct/g, _punctuation)
337363
.getRegex();
@@ -389,6 +415,8 @@ const inlineNormal = {
389415
br,
390416
code: inlineCode,
391417
del: noopTest,
418+
delLDelim: noopTest,
419+
delRDelim: noopTest,
392420
emStrongLDelim,
393421
emStrongRDelimAst,
394422
emStrongRDelimUnd,
@@ -427,6 +455,8 @@ const inlineGfm: Record<InlineKeys, RegExp> = {
427455
...inlineNormal,
428456
emStrongRDelimAst: emStrongRDelimAstGfm,
429457
emStrongLDelim: emStrongLDelimGfm,
458+
delLDelim,
459+
delRDelim,
430460
url: edit(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/)
431461
.replace('protocol', _caseInsensitiveProtocol)
432462
.replace('email', /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/)

test/specs/new/del_flanking.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<p>~1,~2</p>
2+
<p>Between ~1.34 and ~5.4</p>
3+
<p>Between (~1.34) and (~5.4)</p>
4+
<p>~ test~</p>
5+
<p>~test ~</p>

test/specs/new/del_flanking.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
~1,~2
2+
3+
Between ~1.34 and ~5.4
4+
5+
Between (~1.34) and (~5.4)
6+
7+
~ test~
8+
9+
~test ~
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<p><del>test 💁</del></p>
2+
<p><del>💁 test</del></p>
3+
<p><del>🤓 test</del></p>
4+
<p><del>🏖️ test</del></p>
5+
<p><del>🏖️🤓💁 test</del></p>
6+
<p><del>💁 test</del> test</p>
7+
<p>test <del>💁 test</del></p>
8+
<p>test <del>💁 test</del> test</p>
9+
<p>~~💁 ~~</p>
10+
<p>~~ 💁~~</p>
11+
<p><del>⚠️ test</del></p>
12+
<p><del>💁 test</del></p>
13+
<p><del>t💁t</del> test</p>
14+
<p><del>t💁t</del> test</p>
15+
<p>~1 😁~2</p>
16+
<p><del>🏴‍☠️</del> test</p>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
~~test 💁~~
2+
3+
~~💁 test~~
4+
5+
~~🤓 test~~
6+
7+
~~🏖️ test~~
8+
9+
~~🏖️🤓💁 test~~
10+
11+
~~💁 test~~ test
12+
13+
test ~~💁 test~~
14+
15+
test ~~💁 test~~ test
16+
17+
~~💁 ~~
18+
19+
~~ 💁~~
20+
21+
~~⚠️ test~~
22+
23+
~💁 test~
24+
25+
~t💁t~ test
26+
27+
~~t💁t~~ test
28+
29+
~1 😁~2
30+
31+
~~🏴‍☠️~~ test

0 commit comments

Comments
 (0)