diff --git a/src/Lexer.ts b/src/Lexer.ts index 9ba9df600f..bc12e9f3be 100644 --- a/src/Lexer.ts +++ b/src/Lexer.ts @@ -300,6 +300,40 @@ export class _Lexer { return tokens; } + /** + * Does this link text hold a link already? An image does not count: an image + * may hold a link, a link may not. + */ + private linkInText(text: string): boolean { + if (!text.includes('[')) { + return false; + } + + const linkRule = this.tokenizer.rules.inline.link; + for (const match of text.matchAll(this.tokenizer.rules.inline.blockSkip)) { + // blockSkip also matches code spans and html, and the `!` of an image is + // left out of the match, so read the character before it. + if (linkRule.test(match[0]) && text.charAt(match.index - 1) !== '!') { + return true; + } + } + + for (const match of text.matchAll(this.tokenizer.rules.inline.reflinkSearch)) { + const match0 = match[0]; + const refStart = match0.lastIndexOf('['); + if (match0.charAt(0) === '!' || !Object.hasOwn(this.tokens.links, match0.slice(refStart + 1, -1))) { + continue; + } + // a candidate holding a link is not a link either, so it does not count + if (refStart > 1 && this.linkInText(match0.slice(1, refStart - 1))) { + continue; + } + return true; + } + + return false; + } + /** * Lexing/Compiling */ @@ -310,10 +344,27 @@ export class _Lexer { // Mask out reflinks if (this.tokens.links && src.includes('[')) { - maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.reflinkSearch, match0 => - Object.hasOwn(this.tokens.links, match0.slice(match0.lastIndexOf('[') + 1, -1)) - ? '[' + 'a'.repeat(match0.length - 2) + ']' - : match0); + const reflinkSearch = this.tokenizer.rules.inline.reflinkSearch; + const maskReflink = (match0: string): string => { + const refStart = match0.lastIndexOf('['); + if (!Object.hasOwn(this.tokens.links, match0.slice(refStart + 1, -1))) { + return match0; + } + // CommonMark: "Links may not contain other links, at any level of + // nesting." A candidate whose text already holds one never becomes a + // link, so flattening the whole span would hide the emphasis that + // does still apply inside it. Mask the links it holds instead. + // Images are exempt: their text is flattened into an alt attribute. + if (refStart > 1 && match0.charAt(0) !== '!') { + const text = match0.slice(1, refStart - 1); + if (this.linkInText(text)) { + return '[' + text.replace(reflinkSearch, maskReflink) + + '][' + 'a'.repeat(match0.length - refStart - 2) + ']'; + } + } + return '[' + 'a'.repeat(match0.length - 2) + ']'; + }; + maskedSrc = maskedSrc.replace(reflinkSearch, maskReflink); } // Mask out escaped characters. diff --git a/test/specs/commonmark/commonmark.0.31.2.json b/test/specs/commonmark/commonmark.0.31.2.json index 4e348a4a1c..92b13ab027 100644 --- a/test/specs/commonmark/commonmark.0.31.2.json +++ b/test/specs/commonmark/commonmark.0.31.2.json @@ -4269,8 +4269,7 @@ "example": 533, "start_line": 8053, "end_line": 8059, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "*[foo*][ref]\n\n[ref]: /uri\n", diff --git a/test/specs/gfm/commonmark.0.31.2.json b/test/specs/gfm/commonmark.0.31.2.json index 47d0a2256a..a2bf8cafd2 100644 --- a/test/specs/gfm/commonmark.0.31.2.json +++ b/test/specs/gfm/commonmark.0.31.2.json @@ -4269,8 +4269,7 @@ "example": 533, "start_line": 8053, "end_line": 8059, - "section": "Links", - "shouldFail": true + "section": "Links" }, { "markdown": "*[foo*][ref]\n\n[ref]: /uri\n", diff --git a/test/specs/new/emphasis_in_link_text_with_link.html b/test/specs/new/emphasis_in_link_text_with_link.html new file mode 100644 index 0000000000..7c46429c3f --- /dev/null +++ b/test/specs/new/emphasis_in_link_text_with_link.html @@ -0,0 +1,4 @@ +

[foo bar baz qux]ref

+

[foo bar baz qux]ref

+

foo bar baz qux

+

*foo bar*

diff --git a/test/specs/new/emphasis_in_link_text_with_link.md b/test/specs/new/emphasis_in_link_text_with_link.md new file mode 100644 index 0000000000..0c9c07d6a8 --- /dev/null +++ b/test/specs/new/emphasis_in_link_text_with_link.md @@ -0,0 +1,9 @@ +[foo *bar [baz](/url) qux*][ref] + +[foo _bar [baz][ref] qux_][ref] + +[foo *bar ![baz](/img.png) qux*][ref] + +*foo [bar*][ref] + +[ref]: /uri