From 0c3db01de96144f53b6df0e2f467fef932b46a9a Mon Sep 17 00:00:00 2001 From: Pedro Henrique Penna Date: Thu, 15 Oct 2026 13:50:18 -0300 Subject: [PATCH] ci: check relative Markdown links and anchors Nothing in CI checked the repository's relative Markdown links. #529 moved the design documentation, numbered its sections, and retargeted every inbound link by hand, while an older link had already broken: the MXC integration table in aci_edge_sandboxes/README.md linked the anchor native-proxy-and-forwarded-ports of a heading that #512 had renamed to "Native proxy and host loopback". GitHub opens a page at its top when an anchor is missing, so such links break silently, and so do comments that cite a document by path, as the citations of the removed time ABI specification did (#562). Add scripts/check_markdown_links.py, a standard-library check, and run it in check-quality, whose job runs for every pull request that is ready for review, including the documentation-only ones that are most likely to break a link: - It resolves every relative link and image target of the tracked Markdown files, whether an inline link, a reference definition, or the href of an HTML link or src of an image, against the files that Git tracks, so a target that is untracked or differs in case, which GitHub cannot serve, fails too. A directory resolves, and its anchors are those of its README, whatever the README's case. An empty target, such as that of [Self](), links to its own document, and an image's anchor, such as GitHub's #gh-dark-mode-only, goes unchecked. - It resolves each anchor as GitHub's script looks it up, in lowercase and with the user-content- prefix of the ids that GitHub renders, which the anchor may hold already, against the slugs that GitHub generates from the rendered text of the Markdown and HTML headings, including the numeric suffixes of repeated headings, and against the id and name attributes of the HTML elements that GitHub keeps. Other files accept only line anchors, such as #L10, within their length. - It scans the blocks of each document as cmark nests them in block quotes, list items, and GitHub's footnote definitions, with lazy continuation lines and tabs that advance to the next multiple of four columns, and strips their markers before it parses inline content. As on GitHub, front matter, fenced and indented code, and the seven kinds of HTML blocks hold no Markdown links, while the definitions and headings inside those containers count. - It parses inline content left to right as CommonMark does: code spans, autolinks, including GitHub's bare ones, and raw HTML are atomic, each destination is consumed with its title, and brackets match so that links do not nest and an image's description holds no links. A footnote renders, with its fn- and fnref- anchors, only where inline content cites it. A table is a block of its own, which ends as GitHub's table extension ends it, and each of its cells parses alone, while the cells past the header's count, which GitHub drops, hold no links. - It tokenizes the raw HTML, in the order that GitHub renders it, as an HTML parser does, to find the tags whose attributes hold targets and anchors. A comment hides everything that renders up to its end, even in later blocks, while a CDATA section, processing instruction, or declaration ends at its first >. The tags that GitHub filters, such as end\n\n' + "