// Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. import fs from 'fs'; import path from 'path'; import highlightJs from 'highlight.js'; import raw from 'rehype-raw'; import htmlStringify from 'rehype-stringify'; import gfm from 'remark-gfm'; import markdown from 'remark-parse'; import remark2rehype from 'remark-rehype'; import { unified } from 'unified'; import { visit } from 'unist-util-visit'; import * as common from './common.mjs'; import * as typeParser from './type-parser.mjs'; import buildCSSForFlavoredJS from './buildCSSForFlavoredJS.mjs'; const dynamicSizes = Object.create(null); const { highlight, getLanguage } = highlightJs; const docPath = new URL('../../doc/', import.meta.url); // Add class attributes to index navigation links. function navClasses() { return (tree) => { visit(tree, { type: 'element', tagName: 'a' }, (node) => { node.properties.class = 'nav-' + node.properties.href.replace('.html', '').replace(/\W+/g, '-'); }); }; } const gtocPath = new URL('./api/index.md', docPath); const gtocMD = fs.readFileSync(gtocPath, 'utf8') .replace(/\(([^#?]+?)\.md\)/ig, (_, filename) => `(${filename}.html)`) .replace(/^/gms, ''); const gtocHTML = unified() .use(markdown) .use(gfm) .use(remark2rehype, { allowDangerousHtml: true }) .use(raw) .use(navClasses) .use(htmlStringify) .processSync(gtocMD).toString(); const templatePath = new URL('./template.html', docPath); const template = fs.readFileSync(templatePath, 'utf8'); function processContent(content) { content = content.toString(); // Increment header tag levels to avoid multiple h1 tags in a doc. // This means we can't already have an
Source Code: ${path}
`; } else if (node.type === 'text' && node.value) { const value = linkJsTypeDocs(linkManPages(node.value)); if (value !== node.value) { node.type = 'html'; node.value = value; } } }); }; } // Syscalls which appear in the docs, but which only exist in BSD / macOS. const BSD_ONLY_SYSCALLS = new Set(['lchmod']); const MAN_PAGE = /(^|\s)([a-z.]+)\((\d)([a-z]?)\)/gm; // Handle references to man pages, eg "open(2)" or "lchmod(2)". // Returns modified text, with such refs replaced with HTML links, for example // 'open(2)'. function linkManPages(text) { return text.replace( MAN_PAGE, (match, beginning, name, number, optionalCharacter) => { // Name consists of lowercase letters, // number is a single digit with an optional lowercase letter. const displayAs = `${name}(${number}${optionalCharacter})
`;
if (BSD_ONLY_SYSCALLS.has(name)) {
return `${beginning}${displayAs}`;
}
return `${beginning}${displayAs}`;
});
}
const TYPE_SIGNATURE = /\{[^}]+\}/g;
function linkJsTypeDocs(text) {
const parts = text.split('`');
// Handle types, for example the source Markdown might say
// "This argument should be a {number} or {string}".
for (let i = 0; i < parts.length; i += 2) {
const typeMatches = parts[i].match(TYPE_SIGNATURE);
if (typeMatches) {
typeMatches.forEach((typeMatch) => {
parts[i] = parts[i].replace(typeMatch, typeParser.toLink(typeMatch));
});
}
}
return parts.join('`');
}
const isJSFlavorSnippet = (node) => node.lang === 'cjs' || node.lang === 'mjs';
// Preprocess headers, stability blockquotes, and YAML blocks.
export function preprocessElements({ filename }) {
return (tree) => {
const STABILITY_RE = /(.*:)\s*(\d)([\s\S]*)/;
let headingIndex = -1;
let heading = null;
visit(tree, null, (node, index, parent) => {
if (node.type === 'heading') {
headingIndex = index;
heading = node;
} else if (node.type === 'code') {
if (!node.lang) {
console.warn(
`No language set in ${filename}, line ${node.position.start.line}`,
);
}
const className = isJSFlavorSnippet(node) ?
`language-js ${node.lang}` :
`language-${node.lang}`;
const highlighted =
`${(getLanguage(node.lang || '') ? highlight(node.value, { language: node.lang }) : node).value}
`;
node.type = 'html';
const copyButton = '';
if (isJSFlavorSnippet(node)) {
const previousNode = parent.children[index - 1] || {};
const nextNode = parent.children[index + 1] || {};
const charCountFirstTwoLines = Math.max(...node.value.split('\n', 2).map((str) => str.length));
if (!isJSFlavorSnippet(previousNode) &&
isJSFlavorSnippet(nextNode) &&
nextNode.lang !== node.lang) {
// Saving the highlight code as value to be added in the next node.
node.value = highlighted;
node.charCountFirstTwoLines = charCountFirstTwoLines;
} else if (isJSFlavorSnippet(previousNode) &&
previousNode.lang !== node.lang) {
const actualCharCount = Math.max(charCountFirstTwoLines, previousNode.charCountFirstTwoLines);
(dynamicSizes[filename] ??= new Set()).add(actualCharCount);
node.value = `` + '' + previousNode.value + highlighted + copyButton + ''; node.lang = null; previousNode.value = ''; previousNode.lang = null; } else { // Isolated JS snippet, no need to add the checkbox. node.value = `
${highlighted} ${copyButton}`; } } else { node.value = `
${highlighted} ${copyButton}`; } } else if (node.type === 'html' && common.isYAMLBlock(node.value)) { node.value = parseYAML(node.value); } else if (node.type === 'blockquote') { const paragraph = node.children[0].type === 'paragraph' && node.children[0]; const text = paragraph && paragraph.children[0].type === 'text' && paragraph.children[0]; if (text && text.value.includes('Stability:')) { const [, prefix, number, explication] = text.value.match(STABILITY_RE); // Stability indices are never more than 3 nodes away from their // heading. const isStabilityIndex = index - headingIndex <= 3; if (heading && isStabilityIndex) { heading.stability = number; headingIndex = -1; heading = null; } // Do not link to the section we are already in. const noLinking = filename.includes('documentation') && heading !== null && heading.children[0].value === 'Stability index'; // Collapse blockquote and paragraph into a single node node.type = 'paragraph'; node.children.shift(); node.children.unshift(...paragraph.children); // Insert div with prefix and number node.children.unshift({ type: 'html', value: `