Skip to content

fix: keep emphasis in the text of a rejected reference link - #4059

Open
luantaraschi wants to merge 1 commit into
markedjs:masterfrom
luantaraschi:fix/emphasis-in-rejected-link-text
Open

fix: keep emphasis in the text of a rejected reference link#4059
luantaraschi wants to merge 1 commit into
markedjs:masterfrom
luantaraschi:fix/emphasis-in-rejected-link-text

Conversation

@luantaraschi

Copy link
Copy Markdown

Marked version: 18.0.10, current main at e250e31

Markdown flavor: CommonMark|GitHub Flavored Markdown

Description

No issue for this one. It is what is left of the group I listed in #4050 after #4051 landed: the tokenizer now refuses to nest a link inside a link, but the mask that emStrong reads was not told, so it still treats the rejected candidate as a link and hides every emphasis marker inside it.

Expectation

[foo *bar [baz](/url) qux*][ref]

[ref]: /uri
<p>[foo <em>bar <a href="/url">baz</a> qux</em>]<a href="/uri">ref</a></p>

commonmark 0.31.2, already a dev dependency here, returns exactly that.

Result

<p>[foo *bar <a href="/url">baz</a> qux*]<a href="/uri">ref</a></p>

The asterisks are printed as text. The reference link and the inner link are both right, only the emphasis is gone. Same with _, with **, and when the inner link is a reference link instead of an inline one, which is CommonMark example 533.

The trigger is narrow but it is ordinary prose: a reference link whose text happens to hold another link. Nothing warns the author, the markers just disappear from the output.

What was attempted

inlineTokens masks a reference link candidate by replacing the whole span with [aaaa...], so emStrong cannot pair a delimiter outside a link with one inside it. That is the right call when the candidate really is a link, because the link text is tokenized separately.

When the text already holds a link the candidate is not a link, #4051 made outputLink say so, and the span goes back through the loop as ordinary text. The mask built before the loop still reads [aaaa...] there, so the * never reaches emStrong.

The mask now follows the same rule the tokenizer follows. A candidate whose text holds a link keeps its brackets and its emphasis markers visible, and the links inside it are masked instead. Images are left on the old path, since an image may hold a link.

linkInText answers the question. It returns on text.includes('[') before touching a regex, so text without brackets costs nothing, and it skips images in both directions: an image inside link text does not disqualify the outer candidate, and an image candidate is never disqualified by what it holds.

I first tried rebuilding the mask from the lexer loop whenever a candidate was rejected. It is a smaller diff and it reads better, but it is quadratic in the number of rejections in one inline run, and a paragraph with 400 of them went from 10.1 ms to 44.6 ms. Given #4040 and #4048 are open on this very block, that seemed like the wrong thing to hand you.

Scope

Fixes CommonMark example 533. shouldFail cleared in test/specs/commonmark/ and in test/specs/gfm/, Links goes from 80 to 81 of 90.

512, 520 and 528 are still failing and are not touched here. They share a title with 533 in my grouping but not a cause: they need _inlineLabel to match more than one level of nested brackets, which is a different change.

Measurements

Differential run over 317 inputs, combinations of brackets, emphasis, inline links, reference links, images and code spans, plus every file in test/specs/new, in both flavors, against the bundled commonmark:

  • 18 outputs changed, which is the same 9 inputs once per flavor
  • 8 of those 9 went from disagreeing with the reference to matching it byte for byte
  • the 9th is the new spec file, where the only remaining difference is <img ...> against <img ... />
  • nothing moved away from the reference

Timing on Node 22.20.0, best of five runs:

input before after
README plus USING_PRO, 20 copies 21.12 ms 20.77 ms
2000 reference definitions and 2000 uses 206.01 ms 205.69 ms
400 rejected candidates in one paragraph 5.22 ms 4.62 ms
300 levels of nesting 0.63 ms 0.59 ms

npm test is green: 1785 spec tests, 191 unit tests, plus umd, cjs, types and lint.

Contributor

  • Test(s) exist to ensure functionality and minimize regression (if no tests added, list tests covering this PR); or,
  • no tests required for this PR.
  • If submitting new feature, it has been documented in the appropriate places.

@vercel

vercel Bot commented Aug 18, 2026

Copy link
Copy Markdown

@AntonioBraga88 is attempting to deploy a commit to the MarkedJS Team on Vercel.

A member of the Team first needs to authorize it.

A reference link candidate whose text already holds a link never becomes
a link, but the mask em and strong read still flattened the whole span,
so every emphasis marker inside it was invisible.

Mask the links the text holds instead of the span, and leave images
alone, since an image may hold a link.

Fixes CommonMark example 533.
@luantaraschi
luantaraschi force-pushed the fix/emphasis-in-rejected-link-text branch from 088bfc5 to 726aed0 Compare August 18, 2026 23:04
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
marked-website Ready Ready Preview Aug 19, 2026 1:17am

Request Review

@UziTech UziTech left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks! 💯

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants