check-doc-refs: catch bare relative markdown links, and only judge a file rendered where it is stored
The rel arm required a leading `./` or `../`, so `[x](swarm/services.md)` — the form a sibling link is usually written in — matched none of the three arms. That is exactly the shape a directory move breaks: during #3911 a link in that form moved into a new directory, resolved to nothing, and the lint stayed green. Reproduced by putting the broken form back with `sed` (MUTATED_DIRTY=1) and re-running: exit 0, a control that fails to fail. Widening the pattern surfaces 116 links the arm never saw (76 -> 192 matches). 111 are in `.md` files and all 111 resolve today, so this adds coverage without a single new failure in the tree; the other 5 are the interesting part, and they are why the arm now selects on file type. A link is only resolvable against its own directory when the file is *rendered where it is stored*. The four in `nix/docs/default.nix` are inside a markdown string this derivation writes to `$out`, and the one in `hive-c0re/src/workers/knowledge.rs` is template text for another repo's README — resolving either from the source directory certifies a path no reader ever requests. `lint:allow` is the wrong tool for them: the marker lives inside the generated text, so it would ship in the published page. The `.rs` arm keeps its error, narrowed to doc-comment lines. Its premise still holds — the repo has zero relative links in `.rs` doc comments — but without the narrowing the widened pattern reports the knowledge.rs template string with a message about rustdoc that is wrong for it. Measured: the old arm's 76 hits are all in `.md` files, so restricting resolution to `.md` removes no existing coverage. Closes #4076
This commit is contained in:
parent
239354d205
commit
484b8b1129
1 changed files with 28 additions and 20 deletions
|
|
@ -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/<dir>/<f>.md`
|
||||
# rel — a markdown link resolved against the linking file, [x](../<dir>/<f>.md)
|
||||
# rel — a markdown link resolved against the linking file, dot-prefixed
|
||||
# `[x](../<dir>/<f>.md)` or bare `[x](<dir>/<f>.md)`
|
||||
# path — any repo-relative source path, `<crate>/src/<f>.rs`, `nix/<f>.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/<crate>/<module…>/`, 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/<crate>/<module…>/`, 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
|
||||
|
|
|
|||
Loading…
Reference in a new issue