diff --git a/src/Lexer.ts b/src/Lexer.ts index 50b15d9ae5..b905c4c1bd 100644 --- a/src/Lexer.ts +++ b/src/Lexer.ts @@ -397,7 +397,17 @@ export class _Lexer { // 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)); + // A backslash inside a code span is literal, not an escape. Mask the + // backticks inside the span instead, so blockSkip still skips all of it. + maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.anyPunctuationOutsideCode, (match0, code, punct) => { + if (punct) { + return '+'.repeat(match0.length); + } + if (code) { + return code + match0.slice(code.length, -code.length).replace(/`/g, '+') + code; + } + return match0; + }); // Mask out other blocks maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.blockSkip, (match0, _link, context) => { diff --git a/src/rules.ts b/src/rules.ts index a707f42ce5..d942e0081e 100644 --- a/src/rules.ts +++ b/src/rules.ts @@ -431,6 +431,14 @@ const anyPunctuation = edit(/\\(punct)/, 'gu') .replace(/punct/g, _punctuation) .getRegex(); +// `anyPunctuation` for the em/strong mask. A backslash is literal inside a code +// span, so a code span (matched like `inline.code`), or a backtick run that +// opens none, is matched whole and kept; only the escapes outside code spans +// are masked. +const anyPunctuationOutsideCode = edit(/(`+)(?:[^`]|[^`][\s\S]*?[^`])\1(?!`)|`+|\\(punct)/, 'gu') + .replace(/punct/g, _punctuation) + .getRegex(); + const autolink = edit(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/) .replace('scheme', /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/) .replace('email', /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/) @@ -513,6 +521,7 @@ const _extendedEmailProtocol = edit(/(?:mailto:email|xmpp:email(?:\/[A-Za-z0-9@. const inlineNormal = { _backpedal: noopTest, // only used for GFM url anyPunctuation, + anyPunctuationOutsideCode, autolink, blockSkip, br, diff --git a/test/specs/new/codespan_backslash_emphasis.html b/test/specs/new/codespan_backslash_emphasis.html new file mode 100644 index 0000000000..d8856d239b --- /dev/null +++ b/test/specs/new/codespan_backslash_emphasis.html @@ -0,0 +1,13 @@ +

Note that \ or / will be interpreted as path separators on Windows, and will match against / in glob expressions.

+ +

Use C:\Users\ for home and D:\ for data.

+ +

a\ b c

+ +

`escaped tick` code

+ +

\ \ after double backticks

+ +

_\`_

+ +

_a`_

diff --git a/test/specs/new/codespan_backslash_emphasis.md b/test/specs/new/codespan_backslash_emphasis.md new file mode 100644 index 0000000000..d5b6ad4396 --- /dev/null +++ b/test/specs/new/codespan_backslash_emphasis.md @@ -0,0 +1,13 @@ +Note that `\` or `/` _will_ be interpreted as path separators on Windows, and will match against `/` in glob expressions. + +Use `C:\Users\` for **home** and `D:\` for data. + +`a\` *b* `c` + +\`*escaped tick*\` `code` + +`\` ``\`` **after double backticks** + +_``\`_`` + +_`` a`_ ``