Skip to content

blockmap: a bracket inside a code span must not arm the spanning guards - #17

Merged
jbeda merged 2 commits into
jbeda:mainfrom
karlkfi:codespan-mask
Aug 9, 2026
Merged

jbeda merged 2 commits into
jbeda:mainfrom
karlkfi:codespan-mask

Conversation

@karlkfi

@karlkfi karlkfi commented Aug 9, 2026 •

Copy link
Copy Markdown
Contributor

Found while adopting sentence-per-line across a MkDocs docset (263 non-vendored files, 27.8k prose lines). Three PRs came out of that run. This is the first and the least contentious.

Problem

hasUnbalancedBracket, hasUnclosedDestParen and hasUnclosedAngleDestOpener count delimiters in the raw line. A [ inside an inline code span therefore arms them when it straddles a soft line break, even though a bracket in a code span is literal and cannot open a label, a definition, or a destination.

$ printf 'Queues N jobs via a workflow\n(matrix on `runs-on: [self-hosted,\n<label>]`), analogous. Second sentence.\n' | mdreflow -
Queues N jobs via a workflow
(matrix on `runs-on: [self-hosted,
<label>]`), analogous. Second sentence.

Balanced on one line, the same span reflows fine, which is what makes the spanning case look unintended rather than deliberate:

$ printf 'Queues N jobs via a workflow\n(matrix on `runs-on: [self-hosted]`), analogous. Second sentence.\n' | mdreflow -
Queues N jobs via a workflow (matrix on `runs-on: [self-hosted]`), analogous.
Second sentence.

It lands on documentation that describes Markdown or YAML syntax, where such spans are the subject matter rather than an accident.

Proposal

maskCodeSpans replaces code-span interiors with a filler byte before the delimiter scans, preserving line structure and byte offsets so the existing scans see identical geometry.

It follows CommonMark's code-span rule rather than "text between backticks": a run of N backticks opens a span that only a run of exactly N closes, newlines included. A run with no matching closer is literal text and is left unmasked. That last part is load-bearing. It keeps `unclosed [bracket arming the guards, where there is no span and the bracket really is prose, so the change is scoped strictly to real code spans and every bracket outside one behaves as before.

Depends on hasBacktickInBareURL for correctness

maskCodeSpans pairs backtick runs itself, so it shares the blind spot that guard was added for: a backtick consumed into a linkified bare URL is not a delimiter to goldmark, and my scan would pair one out of step on exactly the inputs seed 41e98cb4c9e00729 covers. It is safe only because hasBacktickInBareURL returns before the masking runs, and I resolved the rebase to keep that order. Narrowing or moving that guard later means giving maskCodeSpans the same treatment at the same time. Detail in the thread below.

Verification

  • go test ./... green, including the committed FuzzFormat seeds.
  • -fuzz FuzzFormat -fuzztime 180s before the rebase: 4,463,558 execs, 890 new interesting, no failures. Re-run for 120s after rebasing onto the four new fuzz-find commits, also clean.
  • Six new table cases in TestCodeSpanBracketsDoNotArmGuards, plus seven fuzz seeds covering multi-line spans, unmatched runs, mismatched run lengths, and a real link outside a span whose bracket sits inside one.

Out of scope

inLinkRefDefZone runs ahead of this and stays blunt about definition-shaped lines, so a `[label]: target` in a code span still pulls its paragraph into the zone. One of the new test cases records that interaction so it is not mistaken for the masking failing.

I did try making the zone code-span aware. Your fuzzer killed it in under four seconds on "[08\"z00]:00 0": masking the span removes the ]:, the paragraph reflows, reflowing moves the line breaks, and the second pass sees different span boundaries. Not idempotent. That is the "fuzzing kept finding a seventh shape" outcome your own comment describes, so I left the zone alone.

Two follow-ups build on this branch and I will send them separately.

hasUnbalancedBracket, hasUnclosedDestParen and hasUnclosedAngleDestOpener
count delimiters in the raw line, so a "[" inside an inline code span arms
them when it straddles a soft line break. Inside a code span the bracket is
literal: it cannot open a link label, a reference definition, or a
destination, so the paragraph was never the hazard the guards exist for.

The cost falls on documentation that describes Markdown or YAML syntax,
where such spans are the subject matter. A "runs-on: [self-hosted,
<label>]" matrix snippet split across two source lines freezes its whole
paragraph at the old wrapping, permanently: the paragraph is skipped
because the bracket spans a break, and being skipped is what stops the
break being removed.

maskCodeSpans follows CommonMark's code-span rule rather than "text between
backticks": a run of N backticks opens a span only a run of exactly N
closes, newlines included, and a run with no matching closer is literal
text left unmasked. That keeps the change scoped to code spans, so every
bracket outside one behaves exactly as before.

Verified: full suite, the committed fuzz corpus, 4.46M FuzzFormat
executions with no failure, and seeds covering multi-line spans, unmatched
runs, mismatched run lengths, and a real link outside a span whose bracket
sits inside one.
@karlkfi

karlkfi commented Aug 9, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main after your four fuzz-find commits. One of them interacts with this PR in a way that is invisible in the diff, so flagging it rather than leaving it implicit.

maskCodeSpans pairs backtick runs itself. It therefore has the same blind spot hasBacktickInBareURL was added for: a backtick consumed into a linkified bare URL is not a delimiter to goldmark, so my scan pairs one out of step for exactly the inputs your seed 41e98cb4c9e00729 covers. Masking the wrong byte range would put the guards back on the wrong geometry, which is the failure this PR is supposed to remove.

It is safe as merged only because your guard returns before mine runs. I resolved the conflict to keep that order:

if hasBacktickInBareURL(trimmed) {
    return Paragraph{}, true
}
masked := maskCodeSpans(trimmed)
if hasUnbalancedBracket(masked) || ...

So this PR now depends on hasBacktickInBareURL for correctness, not just for its own stated reason. If you ever narrow or move that guard, maskCodeSpans needs the same treatment at the same time. Happy to add a test pinning the ordering if you want the dependency enforced rather than documented.

Re-verified after the rebase: full suite green, 120s of FuzzFormat with no failures, on this branch and on both stacked follow-ups.

golangci-lint's misspell check rejects the British spelling; CI runs it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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