-
Notifications
You must be signed in to change notification settings - Fork 2
/
anchor-links.ts
94 lines (87 loc) · 2.71 KB
/
anchor-links.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
import FS from "node:fs/promises";
import Path from "node:path";
import * as Cheerio from "cheerio";
import { slugToFilePath } from "../utils.js";
import { contentPath, type FileContext } from "../context.js";
// Remember to run yarn build files/en-us/web/javascript/**/*.md
async function* getFiles(dir: string): AsyncGenerator<[string, string]> {
const dirents = await FS.readdir(dir, { withFileTypes: true });
for (const dirent of dirents) {
const res = Path.join(dir, dirent.name);
if (dirent.isDirectory()) yield* getFiles(res);
else if (res.endsWith(".html"))
yield Promise.all([res, FS.readFile(res, "utf8")]);
}
}
const buildDir = Path.join(contentPath, "build");
const files = getFiles(Path.join(buildDir, "/en-us/docs/web/javascript"));
const pathToIds = new Map<string, string[]>();
const pathToLinks = new Map<string, URL[]>();
for await (const [filename, content] of files) {
const $ = Cheerio.load(content);
pathToLinks.set(
filename,
$("a")
.map((i, el) => $(el).attr("href"))
.get()
.map(
(url) =>
new URL(
url.startsWith("#")
? `${filename
.replace(/\/index.html/g, "")
.replace(buildDir, "")}${url}`
: url,
"https://developer.mozilla.org",
),
)
.filter(
(url) =>
url.hostname === "developer.mozilla.org" &&
url.hash &&
url.hash !== "#languages-switcher-button",
),
);
pathToIds.set(
filename,
$("[id]")
.map((i, el) => $(el).attr("id"))
.get(),
);
}
export default function rule(context: FileContext): void {
const failures = [];
const urls = pathToLinks.get(
context.path
.replace("files/en-us", "build/en-us/docs")
.replace(".md", ".html"),
);
if (!urls) throw new Error(`Invalid path received: ${context.path}`);
for (const url of urls) {
const referencedPath = Path.join(
buildDir,
"en-us/docs",
slugToFilePath(url.pathname).replace("files/en-us", ""),
"index.html",
);
const ids = pathToIds.get(referencedPath);
if (ids) {
const target = ids.find((id) => id === decodeURI(url.hash.slice(1)));
if (!target) {
failures.push(
`Anchor not found: ${url
.toString()
.replace("https://developer.mozilla.org", "")}`,
);
}
} else if (url.pathname.includes("Web/JavaScript")) {
// We don't check non-JS pages because we don't always build them
failures.push(`Page not found: ${url}`);
}
}
failures.forEach((f) => {
context.report(f);
});
}
Object.defineProperty(rule, "name", { value: "anchor-links" });
rule.appliesTo = () => "all";