ReviewOS

also looking at this

stacks/bunpress

fix: name the document a refused Markdown link is in, and report all of them

#95
Open glennmichael123 wants to merge fix/collect-every-refused-link into main
3 files +195 -7
7 days ago

Closes #92. Closes #93.

Two commits, each reviewable on its own: the first makes a refused link say where it was written, the second reports all of them instead of the first.

Both link failures in resolveLink named the destination and nothing else, which is the half the reader already knows: the destination is what is missing, and the file holding the link is what has to be edited. On a docs set of any size the only way to act on the message was to grep for the string.

The throw site already had the answer. resolveLink reads page.sourceFile on the line above, to resolve the destination it then rejects:

error: guides/start.md: Markdown link has no documentation source: ../adr/absent.md

Relative to the documentation root, because an absolute temporary path is noise in a build log. Under an include it names the included file rather than its host, because rewriteMarkdownSourceLinks hands resolveLink the innermost origin on its stack. That is the file somebody has to open, and the two differ exactly when the message matters most, so there is a test for it.

A documentation set with N dead links took N builds to clean up: fix one, rebuild, learn the next. Each run is a full build and the count is unknowable until the last one passes. That is felt hardest on the first build after adopting the check, when a set that has never been link-checked is checked all at once.

resolveLink now takes an optional collector. With one it records the failure and returns the destination untouched; without one it throws exactly as before, which keeps the dev server failing fast: it renders a single page on request and has no later point to report at. The build passes a collector, renders every page, and throws once:

4 Markdown links could not be resolved:
  guide.md  ./missing-one.md  (no documentation source) (3 times)
  other.md  ./missing-two.md  (no documentation source)

Repeats are grouped with a count rather than printed as identical rows. There is no line number: by the time links are resolved the markdown has already become HTML, so the authored position is gone, and a fabricated one would be worse than none. The case this came from was three links to one missing file in a single document.

Nothing changes about what is published

The throw is still inside withBuildOutput, before anything is moved into place, so a build with a dead link still refuses and still leaves the previous complete output untouched. The existing tests covering that behaviour pass unchanged. Returning the destination untouched during a collecting render is safe for the same reason: that HTML is never published, and leaving it alone keeps the failing page readable while it is being looked at.

Tests

Five, in test/markdown-source-links.test.ts beside the existing coverage for this area. Each behavioural one was run against the unpatched source first and fails there:

Expected substring: "guides/start.md: Markdown link has no documentation source: ../adr/absent.md"
Received message:   "Markdown link has no documentation source: ../adr/absent.md"

Expected to contain: "3 Markdown links could not be resolved"
Received: "Error during build: Error: guides/start.md: Markdown link has no documentation source: ./absent-one.md"

Full suite: 615 pass, 0 fail. Lint unchanged at the 3 pre-existing warnings in README.md and serve.ts, verified identical with and without these changes. tsc --noEmit clean.

🤖 Generated with Claude Code

3 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.