Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 55 additions & 4 deletions src/Lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,40 @@ export class _Lexer<ParserOutput = string, RendererOutput = string> {
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, links: 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) === '!' || !links.includes(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), links)) {
continue;
}
return true;
}

return false;
}

/**
* Lexing/Compiling
*/
Expand All @@ -312,10 +346,27 @@ export class _Lexer<ParserOutput = string, RendererOutput = string> {
if (this.tokens.links) {
const links = Object.keys(this.tokens.links);
if (links.length > 0) {
maskedSrc = maskedSrc.replace(this.tokenizer.rules.inline.reflinkSearch, match0 =>
links.includes(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 (!links.includes(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, links)) {
return '[' + text.replace(reflinkSearch, maskReflink)
+ '][' + 'a'.repeat(match0.length - refStart - 2) + ']';
}
}
return '[' + 'a'.repeat(match0.length - 2) + ']';
};
maskedSrc = maskedSrc.replace(reflinkSearch, maskReflink);
}
}

Expand Down
3 changes: 1 addition & 2 deletions test/specs/commonmark/commonmark.0.31.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
3 changes: 1 addition & 2 deletions test/specs/gfm/commonmark.0.31.2.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions test/specs/new/emphasis_in_link_text_with_link.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<p>[foo <em>bar <a href="/url">baz</a> qux</em>]<a href="/uri">ref</a></p>
<p>[foo <em>bar <a href="/uri">baz</a> qux</em>]<a href="/uri">ref</a></p>
<p><a href="/uri">foo <em>bar <img src="/img.png" alt="baz"> qux</em></a></p>
<p>*foo <a href="/uri">bar*</a></p>
9 changes: 9 additions & 0 deletions test/specs/new/emphasis_in_link_text_with_link.md
Original file line number Diff line number Diff line change
@@ -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