Skip to content

Commit 377823a

Browse files
authored
fix: Support escapes within emphasis (#2627)
* fix: Support escapes within emphasis ...particularly right at the end. Fixes #2280 * chore: realign comments with regexp alternatives * test: Add HTML test for escapes within emphasis * fix: Correct recognition and masking of escaped emphasis punctuation * fix: Correct backslash fake-lookbehind
1 parent 54410cd commit 377823a

File tree

6 files changed

+63
-9
lines changed

6 files changed

+63
-9
lines changed

src/Lexer.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,8 @@ export class Lexer {
350350

351351
// Mask out escaped em & strong delimiters
352352
while ((match = this.tokenizer.rules.inline.escapedEmSt.exec(maskedSrc)) != null) {
353-
maskedSrc = maskedSrc.slice(0, match.index) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex);
353+
maskedSrc = maskedSrc.slice(0, match.index + match[0].length - 2) + '++' + maskedSrc.slice(this.tokenizer.rules.inline.escapedEmSt.lastIndex);
354+
this.tokenizer.rules.inline.escapedEmSt.lastIndex--;
354355
}
355356

356357
while (src) {

src/Tokenizer.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -629,22 +629,24 @@ export class Tokenizer {
629629
// Remove extra characters. *a*** -> *a*
630630
rLength = Math.min(rLength, rLength + delimTotal + midDelimTotal);
631631

632+
const raw = src.slice(0, lLength + match.index + (match[0].length - rDelim.length) + rLength);
633+
632634
// Create `em` if smallest delimiter has odd char count. *a***
633635
if (Math.min(lLength, rLength) % 2) {
634-
const text = src.slice(1, lLength + match.index + rLength);
636+
const text = raw.slice(1, -1);
635637
return {
636638
type: 'em',
637-
raw: src.slice(0, lLength + match.index + rLength + 1),
639+
raw,
638640
text,
639641
tokens: this.lexer.inlineTokens(text)
640642
};
641643
}
642644

643645
// Create 'strong' if smallest delimiter has even char count. **a***
644-
const text = src.slice(2, lLength + match.index + rLength - 1);
646+
const text = raw.slice(2, -2);
645647
return {
646648
type: 'strong',
647-
raw: src.slice(0, lLength + match.index + rLength + 1),
649+
raw,
648650
text,
649651
tokens: this.lexer.inlineTokens(text)
650652
};

src/rules.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,9 @@ export const inline = {
168168
emStrong: {
169169
lDelim: /^(?:\*+(?:([punct_])|[^\s*]))|^_+(?:([punct*])|([^\s_]))/,
170170
// (1) and (2) can only be a Right Delimiter. (3) and (4) can only be Left. (5) and (6) can be either Left or Right.
171-
// () Skip orphan inside strong () Consume to delim (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a
172-
rDelimAst: /^[^_*]*?\_\_[^_*]*?\*[^_*]*?(?=\_\_)|[^*]+(?=[^*])|[punct_](\*+)(?=[\s]|$)|[^punct*_\s](\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|[^punct*_\s](\*+)(?=[^punct*_\s])/,
173-
rDelimUnd: /^[^_*]*?\*\*[^_*]*?\_[^_*]*?(?=\*\*)|[^_]+(?=[^_])|[punct*](\_+)(?=[\s]|$)|[^punct*_\s](\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _
171+
// () Skip orphan inside strong () Consume to delim (1) #*** (2) a***#, a*** (3) #***a, ***a (4) ***# (5) #***# (6) a***a
172+
rDelimAst: /^(?:[^_*\\]|\\.)*?\_\_(?:[^_*\\]|\\.)*?\*(?:[^_*\\]|\\.)*?(?=\_\_)|(?:[^*\\]|\\.)+(?=[^*])|[punct_](\*+)(?=[\s]|$)|(?:[^punct*_\s\\]|\\.)(\*+)(?=[punct_\s]|$)|[punct_\s](\*+)(?=[^punct*_\s])|[\s](\*+)(?=[punct_])|[punct_](\*+)(?=[punct_])|(?:[^punct*_\s\\]|\\.)(\*+)(?=[^punct*_\s])/,
173+
rDelimUnd: /^(?:[^_*\\]|\\.)*?\*\*(?:[^_*\\]|\\.)*?\_(?:[^_*\\]|\\.)*?(?=\*\*)|(?:[^_\\]|\\.)+(?=[^_])|[punct*](\_+)(?=[\s]|$)|(?:[^punct*_\s\\]|\\.)(\_+)(?=[punct*\s]|$)|[punct*\s](\_+)(?=[^punct*_\s])|[\s](\_+)(?=[punct*])|[punct*](\_+)(?=[punct*])/ // ^- Not allowed for _
174174
},
175175
code: /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,
176176
br: /^( {2,}|\\)\n(?!\s*$)/,
@@ -186,7 +186,9 @@ inline.punctuation = edit(inline.punctuation).replace(/punctuation/g, inline._pu
186186

187187
// sequences em should skip over [title](link), `code`, <html>
188188
inline.blockSkip = /\[[^\]]*?\]\([^\)]*?\)|`[^`]*?`|<[^>]*?>/g;
189-
inline.escapedEmSt = /\\\*|\\_/g;
189+
// lookbehind is not available on Safari as of version 16
190+
// inline.escapedEmSt = /(?<=(?:^|[^\\)(?:\\[^])*)\\[*_]/g;
191+
inline.escapedEmSt = /(?:^|[^\\])(?:\\\\)*\\[*_]/g;
190192

191193
inline._comment = edit(block._comment).replace('(?:-->|$)', '-->').getRegex();
192194

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<p><strong>strong text[</strong>]</p>
2+
3+
<p><strong>strong text\[</strong>]</p>
4+
5+
<p><em>em[pha](sis)</em></p>
6+
7+
<p><em>\</em></p>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
**strong text\[**\]
2+
3+
**strong text\\\[**\]
4+
5+
_em\[pha\]\(sis\)_
6+
7+
_\\_

test/unit/Lexer-spec.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,41 @@ paragraph
776776
});
777777
});
778778

779+
it('escaped punctuation inside emphasis', () => {
780+
expectInlineTokens({
781+
md: '**strong text\\[**\\]',
782+
tokens: [
783+
{
784+
type: 'strong',
785+
raw: '**strong text\\[**',
786+
text: 'strong text\\[',
787+
tokens: [
788+
{ type: 'text', raw: 'strong text', text: 'strong text' },
789+
{ type: 'escape', raw: '\\[', text: '[' }
790+
]
791+
},
792+
{ type: 'escape', raw: '\\]', text: ']' }
793+
]
794+
});
795+
expectInlineTokens({
796+
md: '_em\\<pha\\>sis_',
797+
tokens: [
798+
{
799+
type: 'em',
800+
raw: '_em\\<pha\\>sis_',
801+
text: 'em\\<pha\\>sis',
802+
tokens: [
803+
{ type: 'text', raw: 'em', text: 'em' },
804+
{ type: 'escape', raw: '\\<', text: '&lt;' },
805+
{ type: 'text', raw: 'pha', text: 'pha' },
806+
{ type: 'escape', raw: '\\>', text: '&gt;' },
807+
{ type: 'text', raw: 'sis', text: 'sis' }
808+
]
809+
}
810+
]
811+
});
812+
});
813+
779814
it('html', () => {
780815
expectInlineTokens({
781816
md: '<div>html</div>',

0 commit comments

Comments
 (0)