Skip to content

pr-sanity's link check does not skip fenced code blocks #202

Description

@realmarcin

From the review of #201.

check_markdown_links in scripts/pr_sanity.py scans Markdown line by line and does not track fenced code blocks, so a link written as an example inside a fence is treated as a real link:

Write it like this:

```markdown
see [the docs](docs/example.md)
```

If docs/example.md does not exist, that is reported as BROKEN_LINK even though nothing is actually broken. Confirmed against the regex directly — it matches inside a fence.

Impact today: none

The repo currently reports 0 findings across 44 markdown files and 31 relative links, so nothing is affected. This is latent: it will bite the first person who documents a link pattern in a fence, and it will bite them as a CI failure on an unrelated PR, which is the annoying kind.

That risk is slightly elevated by pr-sanity running unfiltered on every PR — a false positive here blocks any PR, not just docs PRs.

Fix

Track fence state while scanning: toggle on lines matching ^\s*(|~~~) ``` and skip lines while inside. Two details worth getting right:

  • Indented fences inside list items still open a block.
  • Nested/longer fences```` opens a block that a 3-backtick line does not close. Matching fence length is the correct rule; ignoring it would mis-parse the example above, which is itself a 4-backtick fence containing a 3-backtick one.

Also worth considering: inline code spans (`[x](y.md)`) have the same problem on a single line.

Not fixed in #201 because fence-aware parsing is more than a one-liner and could introduce its own false negatives — silently skipping real links would be worse than the current false positive, given the whole point of that script is to not verify nothing.

Acceptance

A link inside a fenced block is ignored; a link outside one is still checked; and a test covers a 4-backtick fence wrapping a 3-backtick fence so the length rule does not regress.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions