Files
react.dev/scripts/headingIDHelpers/generateHeadingIDs.js
2023-03-18 22:35:53 +00:00

111 lines
2.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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 doesnt check that).
const assert = require('assert');
const fs = require('fs');
const GithubSlugger = require('github-slugger');
const walk = require('./walk');
let modules;
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;
}
async function main(paths) {
paths = paths.length === 0 ? ['src/content'] : paths;
const [unifiedMod, remarkParseMod, remarkSlugMod] = await Promise.all([
import('unified'),
import('remark-parse'),
import('remark-slug'),
]);
const unified = unifiedMod.unified;
const remarkParse = remarkParseMod.default;
const remarkSlug = remarkSlugMod.default;
modules = {unified, remarkParse, remarkSlug};
const files = paths.map((path) => [...walk(path)]).flat();
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'));
});
}
module.exports = main;