-
Notifications
You must be signed in to change notification settings - Fork 7.6k
/
generateHeadingIDs.js
108 lines (90 loc) · 2.94 KB
/
generateHeadingIDs.js
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*/
// To do: Make this ESM.
// To do: properly check heading numbers (headings with the same text get
// numbered, this script doesn’t check that).
const assert = require('assert');
const fs = require('fs');
const GithubSlugger = require('github-slugger');
let modules
function walk(dir) {
let results = [];
const list = fs.readdirSync(dir);
list.forEach(function (file) {
file = dir + '/' + file;
const stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
/* Recurse into a subdirectory */
results = results.concat(walk(file));
} else {
/* Is a file */
results.push(file);
}
});
return results;
}
function stripLinks(line) {
return line.replace(/\[([^\]]+)\]\([^)]+\)/, (match, p1) => p1);
}
function addHeaderID(line, slugger) {
// check if we're a header at all
if (!line.startsWith('#')) {
return line;
}
const match = /^(#+\s+)(.+?)(\s*\{(?:\/\*|#)([^\}\*\/]+)(?:\*\/)?\}\s*)?$/.exec(line);
const before = match[1] + match[2]
const proc = modules.unified().use(modules.remarkParse).use(modules.remarkSlug)
const tree = proc.runSync(proc.parse(before))
const head = tree.children[0]
assert(head && head.type === 'heading', 'expected `' + before + '` to be a heading, is it using a normal space after `#`?')
const autoId = head.data.id
const existingId = match[4]
const id = existingId || autoId
// Ignore numbers:
const cleanExisting = existingId ? existingId.replace(/-\d+$/, '') : undefined
const cleanAuto = autoId.replace(/-\d+$/, '')
if (cleanExisting && cleanExisting !== cleanAuto) {
console.log('Note: heading `%s` has a different ID (`%s`) than what GH generates for it: `%s`:', before, existingId, autoId)
}
return match[1] + match[2] + ' {/*' + id + '*/}';
}
function addHeaderIDs(lines) {
// Sluggers should be per file
const slugger = new GithubSlugger();
let inCode = false;
const results = [];
lines.forEach((line) => {
// Ignore code blocks
if (line.startsWith('```')) {
inCode = !inCode;
results.push(line);
return;
}
if (inCode) {
results.push(line);
return;
}
results.push(addHeaderID(line, slugger));
});
return results;
}
const [path] = process.argv.slice(2);
main()
async function main() {
const [unifiedMod, remarkParseMod, remarkSlugMod] = await Promise.all([import('unified'), import('remark-parse'), import('remark-slug')])
const unified = unifiedMod.default
const remarkParse = remarkParseMod.default
const remarkSlug = remarkSlugMod.default
modules = {unified, remarkParse, remarkSlug}
const files = walk(path);
files.forEach((file) => {
if (!(file.endsWith('.md') || file.endsWith('.mdx'))) {
return;
}
const content = fs.readFileSync(file, 'utf8');
const lines = content.split('\n');
const updatedLines = addHeaderIDs(lines);
fs.writeFileSync(file, updatedLines.join('\n'));
});
}