Repository navigation
test(docs): read guide links through linkDestinations instead of a regex scan - #564
Merged
Merged
Conversation
The guides' three link tests (orphan pages, every relative link resolves to a file, every #fragment names a heading) scanned the markdown source with a regex over the lines outside fences and dropped targets matching a scheme with `//` or `mailto:`. They now read the rendered page through linkDestinations, which the file already had: Bun.markdown.html renders, HTMLRewriter reads every href and src, and `entities` decodes character references. The hand-rolled regex scan is deleted and the orphan test shares the same helper. linkTargets returns a record per link: the destination as rendered, and the path and the fragment split on the rendered destination and percent-decoded apart through node:querystring's unescape, which decodes every valid escape and leaves a bare % as written, as a server does. An encoded %23 so stays part of the path, and a destination holding both a bare % and a valid escape decodes, where a whole-string decodeURIComponent threw and fell back to the raw text. Differential, old scan against new, over every page the tests read (42 docs/ pages plus README.md, CONTRIBUTING.md, and .github/SECURITY.md): 424 targets each, identical on all 45 files, so no test's verdict changes. Over 3000 generated documents composed from 33 link forms, the new scan equals the browser's answer in all 3000 and the old scan in 631. The forms they differ on: a titled destination, an angle-bracketed one, a nested-parenthesis title, the three reference-style forms, a raw HTML anchor and img, a tel: scheme, a link-shaped code span, an escaped bracket, and percent-, entity-, and backslash-encoded paths. The new test pins each of those with the record it expects, plus the encoded # and the mixed bare-and-escaped percent. The old scan's unclosed-fence throw has its successor in the heading test, which still runs linesOutsideFences over the same files.
Contributor
File size check0 over a hard cap (fails), 6 warning(s).
Split the file, wrap the line, shorten or exempt the comment, or list the path in 5 managed file(s) skipped; repo-platform owns them. |
There was a problem hiding this comment.
🟡 Changes recommended
Valid rendered links can still cause false documentation failures because URL normalization and external-link filtering are incomplete.
2 open issues
What changed in this PR
This PR replaces the documentation tests' regex-based link scan with the existing rendered-HTML extractor, bringing link validation closer to browser behavior.
Changes:
- Reuses
linkDestinationsfor local-file, fragment, and orphan-page checks. - Decodes paths and fragments separately with tolerant percent decoding.
- Adds mutation coverage for link forms the regex handled incorrectly.
| File | Description |
|---|---|
test/docs/guides.test.ts |
Refactors link extraction and decoding, updates guards, and adds regression cases. |
Review effort: Balanced
Two destination forms the first-# split read wrong: a network path (`<img src="//cdn.example/x.png">`) or a scheme outside `[a-z]` (`git+ssh://...`) passed the `^[a-z]+:` test and was reported as a missing file, and a padded raw-HTML href (`<a href=" guide.md ">`) or a destination with a query (`guide.md?view=1`) kept the padding or the query in the path and was reported missing although the file exists. linkTargets now takes the page's path from the repository root and resolves every rendered destination with URL.parse against a base on SITE_ORIGIN, the origin no real link names that linksLeavingDocs already uses. A destination the parser rejects or that lands on another origin (an absolute URL, a `//host/` network path, a non-special scheme) is external; otherwise the path is the pathname from the repository root, percent-decoded as before, and the fragment the decoded hash. The parser trims padding and splits the query before anything is decoded, so an encoded %23 still stays in the path. LinkTarget.path is repository-relative now, so the three link tests resolve with join(ROOT, path) and the fragment-only special case is gone: such a link resolves to its page. Differential over the 45 pages the tests read (42 docs/ pages, README.md, CONTRIBUTING.md, .github/SECURITY.md), each link normalized to the file it names plus its fragment: the regex scan, the first-# split, and the URL-parser resolution give 424 links each and no page differs between any two. The fixture gained the four forms, with repository-relative expectations; a mutation back to the first-# split fails exactly those four entries.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What this changes
How
//host/network paths, and non-special schemes such asgit+ssh:.%23stays in the path and50%%20done.mdreads as50% done.md.linkDestinations, already in the same file; the WHATWGURLandentitiescover resolution and decoding.Proof
Technical details
a.md#yields no fragment (the page itself); a malformed absolute reference such ashttps://is skipped as external.LinkTarget.pathis repository-relative, so the three tests resolve withjoin(ROOT, path)and the fragment-only special case is gone.test/docs/guides.test.ts.