diff --git a/scripts/check-doc-refs.sh b/scripts/check-doc-refs.sh
index d6ae0a8f..2e90f896 100755
--- a/scripts/check-doc-refs.sh
+++ b/scripts/check-doc-refs.sh
@@ -3,15 +3,14 @@
#
# A pointer at a doc is the hive's preferred alternative to duplicating that
# doc's prose next to the code. That trade only pays while the pointer
-# resolves, and nothing was checking. A `docs/` reorganisation left 15 distinct
-# dead paths across nix, css, html, js, markdown and .prettierignore.
+# resolves, and nothing was checking.
#
-# Emits a CI error annotation per hit and exits 1 if any pointer is dead, 0
-# otherwise. Runs as its own job; seconds, pure git+grep.
+# Emits a CI error annotation per hit; exit 1 if any pointer is dead.
#
# Three arms, because a pointer is written three ways:
# root — a repo-root-relative path in prose or a comment, `docs/
/.md`
-# rel — a markdown link resolved against the linking file, [x](..//.md)
+# rel — a markdown link resolved against the linking file, dot-prefixed
+# `[x](..//.md)` or bare `[x](/.md)`
# path — any repo-relative source path, `/src/.rs`, `nix/.nix`
# (spelled with placeholders on purpose: a literal example path here would be
# a dead pointer this script then reports against itself)
@@ -31,7 +30,7 @@
set -eu
root_pattern='docs/[A-Za-z0-9._/-]*\.md'
-rel_pattern='\]\(\.\.*/[A-Za-z0-9._/-]*\.md'
+rel_pattern='\]\([A-Za-z0-9._][A-Za-z0-9._/-]*\.md'
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
@@ -66,25 +65,34 @@ while IFS=: read -r file lineno match; do
[ -n "${match:-}" ] || continue
exempt "$file" "$lineno" && continue
link="${match#](}"
- # A `.rs` doc comment has a second renderer, and it does not count `../`
- # the way the rest of this arm assumes. rustdoc emits the href verbatim
- # onto a page at `target/doc///`, so the link resolves
- # against *that* directory, not the source file's. Resolving from the
- # source therefore certifies links that render broken — a false pass, not
- # a miss, and worse than no check.
+ # This arm resolves against the directory of the file the link is written
+ # in, so it can only judge a file that is rendered where it is stored — a
+ # tracked `.md`. A link inside a nix or rust string literal is text for a
+ # document generated somewhere else (`$out`, another repo), and resolving
+ # it from the source directory certifies a path no reader ever requests.
+ # `lint:allow` is not the answer for those: the marker would sit inside
+ # the generated text and ship in the published page.
#
- # Refused rather than resolved rustdoc-aware: the repo has zero relative
- # links in `.rs` doc comments (measured on this commit), so nothing needs
- # the second reader, and the backticked repo-root form the root arm above
- # already gates works under both. Inferring module nesting would be
- # machinery in service of a form nothing uses.
+ # rustdoc is the one non-md renderer that earns an error instead of a
+ # skip. It emits the href verbatim onto a page at
+ # `target/doc///`, so a relative link in a doc comment
+ # resolves against *that* directory and renders broken; the backticked
+ # repo-root form the root arm gates works under both renderers. Doc
+ # comments only — inferring module nesting to resolve one properly would
+ # be machinery in service of a form nothing uses.
case "$file" in
+ *.md) ;;
*.rs)
- dead=$((dead + 1))
- printf '::error file=%s,line=%s::relative markdown link in a rust doc comment renders against the rustdoc output directory, not this file; use a backticked repo-root pointer instead: %s\n' \
- "$file" "$lineno" "$link"
+ case "$(sed -n "${lineno}p" "$file")" in
+ *///* | *//!*)
+ dead=$((dead + 1))
+ printf '::error file=%s,line=%s::relative markdown link in a rust doc comment renders against the rustdoc output directory, not this file; use a backticked repo-root pointer instead: %s\n' \
+ "$file" "$lineno" "$link"
+ ;;
+ esac
continue
;;
+ *) continue ;;
esac
# Resolved from the linking file's directory, so `../` counts the same way
# the renderer counts it. `-m` keeps a link that escapes the repo root