diff --git a/package-lock.json b/package-lock.json index 57c64b93..18f2d392 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "html-minifier-terser": "^7.2.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.0", - "remark-html": "^16.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-stringify": "^11.0.0", @@ -1389,21 +1388,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-sanitize": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/hast-util-sanitize/-/hast-util-sanitize-5.0.1.tgz", - "integrity": "sha512-IGrgWLuip4O2nq5CugXy4GI2V8kx4sFVy5Hd4vF7AR2gxS0N9s7nEAVUyeMtZKZvzrxVsHt73XdTsno1tClIkQ==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@ungap/structured-clone": "^1.2.0", - "unist-util-position": "^5.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/hast-util-to-html": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.3.tgz", @@ -3283,23 +3267,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remark-html": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/remark-html/-/remark-html-16.0.1.tgz", - "integrity": "sha512-B9JqA5i0qZe0Nsf49q3OXyGvyXuZFDzAP2iOFLEumymuYJITVpiH1IgsTEwTpdptDmZlMDMWeDmSawdaJIGCXQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "hast-util-sanitize": "^5.0.0", - "hast-util-to-html": "^9.0.0", - "mdast-util-to-hast": "^13.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", diff --git a/package.json b/package.json index 9ac2c816..4b151771 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,6 @@ "html-minifier-terser": "^7.2.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.0", - "remark-html": "^16.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-stringify": "^11.0.0", diff --git a/shiki.config.mjs b/shiki.config.mjs index d53dddcc..aab14243 100644 --- a/shiki.config.mjs +++ b/shiki.config.mjs @@ -30,7 +30,7 @@ export default { // Only register the languages that the API docs use // and override the JavaScript language with the aliases langs: [ - { ...javaScriptLanguage[0], aliases: ['mjs', 'cjs', 'js'] }, + ...httpLanguage, ...jsonLanguage, ...typeScriptLanguage, ...shellScriptLanguage, @@ -40,7 +40,7 @@ export default { ...diffLanguage, ...cLanguage, ...cPlusPlusLanguage, - ...httpLanguage, ...coffeeScriptLanguage, + { ...javaScriptLanguage[0], aliases: ['mjs', 'cjs', 'js'] }, ], }; diff --git a/src/constants.mjs b/src/constants.mjs index a44763f5..227506d6 100644 --- a/src/constants.mjs +++ b/src/constants.mjs @@ -58,24 +58,31 @@ export const DOC_API_SLUGS_REPLACEMENTS = [ // is a specific type of API Doc entry (e.g., Event, Class, Method, etc) // and to extract the inner content of said Heading to be used as the API doc entry name export const DOC_API_HEADING_TYPES = [ - { type: 'method', regex: /^`?([A-Z]\w+(?:\.[A-Z]\w+)*\.\w+)\([^)]*\)`?$/i }, + { + type: 'method', + regex: + /^`?(?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*((?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\]))\([^)]*\)`?$/i, + }, { type: 'event', regex: /^Event: +`?['"]?([^'"]+)['"]?`?$/i }, { type: 'class', regex: - /^Class: +`?([A-Z]\w+(?:\.[A-Z]\w+)*(?: +extends +[A-Z]\w+(?:\.[A-Z]\w+)*)?)`?$/i, + /^class: +`?((?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*[A-Z]\w+)(?: +extends +(?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*[A-Z]\w+)?`?$/i, }, { type: 'ctor', - regex: /^(?:Constructor: +)?`?new +([A-Z]\w+(?:\.[A-Z]\w+)*)\([^)]*\)`?$/i, + regex: + /^(?:Constructor: +)?`?new +((?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*[A-Z]\w+)\([^)]*\)`?$/i, }, { type: 'classMethod', - regex: /^Static method: +`?([A-Z]\w+(?:\.[A-Z]\w+)*\.\w+)\([^)]*\)`?$/i, + regex: + /^Static method: +`?(?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)*((?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\]))\([^)]*\)`?$/i, }, { type: 'property', - regex: /^(?:Class property: +)?`?([A-Z]\w+(?:\.[A-Z]\w+)*\.\w+)`?$/i, + regex: + /^(?:Class property: +)?`?(?:(?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\])\.?)+((?:(?:(?:\\?_)+|\b)\w+\b|\\?\[[\w.]+\\?\]))`?$/i, }, ]; diff --git a/src/generators/legacy-html/assets/api.js b/src/generators/legacy-html/assets/api.js index a2e3c5fb..7bb67a21 100644 --- a/src/generators/legacy-html/assets/api.js +++ b/src/generators/legacy-html/assets/api.js @@ -165,8 +165,6 @@ let code = ''; - console.log(parentNode); - if (flavorToggle) { if (flavorToggle.checked) { code = parentNode.querySelector('.mjs').textContent; diff --git a/src/generators/legacy-json/index.mjs b/src/generators/legacy-json/index.mjs index 8ed7e61b..edf06558 100644 --- a/src/generators/legacy-json/index.mjs +++ b/src/generators/legacy-json/index.mjs @@ -28,6 +28,7 @@ export default { dependsOn: 'ast', async generate(input, { output }) { + writeFile('tmp.json', JSON.stringify(input, null, 4)); // This array holds all the generated values for each module const generatedValues = []; diff --git a/src/generators/legacy-json/utils/buildSection.mjs b/src/generators/legacy-json/utils/buildSection.mjs index d92840a5..e037d395 100644 --- a/src/generators/legacy-json/utils/buildSection.mjs +++ b/src/generators/legacy-json/utils/buildSection.mjs @@ -1,15 +1,14 @@ -import { unified } from 'unified'; -import html from 'remark-html'; import { DEFAULT_EXPRESSION, LEADING_HYPHEN, NAME_EXPRESSION, - PARAM_EXPRESSION, RETURN_EXPRESSION, TYPE_EXPRESSION, } from '../constants.mjs'; import { buildHierarchy } from './buildHierarchy.mjs'; import parseSignature from './parseSignature.mjs'; +import { getRemarkRehype } from '../../../utils/remark.mjs'; +import { transformNodesToString } from '../../../utils/unist.mjs'; const sectionTypePlurals = { module: 'modules', @@ -32,8 +31,16 @@ const sectionTypePlurals = { */ function createMeta(entry) { const makeArrayIfNotAlready = val => (Array.isArray(val) ? val : [val]); - const { added_in, n_api_version, deprecated_in, removed_in, changes } = entry; + if ( + !added_in && + !n_api_version && + !deprecated_in && + !removed_in && + changes.length < 1 + ) { + return undefined; + } return { changes, added: added_in ? makeArrayIfNotAlready(added_in) : undefined, @@ -53,62 +60,42 @@ function createMeta(entry) { * @returns {import('../types.d.ts').Section} */ function createSection(entry, head) { - const text = textJoin(head.children); - + const text = transformNodesToString(head.children); return { textRaw: text, + name: head.data.name, type: head.data.type, - name: text.toLowerCase().replaceAll(' ', '_'), - displayName: head.data.name, meta: createMeta(entry), introduced_in: entry.introduced_in, }; } -/** - * @param {Array} nodes - */ -function textJoin(nodes) { - return nodes - .map(node => { - switch (node.type) { - case 'strong': - return `**${textJoin(node.children)}**`; - case 'emphasis': - return `_${textJoin(node.children)}_`; - case 'link': { - return `[${node.label}][]`; - } - default: - if (node.children) { - return textJoin(node.children); - } - - return node.value; - } - }) - .join(''); -} - /** * Find name, type, default, desc properties * @param {import('mdast').ListItem} child + * @param {import('../types.d.ts').HierarchizedEntry} entry * @returns {import('../types.d.ts').List} */ -function parseListItem(child) { +function parseListItem(child, entry) { /** * @type {import('../types.d.ts').List} */ const current = {}; - current.textRaw = textJoin( - child.children.filter(node => node.type !== 'list') - ) + current.textRaw = child.children + .filter(node => node.type !== 'list') + .map(node => + entry.rawContent.slice( + node.position.start.offset, + node.position.end.offset + ) + ) + .join('') .replace(/\s+/g, ' ') .replaceAll(//gs, ''); if (!current.textRaw) { - throw new Error(`empty list item: ${JSON.stringify(child)}`); + throw new Error(`Empty list item: ${JSON.stringify(child)}`); } let text = current.textRaw; @@ -116,14 +103,6 @@ function parseListItem(child) { // Extract name if (RETURN_EXPRESSION.test(text)) { current.name = 'return'; - - let matchResult = text.match(/`(.*?)`/); - if (matchResult) { - let returnType = matchResult[1]; - returnType = returnType.substring(1, returnType.length - 1); - current.type = returnType; - } - text = text.replace(RETURN_EXPRESSION, ''); } else { const [, name] = text.match(NAME_EXPRESSION) || []; @@ -131,13 +110,13 @@ function parseListItem(child) { current.name = name; text = text.replace(NAME_EXPRESSION, ''); } + } - // Extract type (if provided) - const [, type] = text.match(TYPE_EXPRESSION) || []; - if (type) { - current.type = type; - text = text.replace(TYPE_EXPRESSION, ''); - } + // Extract type (if provided) + const [, type] = text.match(TYPE_EXPRESSION) || []; + if (type) { + current.type = type; + text = text.replace(TYPE_EXPRESSION, ''); } // Remove leading hyphens @@ -157,7 +136,9 @@ function parseListItem(child) { const options = child.children.find(child => child.type === 'list'); if (options) { - current.options = options.children.map(child => parseListItem(child)); + current.options = options.children.map(child => + parseListItem(child, entry) + ); } return current; @@ -171,28 +152,6 @@ function handleEntry(entry, parentSection) { // Clone the children so we don't mess with any other generators let [headingNode, ...nodes] = structuredClone(entry.content.children); - /** - * @returns {import('../types.d.ts').Section} - */ - const setupSection = () => { - // Create the section object with base data we know now - const section = createSection(entry, headingNode); - - // Get the plural type of the section (e.g. 'modules' for type 'module') - const pluralType = sectionTypePlurals[section.type]; - - // Check if our parent section has a array property with the plural type - // already, create it if not - if (!(pluralType in parentSection)) { - parentSection[pluralType] = []; - } - - // Add this section to our parent - parentSection[sectionTypePlurals[section.type]].push(section); - - return section; - }; - /** * Grabs stability number & text and adds it to the section * @param {import('../types.d.ts').Section} section @@ -208,7 +167,7 @@ function handleEntry(entry, parentSection) { node.children[0].type === 'paragraph' && nodes.slice(0, i).every(node => node.type === 'list') ) { - const text = textJoin(node.children[0].children); + const text = transformNodesToString(node.children[0].children); const stability = /^Stability: ([0-5])(?:\s*-\s*)?(.*)$/s.exec(text); if (stability) { section.stability = parseInt(stability[1], 10); @@ -230,23 +189,21 @@ function handleEntry(entry, parentSection) { * * @param {import('../types.d.ts').Section} section */ - const parseListIfThereIsOne = section => { + const parseList = section => { const list = nodes.length && nodes[0].type === 'list' ? nodes.shift() : null; - if (!list) { - return; - } /** * @type {Array} */ - const values = list ? list.children.map(child => parseListItem(child)) : []; + const values = list + ? list.children.map(child => parseListItem(child, entry)) + : []; switch (section.type) { case 'ctor': case 'classMethod': case 'method': { section.signatures = [parseSignature(section.textRaw, values)]; - break; } @@ -281,7 +238,9 @@ function handleEntry(entry, parentSection) { default: // List wasn't consumed, add it back - nodes.unshift(list); + if (list !== null) { + nodes.unshift(list); + } } }; @@ -298,14 +257,10 @@ function handleEntry(entry, parentSection) { } // Render the description as if it was html - section.desc = unified() - .use(function () { - this.Parser = () => ({ type: 'root', children: nodes }); - }) - .use(html, { sanitize: false }) - .processSync('') - .toString() - .trim(); + const html = getRemarkRehype(); + section.desc = html.stringify( + html.runSync({ type: 'root', children: nodes }) + ); if (!section.desc) { // Rendering returned nothing @@ -325,17 +280,51 @@ function handleEntry(entry, parentSection) { entry.hierarchyChildren.forEach(child => handleEntry(child, section)); }; + /** + * @param {import('../types.d.ts').Section} section + * @param {import('../types.d.ts').Section} parentSection + */ + const addAdditionalMetadata = (section, parentSection) => { + if (section.type === 'module') { + section.name = section.textRaw + .toLowerCase() + .trim() + .replaceAll(/\s+/g, '_'); + section.displayName = headingNode.data.name; + } + + const type = + parentSection.type === 'misc' && section.type === 'module' + ? 'misc' + : section.type; + section.type = type; + }; + + const addToParent = (section, parentSection) => { + // Get the plural type of the section (e.g. 'modules' for type 'module') + const pluralType = sectionTypePlurals[section.type]; + + // Check if our parent section has a array property with the plural type + // already, create it if not + if (!(pluralType in parentSection)) { + parentSection[pluralType] = []; + } + + // Add this section to our parent + parentSection[sectionTypePlurals[section.type]].push(section); + }; + /** * @param {import('../types.d.ts').Section} section * @param {import('../types.d.ts').Section} parentSection */ const makeChildrenTopLevelIfMisc = (section, parentSection) => { - if (parentSection.type !== 'misc') { + if (section.type !== 'misc' || parentSection.type === 'misc') { return; } for (const key of Object.keys(section)) { - if (key in ['textRaw', 'name', 'type', 'desc', 'miscs']) { + if (['textRaw', 'name', 'type', 'desc', 'miscs'].includes(key)) { continue; } @@ -349,16 +338,20 @@ function handleEntry(entry, parentSection) { } }; - const section = setupSection(); + const section = createSection(entry, headingNode); parseStability(section); - parseListIfThereIsOne(section); + parseList(section); addDescription(section); handleChildren(section); + addAdditionalMetadata(section, parentSection); + + addToParent(section, parentSection); + makeChildrenTopLevelIfMisc(section, parentSection); if (section.type === 'property') { diff --git a/src/generators/legacy-json/utils/parseSignature.mjs b/src/generators/legacy-json/utils/parseSignature.mjs index 3508ede9..a28983fb 100644 --- a/src/generators/legacy-json/utils/parseSignature.mjs +++ b/src/generators/legacy-json/utils/parseSignature.mjs @@ -31,31 +31,30 @@ function parseNameAndOptionalStatus(parameterName, optionalDepth) { // name. This will tell us where the parameter's name ends. let endingIdx = parameterName.length - 1; for (; endingIdx >= 0; endingIdx--) { - const levelChange = OPTIONAL_LEVEL_CHANGES[parameterName[startingIdx]]; - + const levelChange = OPTIONAL_LEVEL_CHANGES[parameterName[endingIdx]]; if (!levelChange) { break; } optionalDepth += levelChange; } - console.log('', startingIdx, endingIdx) + return [ parameterName.substring(startingIdx, endingIdx + 1), optionalDepth, - isParameterOptional + isParameterOptional, ]; } /** * @param {string} parameterName - * @returns {[string, string | undefined]} + * @returns {[string, string | undefined]} */ function parseDefaultValue(parameterName) { /** * @type {string | undefined} */ - let defaultValue + let defaultValue; const equalSignPos = parameterName.indexOf('='); if (equalSignPos !== -1) { @@ -66,28 +65,28 @@ function parseDefaultValue(parameterName) { parameterName = parameterName.substring(0, equalSignPos); } - return [parameterName, defaultValue] + return [parameterName, defaultValue]; } /** - * @param {string} parameterName - * @param {number} index - * @param {Array} markdownParameters + * @param {string} parameterName + * @param {number} index + * @param {Array} markdownParameters * @returns {import('../types.d.ts').Parameter} */ function findParameter(parameterName, index, markdownParameters) { - let parameter = markdownParameters[index] + let parameter = markdownParameters[index]; if (parameter && parameter.name === parameterName) { - return parameter + return parameter; } // Method likely has multiple signatures, something like - // `new Console(stdout[, stderr][, ignoreErrors])` and `new Console(options)` + // `new Console(stdout[, stderr][, ignoreErrors])` and `new Console(options)` // Try to find the parameter that this is being shared with for (const markdownProperty of markdownParameters) { if (markdownProperty.name === parameterName) { // Found it - return markdownParameters + return markdownParameters; } else if (markdownProperty.options) { for (const option of markdownProperty.options) { if (option.name === parameterName) { @@ -102,9 +101,7 @@ function findParameter(parameterName, index, markdownParameters) { if (parameterName.startsWith('...')) { return { name: parameterName }; } else { - throw new Error( - `Invalid param "${parameterName}"` - ); + throw new Error(`Invalid param "${parameterName}"`); } } @@ -138,30 +135,28 @@ function parseParameters(declaredParameters, markdownParameters) { * This will handle the first and second thing for us * @type {boolean} */ - console.log(parameterName) let isParameterOptional; [parameterName, optionalDepth, isParameterOptional] = parseNameAndOptionalStatus(parameterName, optionalDepth); - console.log('', parameterName) /** * Now let's work on the third thing * @type {string | undefined} */ let defaultValue; - [parameterName, defaultValue] = parseDefaultValue(parameterName) + [parameterName, defaultValue] = parseDefaultValue(parameterName); - const parameter = findParameter(parameterName, i, markdownParameters) + const parameter = findParameter(parameterName, i, markdownParameters); if (isParameterOptional) { - parameter.optional = true + parameter.optional = true; } if (defaultValue) { - parameter.default = defaultValue + parameter.default = defaultValue; } - parameters.push(parameter) + parameters.push(parameter); }); return parameters; @@ -176,7 +171,7 @@ export default (textRaw, markdownParameters) => { /** * @type {import('../types.d.ts').MethodSignature} */ - const signature = {}; + const signature = { params: [] }; // Find the return value & filter it out markdownParameters = markdownParameters.filter(value => { @@ -192,10 +187,11 @@ export default (textRaw, markdownParameters) => { * Extract the parameters from the method's declaration * @example `[sources[, options]]` */ - let [, declaredParameters] = `\`${textRaw}\``.match(PARAM_EXPRESSION) || []; + let [, declaredParameters] = + textRaw.substring(1, textRaw.length - 1).match(PARAM_EXPRESSION) || []; if (!declaredParameters) { - return undefined; + return signature; } /** @@ -207,4 +203,4 @@ export default (textRaw, markdownParameters) => { signature.params = parseParameters(declaredParameters, markdownParameters); return signature; -} +}; diff --git a/src/metadata.mjs b/src/metadata.mjs index 389a3e59..167aaf2f 100644 --- a/src/metadata.mjs +++ b/src/metadata.mjs @@ -156,6 +156,7 @@ const createMetadata = slugger => { stability: internalMetadata.stability, content: section, tags, + rawContent: apiDoc.toString(), }; if (introduced_in) { diff --git a/src/parser.mjs b/src/parser.mjs index 3c05b64f..c4c34347 100644 --- a/src/parser.mjs +++ b/src/parser.mjs @@ -140,8 +140,8 @@ const createParser = () => { // Visits all Text nodes from the current subtree and if there's any that matches // any API doc type reference and then updates the type reference to be a Markdown link - visit(subTree, createQueries.UNIST.isTextWithType, node => - updateTypeReference(node) + visit(subTree, createQueries.UNIST.isTextWithType, (node, _, parent) => + updateTypeReference(node, parent) ); // Removes already parsed items from the subtree so that they aren't included in the final content diff --git a/src/queries.mjs b/src/queries.mjs index 8937539c..55022d7f 100644 --- a/src/queries.mjs +++ b/src/queries.mjs @@ -12,12 +12,14 @@ import { parseYAMLIntoMetadata, transformTypeToReferenceLink, } from './utils/parser.mjs'; +import { getRemark } from './utils/remark.mjs'; /** * Creates an instance of the Query Manager, which allows to do multiple sort * of metadata and content metadata manipulation within an API Doc */ const createQueries = () => { + const remark = getRemark(); /** * Sanitizes the YAML source by returning the inner YAML content * and then parsing it into an API Metadata object and updating the current Metadata @@ -71,15 +73,17 @@ const createQueries = () => { * into a Markdown link referencing to the correct API docs * * @param {import('mdast').Text} node A Markdown link node + * @param {import('mdast').Parent} parent The parent node */ - const updateTypeReference = node => { + const updateTypeReference = (node, parent) => { const replacedTypes = node.value.replace( createQueries.QUERIES.normalizeTypes, transformTypeToReferenceLink ); - node.type = 'html'; - node.value = replacedTypes; + const newNode = remark.parse(replacedTypes); + const index = parent.children.indexOf(node); + parent.children.splice(index, 1, ...newNode.children); return [SKIP]; }; diff --git a/src/types.d.ts b/src/types.d.ts index f40eb7fe..e372cce7 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -98,6 +98,8 @@ declare global { // Extra YAML section entries that are stringd and serve // to provide additional metadata about the API doc entry tags: Array; + // The raw file content + rawContent: string; } export interface ApiDocReleaseEntry {