Skip to content

fix: allow one more level of nested brackets in a link label - #4064

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

fix: allow one more level of nested brackets in a link label#4064
luantaraschi wants to merge 1 commit into
markedjs:masterfrom
luantaraschi:fix/nested-brackets-in-link-text

Conversation

@luantaraschi

Copy link
Copy Markdown

Part of the "nested brackets in link text" group in #4050.

CommonMark says the brackets in a link label may nest, with no limit on the depth:

Brackets are allowed in the link text only if (a) they are backslash-escaped or (b) they appear as a matched pair of brackets, with an open bracket [, a sequence of zero or more inlines, and a close bracket ].

_inlineLabel allowed one level. The nested alternative it carries, \[(?:\[\s\S]|[^\[\]\])*\], matches a pair of brackets whose contents hold no bracket at all, so a second pair inside made the whole label fail to match and the text stayed literal:

[link [foo [bar]]](/uri)
  <p>[link [foo [bar]]](/uri)</p>
  <p><a href="/uri">link [foo [bar]]</a></p>

The nested alternative now accepts one such pair itself. Everything else about the label is unchanged, including the code-span alternatives that keep a backtick span from swallowing a bracket.

Why two levels and not more

A regex cannot follow arbitrary nesting, so the depth is a number someone has to pick. I built the pattern at depths 1 through 4 and ran the CommonMark suite at each:

depth examples that start passing examples that break
1 (today)
2 512, 520, 528 none
3 512, 520, 528 none
4 512, 520, 528 none

The third and the fourth level pay for nothing: the spec has no case that needs them. So this stops at two, and the comment in the source says so, since the next person to look at that line will ask the same question.

Example 520, ![[[foo](uri1)](uri2)](uri3), comes along for the ride. It needs the same second level, and the link-inside-a-link rejection from #4051 already does the rest.

Cost

link, reflink and their gfm and pedantic variants each grow by 28 characters of source. Parsing 200 documents holding a 2000 character label measured the same before and after, at 14ms.

npm run test:redos is clean: 148 patterns analysed, all reported safe, including the deeper inline.normal.link and inline.normal.reflink.

Tests

The three examples are covered by the spec suite itself, so this drops their shouldFail flags in test/specs/commonmark/commonmark.0.31.2.json and in the gfm copy of the same file, which flagged them separately.

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

What is still failing in that group

524, 526, 536 and 538 are a different root cause, the one about raw HTML and autolinks binding more tightly than the brackets of a link text, and are untouched here.

CommonMark lets the brackets in a link label nest to any depth. The label
pattern only allowed a single level, so a link whose text carried a nested
pair was not read as a link at all.

Fixes CommonMark examples 512, 520 and 528.
@vercel

vercel Bot commented Aug 20, 2026

Copy link
Copy Markdown

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

A member of the Team first needs to authorize it.

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.

1 participant