You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
108 lines
2.9 KiB
108 lines
2.9 KiB
/**
|
|
* 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'));
|
|
});
|
|
|
|
}
|
|
|