Skip to content

test(docs): read guide links through linkDestinations instead of a regex scan - #564

Merged
Vivswan merged 2 commits into
mainfrom
refactor/guides-link-destinations
Oct 27, 2026
Merged

Vivswan merged 2 commits into
mainfrom
refactor/guides-link-destinations

Conversation

@Vivswan

@Vivswan Vivswan commented Oct 26, 2026 •

Copy link
Copy Markdown
Owner

What this changes

linkTargets: a `](...)` regex over lines outside fences, a scheme filter, a try/catch decode
  ->  the file's own linkDestinations (rendered HTML, HTMLRewriter href/src, entity decoding),
      each destination resolved with URL.parse against the docs site origin

differential, old against new, the browser's reading as the referee:
  45 real pages (42 docs, README, CONTRIBUTING, SECURITY)   424 targets   0 differ
  3000 generated documents, 33 link forms                   new = browser 3000/3000, old = browser 631/3000

How

  • Thirteen link forms read differently in isolation (titles, angle brackets, reference style, raw HTML, tel:, code spans, escaped brackets, encodings); each new answer is the browser's. No real page carries one.
  • External is decided by the URL parser: a null parse or an origin other than the docs site, which covers absolute URLs, //host/ network paths, and non-special schemes such as git+ssh:.
  • The parser trims padding and splits query and fragment before the path is percent-decoded, so an encoded %23 stays in the path and 50%%20done.md reads as 50% done.md.
  • Library: linkDestinations, already in the same file; the WHATWG URL and entities cover resolution and decoding.

Proof

  • Tests: guides 253 pass; the new guard pins the thirteen forms, both decodings, and the four forms from review, each seen red against the scan it replaced.
  • Gates: typecheck, fleet biome 0, member-shape 0, line cap.
Technical details
  • A link that climbs above the repository root is clamped at the dummy origin's root, as a browser clamps at a site root, and then reported missing; a.md# yields no fragment (the page itself); a malformed absolute reference such as https:// is skipped as external.
  • LinkTarget.path is repository-relative, so the three tests resolve with join(ROOT, path) and the fragment-only special case is gone.
  • The old scan's unclosed-fence throw has its successor in the heading test over the same files.
  • Files: test/docs/guides.test.ts.

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.
Copilot AI balanced review requested due to automatic review settings October 26, 2026 20:24
@github-actions

Copy link
Copy Markdown
Contributor

File size check

0 over a hard cap (fails), 6 warning(s).

File Size Tier Cap
src/engine/diff.ts:56 11 comment lines warn 10
src/github/api-error.ts:9 11 comment lines warn 10
src/problem.ts:73 18 comment lines warn 10
src/sections/contract/errors.ts:13 11 comment lines warn 10
src/sections/contract/module.ts:55 12 comment lines warn 10
src/sections/shared/list/decl.ts:265 14 comment lines warn 10

Split the file, wrap the line, shorten or exempt the comment, or list the path in .file-size-allow.local with a # reason.

5 managed file(s) skipped; repo-platform owns them.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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 linkDestinations for 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

Comment thread test/docs/guides.test.ts Outdated
Comment thread test/docs/guides.test.ts Outdated
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.
Copilot AI balanced review requested due to automatic review settings October 26, 2026 21:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The focused test-only refactor addresses the prior feedback and has no unresolved blocking findings.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

@Vivswan
Vivswan marked this pull request as ready for review October 26, 2026 21:12
@Vivswan
Vivswan merged commit eee29eb into main Oct 27, 2026
29 checks passed
@Vivswan
Vivswan deleted the refactor/guides-link-destinations branch October 27, 2026 07:18
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