diff --git a/.link-checker.js b/.link-checker.js index d3a1ff3fda..2377e0ef6a 100644 --- a/.link-checker.js +++ b/.link-checker.js @@ -11,9 +11,16 @@ module.exports = { '.carousel-image a', ], }, + hashCheckHandlers: [ + { + name: 'github', + pattern: /^https:\/\/github\.com\/.*/, + matchHash: (ids, hash) => + ids.includes(hash) || ids.includes(`user-content-${hash}`), + }, + ], ignoreHashesOnExternalPagesMatchingRegex: [ // Some hash links are resolved with JS and are therefore difficult to check algorithmically - /^https:\/\/github\.com\/.*\/wiki\//, /^https:\/\/html\.spec\.whatwg\.org\/multipage\//, ], }; diff --git a/scripts/link-checker.js b/scripts/link-checker.js index 2cf249d217..f8c8649cc0 100644 --- a/scripts/link-checker.js +++ b/scripts/link-checker.js @@ -33,6 +33,23 @@ async function checkLinks() { return getLineNumber; }; + const checkPathForHash = (hrefOrSrc, ids = [], hash) => { + // On some websites, the ids may not exactly match the hash included + // in the link. + // For e.g. GitHub will prepend client facing ids with their own + // calculated value. A heading in a README for example could be + // 'Foo bar', navigated to with https://github.com/foo/bar#foo-bar, + // but GitHub calculates the actual markup id included in the document + // as being 'user-content-foo-bar' for its own page processing purposes. + // + // See https://github.com/w3c/aria-practices/issues/2809 + const handler = options.hashCheckHandlers.find(({ pattern }) => + pattern.test(hrefOrSrc) + ); + if (handler) return handler.matchHash(ids, hash); + else return ids.includes(hash); + }; + const countConsoleErrors = () => { let errorCount = 0; @@ -227,7 +244,11 @@ async function checkLinks() { let matchesHash = true; if (hash) { - matchesHash = !!matchingPage?.ids.includes(hash); + matchesHash = !!checkPathForHash( + pathMinusHash, + matchingPage?.ids, + hash + ); } const isLinkBroken = !( @@ -274,7 +295,11 @@ async function checkLinks() { hrefOrSrc.match(pattern) ); - if (!isHashCheckingDisabled && hash && !pageData.ids.includes(hash)) { + if ( + !isHashCheckingDisabled && + hash && + !checkPathForHash(hrefOrSrc, pageData.ids, hash) + ) { consoleError( `Found broken external link on ${htmlPath}:${lineNumber}:${columnNumber}, ` + 'hash not found on page'