Skip to content

fix: handle bracketed spans inside link labels#288

Open
deshiknaves wants to merge 1 commit into
comarkdown:mainfrom
deshiknaves:deshiknaves/fix-inline-span-silent-pos
Open

fix: handle bracketed spans inside link labels#288
deshiknaves wants to merge 1 commit into
comarkdown:mainfrom
deshiknaves:deshiknaves/fix-inline-span-silent-pos

Conversation

@deshiknaves

Copy link
Copy Markdown

🔗 Linked issue

Resolves #287

❓ Type of change

  • 📖 Documentation (updates to the documentation or readme)
  • 🐞 Bug fix (a non-breaking change that fixes an issue)
  • 👌 Enhancement (improving an existing functionality like performance)
  • ✨ New feature (a non-breaking change that adds functionality)
  • ⚠️ Breaking change (fix or feature that would cause existing functionality to change)

📚 Description

Parsing any link whose label contains a bracketed span throws:

Error: inline rule didn't increment state.pos

Minimal repro:

import { parse } from 'comark'
await parse('[[1] Document](#)') // rejects

This shape is common in LLM-generated citation links ([[1] Source](url)), and the same input parses fine in plain markdown-it.

Causecomark_inline_span (registered before('link')) returned true in silent mode without advancing state.pos. markdown-it's inline-rule contract requires every rule returning true to advance state.pos, silent mode included — skipToken enforces it. When the link rule calls parseLinkLabelskipToken on the inner [1], the span rule matched silently, left pos untouched, and skipToken threw.

Fix — two parts:

  1. Return false in silent mode (same pattern as the built-in emphasis/strikethrough rules), so parseLinkLabel's own bracket-depth tracking consumes the nested [...] and the outer link parses. Just advancing state.pos in the silent branch is not enough: parseLinkLabel treats a multi-char silent match starting with [ as a nested link and aborts the whole label.
  2. Keep bare [text] literal inside link labels. Without this, the span rule ate the inner [1] when the label was re-tokenized, rendering link text "1 Document" instead of the plain-markdown "[1] Document". The link rule tokenizes label children inside state.linkLevel++/--, so when linkLevel > 0 the span rule now only matches explicit [text]{attrs} spans.

Behavior after the fix:

Input Result
[[1] Document](#) link with literal text [1] Document (previously: throw)
[a [x]{.y} b](#) explicit span still works inside a link label
[content]{.class} unchanged
Hello [World] bare span outside links unchanged (matches compare-parsers behavior)

Tests added in test/parse/inline-span.test.ts covering all rows above.

📝 Checklist

  • I have linked an issue or discussion.
  • I have run pnpm verify and it passes.
  • I have updated the documentation accordingly.

Note on pnpm verify: lint and all package test suites pass. The typecheck step reports 4 errors that are pre-existing on unmodified main (verified by running tsc --noEmit on a tree identical to main) and don't involve files touched by this PR:

  • packages/comark-angular/test/rendering.test.ts:87'streaming' does not exist in type 'ParseOptions<[]>'
  • packages/comark-vue/test/vite.test.ts:22 — vite plugin config hook not callable (ObjectHook union)
  • packages/comark-vue/test/vite.test.ts:54'codegenNode' does not exist on template AST node
  • packages/comark/test/plugins/dedupe.test.ts:27number | undefined not assignable to number

Any link label containing a bracketed span, e.g. `[[1] Document](#)`
(common in LLM-generated citation links), threw "inline rule didn't
increment state.pos": the comark_inline_span rule's silent branch
returned true without advancing state.pos, violating markdown-it's
inline-rule contract when parseLinkLabel -> skipToken probed the label.

Return false in silent mode instead (same pattern as emphasis /
strikethrough) so the label's own bracket-depth tracking consumes the
nested `[...]` and the outer link parses. Advancing state.pos in the
silent branch was not enough — parseLinkLabel treats a multi-char
silent match starting with `[` as a nested link and aborts the label.

Also keep bare `[text]` literal inside link labels: the span rule ate
the inner `[1]` when the label was re-tokenized, rendering link text
"1 Document" instead of the plain-markdown literal "[1] Document".
markdown-exit's link rule tokenizes label children inside
state.linkLevel++/--, so when linkLevel > 0 only match explicit
`[text]{attrs}` spans. Bare `[text]` outside links still becomes a
span (MDC behavior, per compare-parsers tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@deshiknaves
deshiknaves requested a review from farnabaz as a code owner July 22, 2026 18:29
@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

@deshiknaves is attempting to deploy a commit to the NuxtLabs 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.

Links with bracketed labels throw "inline rule didn't increment state.pos"

1 participant