From 1870a1ae3a92dff0e3b7629ce82fcbdba78b277d Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Fri, 6 Mar 2026 16:18:39 -0500 Subject: [PATCH 1/8] feat(metadata): new meta format --- docs/generators.md | 2 +- src/__tests__/metadata.test.mjs | 97 ----- src/generators/addon-verify/types.d.ts | 4 +- src/generators/ast/constants.mjs | 1 + src/generators/ast/generate.mjs | 16 +- src/generators/json-simple/generate.mjs | 9 +- src/generators/json-simple/types.d.ts | 4 +- src/generators/jsx-ast/constants.mjs | 6 +- src/generators/jsx-ast/generate.mjs | 3 + src/generators/jsx-ast/types.d.ts | 5 +- .../utils/__tests__/buildBarProps.test.mjs | 4 +- .../jsx-ast/utils/buildBarProps.mjs | 24 +- src/generators/jsx-ast/utils/buildContent.mjs | 30 +- .../jsx-ast/utils/getSortedHeadNodes.mjs | 8 +- src/generators/jsx-ast/utils/signature.mjs | 8 +- src/generators/jsx-ast/utils/types.mjs | 4 +- src/generators/legacy-html-all/generate.mjs | 2 +- src/generators/legacy-html-all/types.d.ts | 2 +- src/generators/legacy-html/types.d.ts | 10 +- .../legacy-html/utils/buildContent.mjs | 54 +-- .../legacy-html/utils/buildDropdowns.mjs | 2 +- .../legacy-html/utils/buildExtraContent.mjs | 10 +- .../legacy-html/utils/tableOfContents.mjs | 12 +- src/generators/legacy-json/types.d.ts | 44 +-- .../legacy-json/utils/buildHierarchy.mjs | 6 +- .../legacy-json/utils/buildSection.mjs | 43 ++- .../legacy-json/utils/parseList.mjs | 8 +- src/generators/llms-txt/types.d.ts | 4 +- .../utils/__tests__/buildApiDocLink.test.mjs | 13 +- .../llms-txt/utils/buildApiDocLink.mjs | 4 +- src/generators/man-page/generate.mjs | 8 +- src/generators/man-page/types.d.ts | 4 +- src/generators/man-page/utils/converter.mjs | 4 +- src/generators/metadata/constants.mjs | 129 +++++++ src/generators/metadata/types.d.ts | 141 +++++++- .../metadata/utils/__tests__/parse.test.mjs | 47 +-- .../metadata/utils/__tests__/transformers.mjs | 29 ++ .../metadata/utils/__tests__/yaml.test.mjs} | 42 +-- src/generators/metadata/utils/parse.mjs | 143 +++----- .../metadata/utils}/slugger.mjs | 2 +- .../metadata/utils/transformers.mjs} | 78 +--- src/generators/metadata/utils/visitors.mjs | 154 ++++++++ src/generators/metadata/utils/yaml.mjs | 59 ++++ src/generators/orama-db/generate.mjs | 2 +- src/generators/orama-db/types.d.ts | 3 +- src/generators/orama-db/utils/title.mjs | 2 +- src/generators/sitemap/types.d.ts | 4 +- .../sitemap/utils/createPageSitemapEntry.mjs | 2 +- src/metadata.mjs | 145 -------- src/parsers/markdown.mjs | 2 +- src/parsers/types.d.ts | 7 + src/types.d.ts | 118 ------- src/utils/configuration/types.d.ts | 5 +- src/utils/generators.mjs | 16 +- src/utils/parser/constants.mjs | 128 ------- src/utils/queries/__tests__/index.test.mjs | 334 ++++++------------ src/utils/queries/constants.mjs | 4 - src/utils/queries/index.mjs | 208 +---------- src/utils/queries/utils.mjs | 4 +- 59 files changed, 912 insertions(+), 1351 deletions(-) delete mode 100644 src/__tests__/metadata.test.mjs create mode 100644 src/generators/ast/constants.mjs create mode 100644 src/generators/metadata/utils/__tests__/transformers.mjs rename src/{utils/parser/__tests__/index.test.mjs => generators/metadata/utils/__tests__/yaml.test.mjs} (61%) rename src/{utils/parser => generators/metadata/utils}/slugger.mjs (93%) rename src/{utils/parser/index.mjs => generators/metadata/utils/transformers.mjs} (66%) create mode 100644 src/generators/metadata/utils/visitors.mjs create mode 100644 src/generators/metadata/utils/yaml.mjs delete mode 100644 src/metadata.mjs create mode 100644 src/parsers/types.d.ts delete mode 100644 src/types.d.ts delete mode 100644 src/utils/parser/constants.mjs diff --git a/docs/generators.md b/docs/generators.md index 86d1d37b..11f7b651 100644 --- a/docs/generators.md +++ b/docs/generators.md @@ -137,7 +137,7 @@ export async function generate(input, worker) { /** * Transform metadata entries to MyFormat - * @param {Array} entries + * @param {Array} entries * @param {import('semver').SemVer} version * @returns {string} */ diff --git a/src/__tests__/metadata.test.mjs b/src/__tests__/metadata.test.mjs deleted file mode 100644 index 8cafb4ab..00000000 --- a/src/__tests__/metadata.test.mjs +++ /dev/null @@ -1,97 +0,0 @@ -import { strictEqual, deepStrictEqual } from 'node:assert'; -import { describe, it } from 'node:test'; - -import GitHubSlugger from 'github-slugger'; -import { u } from 'unist-builder'; -import { VFile } from 'vfile'; - -import createMetadata from '../metadata.mjs'; - -describe('createMetadata', () => { - it('should set the heading correctly', () => { - const slugger = new GitHubSlugger(); - const metadata = createMetadata(slugger); - const heading = u('heading', { - type: 'heading', - data: { - text: 'Test Heading', - type: 'test', - name: 'test', - depth: 1, - }, - }); - metadata.setHeading(heading); - strictEqual(metadata.create(new VFile(), {}).heading.data, heading.data); - }); - - it('should set the stability correctly', () => { - const slugger = new GitHubSlugger(); - const metadata = createMetadata(slugger); - const stability = { - type: 'root', - data: { index: 2, description: '' }, - children: [], - }; - metadata.addStability(stability); - const actual = metadata.create(new VFile(), {}).stability; - deepStrictEqual(actual, { - children: [stability], - type: 'root', - }); - }); - - it('should create a metadata entry correctly', () => { - const slugger = new GitHubSlugger(); - const metadata = createMetadata(slugger); - const apiDoc = new VFile({ path: 'test.md' }); - const section = { type: 'root', children: [] }; - const heading = { - type: 'heading', - data: { - text: 'Test Heading', - type: 'test', - name: 'test', - depth: 1, - }, - }; - const stability = { - type: 'root', - data: { index: 2, description: '' }, - children: [], - }; - const properties = { source_link: 'test.com' }; - metadata.setHeading(heading); - metadata.addStability(stability); - metadata.updateProperties(properties); - const expected = { - added_in: undefined, - api: 'test', - api_doc_source: 'doc/api/test.md', - changes: [], - content: section, - deprecated_in: undefined, - heading, - n_api_version: undefined, - introduced_in: undefined, - llm_description: undefined, - removed_in: undefined, - slug: 'test-heading', - source_link: 'test.com', - stability: { type: 'root', children: [stability] }, - tags: [], - updates: [], - yaml_position: {}, - }; - const actual = metadata.create(apiDoc, section); - deepStrictEqual(actual, expected); - }); - - it('should be serializable', () => { - const { create } = createMetadata(new GitHubSlugger()); - const actual = create(new VFile({ path: 'test.md' }), { - type: 'root', - children: [], - }); - deepStrictEqual(structuredClone(actual), actual); - }); -}); diff --git a/src/generators/addon-verify/types.d.ts b/src/generators/addon-verify/types.d.ts index 44520a3d..c893d8d0 100644 --- a/src/generators/addon-verify/types.d.ts +++ b/src/generators/addon-verify/types.d.ts @@ -1,4 +1,6 @@ +import type { MetadataEntry } from '../metadata/types'; + export type Generator = GeneratorMetadata< {}, - Generate, Promise>> + Generate, Promise>> >; diff --git a/src/generators/ast/constants.mjs b/src/generators/ast/constants.mjs new file mode 100644 index 00000000..15821766 --- /dev/null +++ b/src/generators/ast/constants.mjs @@ -0,0 +1 @@ +export const STABILITY_INDEX_URL = 'documentation.html#stability-index'; diff --git a/src/generators/ast/generate.mjs b/src/generators/ast/generate.mjs index d5574019..ec39d3ac 100644 --- a/src/generators/ast/generate.mjs +++ b/src/generators/ast/generate.mjs @@ -6,12 +6,11 @@ import { extname } from 'node:path'; import { globSync } from 'tinyglobby'; import { VFile } from 'vfile'; +import { STABILITY_INDEX_URL } from './constants.mjs'; import getConfig from '../../utils/configuration/index.mjs'; -import createQueries from '../../utils/queries/index.mjs'; +import { QUERIES } from '../../utils/queries/index.mjs'; import { getRemark } from '../../utils/remark.mjs'; -const { updateStabilityPrefixToLink } = createQueries(); - const remarkProcessor = getRemark(); /** @@ -26,9 +25,14 @@ export async function processChunk(inputSlice, itemIndices) { const results = []; for (const path of filePaths) { - const vfile = new VFile({ path, value: await readFile(path, 'utf-8') }); - - updateStabilityPrefixToLink(vfile); + const content = await readFile(path, 'utf-8'); + const vfile = new VFile({ + path, + value: content.replace( + QUERIES.stabilityIndexPrefix, + match => `[${match}](${STABILITY_INDEX_URL})` + ), + }); results.push({ tree: remarkProcessor.parse(vfile), diff --git a/src/generators/json-simple/generate.mjs b/src/generators/json-simple/generate.mjs index 48be74e1..7fefda7b 100644 --- a/src/generators/json-simple/generate.mjs +++ b/src/generators/json-simple/generate.mjs @@ -6,7 +6,7 @@ import { join } from 'node:path'; import { remove } from 'unist-util-remove'; import getConfig from '../../utils/configuration/index.mjs'; -import createQueries from '../../utils/queries/index.mjs'; +import { UNIST } from '../../utils/queries/index.mjs'; /** * Generates the simplified JSON version of the API docs @@ -16,17 +16,14 @@ import createQueries from '../../utils/queries/index.mjs'; export async function generate(input) { const config = getConfig('json-simple'); - // Iterates the input (ApiDocMetadataEntry) and performs a few changes + // Iterates the input (MetadataEntry) and performs a few changes const mappedInput = input.map(node => { // Deep clones the content nodes to avoid affecting upstream nodes const content = JSON.parse(JSON.stringify(node.content)); // Removes numerous nodes from the content that should not be on the "body" // of the JSON version of the API docs as they are already represented in the metadata - remove(content, [ - createQueries.UNIST.isStabilityNode, - createQueries.UNIST.isHeading, - ]); + remove(content, [UNIST.isStabilityNode, UNIST.isHeading]); return { ...node, content }; }); diff --git a/src/generators/json-simple/types.d.ts b/src/generators/json-simple/types.d.ts index 72960787..2eec8938 100644 --- a/src/generators/json-simple/types.d.ts +++ b/src/generators/json-simple/types.d.ts @@ -1,4 +1,6 @@ +import type { MetadataEntry } from '../metadata/types'; + export type Generator = GeneratorMetadata< {}, - Generate, Promise>> + Generate, Promise>> >; diff --git a/src/generators/jsx-ast/constants.mjs b/src/generators/jsx-ast/constants.mjs index ae22457f..ddf407d6 100644 --- a/src/generators/jsx-ast/constants.mjs +++ b/src/generators/jsx-ast/constants.mjs @@ -43,9 +43,9 @@ export const TAG_TRANSFORMS = { * API lifecycle change labels */ export const LIFECYCLE_LABELS = { - added_in: 'Added in', - deprecated_in: 'Deprecated in', - removed_in: 'Removed in', + added: 'Added in', + deprecated: 'Deprecated in', + removed: 'Removed in', introduced_in: 'Introduced in', }; diff --git a/src/generators/jsx-ast/generate.mjs b/src/generators/jsx-ast/generate.mjs index ea55af76..e94cef5f 100644 --- a/src/generators/jsx-ast/generate.mjs +++ b/src/generators/jsx-ast/generate.mjs @@ -45,7 +45,10 @@ export async function processChunk(slicedInput, itemIndices, docPages) { export async function* generate(input, worker) { const config = getConfig('jsx-ast'); + console.log(input[0]); + const groupedModules = groupNodesByModule(input); + const headNodes = getSortedHeadNodes(input); // Pre-compute docPages once in main thread diff --git a/src/generators/jsx-ast/types.d.ts b/src/generators/jsx-ast/types.d.ts index 9103bea1..ef8eb49f 100644 --- a/src/generators/jsx-ast/types.d.ts +++ b/src/generators/jsx-ast/types.d.ts @@ -1,10 +1,11 @@ +import type { MetadataEntry } from '../metadata/types'; import type { JSXContent } from './utils/buildContent.mjs'; export type Generator = GeneratorMetadata< {}, - Generate, AsyncGenerator>, + Generate, AsyncGenerator>, ProcessChunk< - { head: ApiDocMetadataEntry; entries: Array }, + { head: MetadataEntry; entries: Array }, JSXContent, Array<[string, string]> > diff --git a/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs b/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs index 8c296ea8..1953ef24 100644 --- a/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs +++ b/src/generators/jsx-ast/utils/__tests__/buildBarProps.test.mjs @@ -70,7 +70,7 @@ describe('buildMetaBarProps', () => { it('creates meta bar properties from entries', () => { const head = { api: 'fs', - added_in: 'v1.0.0', + added: 'v1.0.0', }; const entries = [ @@ -106,7 +106,7 @@ describe('buildMetaBarProps', () => { assert.ok(Array.isArray(result.headings)); }); - it('falls back to introduced_in if added_in is missing', () => { + it('falls back to introduced_in if added is missing', () => { const head = { api: 'fs', introduced_in: 'v2.0.0', diff --git a/src/generators/jsx-ast/utils/buildBarProps.mjs b/src/generators/jsx-ast/utils/buildBarProps.mjs index b8da32cf..5735e6ab 100644 --- a/src/generators/jsx-ast/utils/buildBarProps.mjs +++ b/src/generators/jsx-ast/utils/buildBarProps.mjs @@ -19,7 +19,7 @@ import { TOC_MAX_HEADING_DEPTH } from '../constants.mjs'; /** * Generate a combined plain text string from all MDAST entries for estimating reading time. * - * @param {Array} entries - API documentation entries + * @param {Array} entries - API documentation entries */ export const extractTextContent = entries => { return entries.reduce((acc, entry) => { @@ -32,7 +32,7 @@ export const extractTextContent = entries => { /** * Determines if an entry should be included in the Table of Contents. - * @param {ApiDocMetadataEntry} entry + * @param {import('../../metadata/types').MetadataEntry} entry */ const shouldIncludeEntryInToC = ({ heading }) => // Only include headings with text, @@ -42,7 +42,7 @@ const shouldIncludeEntryInToC = ({ heading }) => /** * Extracts and formats heading information from an API documentation entry. - * @param {ApiDocMetadataEntry} entry + * @param {import('../../metadata/types').MetadataEntry} entry */ const extractHeading = entry => { const data = entry.heading.data; @@ -72,16 +72,16 @@ const extractHeading = entry => { return { depth: entry.heading.depth, value: heading, - stability: parseInt(entry.stability?.children[0]?.data.index ?? 2), - slug: data.slug, - data: { id: data.slug, type: data.type }, + stability: parseInt(entry.stability?.data.index ?? 2), + slug: entry.heading.slug, + data: { id: entry.heading.slug, type: data.type }, }; }; /** * Build the list of heading metadata for sidebar navigation. * - * @param {Array} entries - All API metadata entries + * @param {Array} entries - All API metadata entries */ export const extractHeadings = entries => entries.filter(shouldIncludeEntryInToC).map(extractHeading); @@ -89,15 +89,15 @@ export const extractHeadings = entries => /** * Builds metadata for the meta bar (right panel). * - * @param {ApiDocMetadataEntry} head - Main API metadata entry (used as reference point) - * @param {Array} entries - All documentation entries for a given API item + * @param {import('../../metadata/types').MetadataEntry} head - Main API metadata entry (used as reference point) + * @param {Array} entries - All documentation entries for a given API item */ export const buildMetaBarProps = (head, entries) => { const config = getConfig('jsx-ast'); return { headings: extractHeadings(entries), - addedIn: head.introduced_in || head.added_in || '', + addedIn: head.added || head.introduced_in || '', readingTime: readingTime(extractTextContent(entries)).text, viewAs: [ ['JSON', `${head.api}.json`], @@ -110,7 +110,7 @@ export const buildMetaBarProps = (head, entries) => { /** * Converts a compatible version entry into a version label and link. * - * @param {Array} compatibleVersions - Compatible versions + * @param {Array} compatibleVersions - Compatible versions * @param {string} api - API identifier (used in link) */ export const formatVersionOptions = (compatibleVersions, api) => { @@ -140,7 +140,7 @@ export const formatVersionOptions = (compatibleVersions, api) => { /** * Builds metadata for the sidebar (left panel). * - * @param {ApiDocMetadataEntry} entry - Current documentation entry + * @param {import('../../metadata/types').MetadataEntry} entry - Current documentation entry * @param {Array<[string, string]>} docPages - Available doc pages for sidebar navigation */ export const buildSideBarProps = (entry, docPages) => { diff --git a/src/generators/jsx-ast/utils/buildContent.mjs b/src/generators/jsx-ast/utils/buildContent.mjs index 62487631..0932eb9f 100644 --- a/src/generators/jsx-ast/utils/buildContent.mjs +++ b/src/generators/jsx-ast/utils/buildContent.mjs @@ -8,7 +8,6 @@ import { SKIP, visit } from 'unist-util-visit'; import { createJSXElement } from './ast.mjs'; import { buildMetaBarProps } from './buildBarProps.mjs'; import { enforceArray } from '../../../utils/array.mjs'; -import createQueries from '../../../utils/queries/index.mjs'; import { JSX_IMPORTS } from '../../web/constants.mjs'; import { STABILITY_LEVELS, @@ -30,10 +29,11 @@ import { GITHUB_BLOB_URL, populate, } from '../../../utils/configuration/templates.mjs'; +import { UNIST } from '../../../utils/queries/index.mjs'; /** * Processes lifecycle and change history data into a sorted array of change entries. - * @param {ApiDocMetadataEntry} entry - The metadata entry + * @param {import('../../metadata/types').MetadataEntry} entry - The metadata entry * @param {import('unified').Processor} remark - The remark processor */ export const gatherChangeEntries = (entry, remark) => { @@ -57,7 +57,7 @@ export const gatherChangeEntries = (entry, remark) => { /** * Creates a JSX ChangeHistory element or returns null if no changes. - * @param {ApiDocMetadataEntry} entry - The metadata entry + * @param {import('../../metadata/types').MetadataEntry} entry - The metadata entry * @param {import('unified').Processor} remark - The remark processor */ export const createChangeElement = (entry, remark) => { @@ -124,7 +124,7 @@ export const extractHeadingContent = content => { /** * Creates a heading wrapper element with anchors, icons, and optional change history. - * @param {import('mdast').Node} content - The content node to extract text from + * @param {import('../../metadata/types').HeadingNode} content - The content node to extract text from * @param {import('unist').Node|null} changeElement - The change history element, if available */ export const createHeadingElement = (content, changeElement) => { @@ -162,7 +162,7 @@ export const createHeadingElement = (content, changeElement) => { /** * Converts a stability note node to an AlertBox JSX element - * @param {import('mdast').Blockquote} node - The stability node to transform + * @param {import('../../metadata/types').StabilityNode} node - The stability node to transform * @param {number} index - The index of the node in its parent's children array * @param {import('unist').Parent} parent - The parent node containing the stability node */ @@ -196,9 +196,9 @@ const getLevelFromDeprecationType = typeText => { /** * Transforms a heading node by injecting metadata, source links, and signatures. - * @param {ApiDocMetadataEntry} entry - The API metadata entry + * @param {import('../../metadata/types').MetadataEntry} entry - The API metadata entry * @param {import('unified').Processor} remark - The remark processor - * @param {import('mdast').Heading} node - The heading node to transform + * @param {import('../../metadata/types').HeadingNode} node - The heading node to transform * @param {number} index - The index of the node in its parent's children array * @param {import('unist').Parent} parent - The parent node containing the heading */ @@ -253,7 +253,7 @@ export const transformHeadingNode = async ( /** * Processes a single API documentation entry's content - * @param {ApiDocMetadataEntry} entry - The API metadata entry to process + * @param {import('../../metadata/types').MetadataEntry} entry - The API metadata entry to process * @param {import('unified').Processor} remark - The remark processor */ export const processEntry = (entry, remark) => { @@ -261,17 +261,17 @@ export const processEntry = (entry, remark) => { const content = structuredClone(entry.content); // Visit and transform stability nodes - visit(content, createQueries.UNIST.isStabilityNode, transformStabilityNode); + visit(content, UNIST.isStabilityNode, transformStabilityNode); // Visit and transform headings with metadata and links - visit(content, createQueries.UNIST.isHeading, (...args) => + visit(content, UNIST.isHeading, (...args) => transformHeadingNode(entry, remark, ...args) ); // Transform typed lists into property tables visit( content, - createQueries.UNIST.isStronglyTypedList, + UNIST.isStronglyTypedList, (node, idx, parent) => (parent.children[idx] = createSignatureTable(node, remark)) ); @@ -281,7 +281,7 @@ export const processEntry = (entry, remark) => { /** * Builds the overall document layout tree - * @param {Array} entries - API documentation metadata entries + * @param {Array} entries - API documentation metadata entries * @param {ReturnType} sideBarProps - Props for the sidebar component * @param {ReturnType} metaBarProps - Props for the meta bar component * @param {import('unified').Processor} remark - The remark processor @@ -316,11 +316,11 @@ export const createDocumentLayout = ( ]); /** - * @typedef {import('estree').Node & { data: ApiDocMetadataEntry }} JSXContent + * @typedef {import('estree').Node & { data: import('../../metadata/types').MetadataEntry }} JSXContent * * Transforms API metadata entries into processed MDX content - * @param {Array} metadataEntries - API documentation metadata entries - * @param {ApiDocMetadataEntry} head - Main API metadata entry with version information + * @param {Array} metadataEntries - API documentation metadata entries + * @param {import('../../metadata/types').MetadataEntry} head - Main API metadata entry with version information * @param {Object} sideBarProps - Props for the sidebar component * @param {import('unified').Processor} remark - Remark processor instance for markdown processing * @returns {Promise} diff --git a/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs b/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs index 976a8225..c0f0c54c 100644 --- a/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs +++ b/src/generators/jsx-ast/utils/getSortedHeadNodes.mjs @@ -4,8 +4,8 @@ import { OVERRIDDEN_POSITIONS } from '../constants.mjs'; /** * Sorts entries by OVERRIDDEN_POSITIONS and then heading name. - * @param {ApiDocMetadataEntry} a - * @param {ApiDocMetadataEntry} b + * @param {import('../../metadata/types').MetadataEntry} a + * @param {import('../../metadata/types').MetadataEntry} b * @returns {number} */ const headingSortFn = (a, b) => { @@ -29,8 +29,8 @@ const headingSortFn = (a, b) => { /** * Filters and sorts entries by OVERRIDDEN_POSITIONS and then heading name. - * @param {Array} entries - * @returns {Array} + * @param {Array} entries + * @returns {Array} */ export const getSortedHeadNodes = entries => entries.filter(node => node.heading.depth === 1).toSorted(headingSortFn); diff --git a/src/generators/jsx-ast/utils/signature.mjs b/src/generators/jsx-ast/utils/signature.mjs index 032f4f59..4d8df94e 100644 --- a/src/generators/jsx-ast/utils/signature.mjs +++ b/src/generators/jsx-ast/utils/signature.mjs @@ -3,7 +3,7 @@ import { h as createElement } from 'hastscript'; import { createJSXElement } from './ast.mjs'; import { parseListIntoProperties } from './types.mjs'; import { highlighter } from '../../../utils/highlighter.mjs'; -import createQueries from '../../../utils/queries/index.mjs'; +import { UNIST } from '../../../utils/queries/index.mjs'; import { parseListItem } from '../../legacy-json/utils/parseList.mjs'; import parseSignature from '../../legacy-json/utils/parseSignature.mjs'; import { JSX_IMPORTS } from '../../web/constants.mjs'; @@ -66,7 +66,7 @@ export const createSignatureCodeBlock = (functionName, signature, prefix) => { * Infers the "real" function name from a heading node. * Useful when auto-generated headings differ from code tokens. * - * @param {HeadingMetadataEntry} heading - Metadata with name and text fields. + * @param {import('../../metadata/types').HeadingData} heading - Metadata with name and text fields. * @param {any} fallback - Fallback value if inference fails. */ export const getFullName = ({ name, text }, fallback = name) => { @@ -91,12 +91,12 @@ export const getFullName = ({ name, text }, fallback = name) => { * Mutates the `children` array by injecting the signature HAST node. * * @param {import('@types/mdast').Parent} parent - The parent MDAST node (usually a section). - * @param {import('@types/mdast').Heading} heading - The heading node with metadata. + * @param {import('../../metadata/types').HeadingNode} heading - The heading node with metadata. * @param {number} idx - The index at which the heading occurs in `parent.children`. */ export const insertSignatureCodeBlock = ({ children }, { data }, idx) => { // Try to locate the parameter list immediately following the heading - const listIdx = children.findIndex(createQueries.UNIST.isStronglyTypedList); + const listIdx = children.findIndex(UNIST.isStronglyTypedList); // Parse parameters from the list, if found const params = diff --git a/src/generators/jsx-ast/utils/types.mjs b/src/generators/jsx-ast/utils/types.mjs index 3039b17a..3b9c14dd 100644 --- a/src/generators/jsx-ast/utils/types.mjs +++ b/src/generators/jsx-ast/utils/types.mjs @@ -1,12 +1,10 @@ import { u as createTree } from 'unist-builder'; -import createQueries from '../../../utils/queries/index.mjs'; +import { QUERIES, UNIST } from '../../../utils/queries/index.mjs'; import { transformNodesToString } from '../../../utils/unist.mjs'; import { DEFAULT_EXPRESSION } from '../../legacy-json/constants.mjs'; import { TRIMMABLE_PADDING_REGEX } from '../constants.mjs'; -const { QUERIES, UNIST } = createQueries; - /** * Checks if the node is a union separator (`' | '`) or a type reference * (a link wrapping `` inline code). diff --git a/src/generators/legacy-html-all/generate.mjs b/src/generators/legacy-html-all/generate.mjs index a322445e..7849e235 100644 --- a/src/generators/legacy-html-all/generate.mjs +++ b/src/generators/legacy-html-all/generate.mjs @@ -32,7 +32,7 @@ export async function generate(input) { // Aggregates all individual content into one giant string const aggregatedContent = entries.map(entry => entry.content).join('\n'); - // Creates a "mimic" of an `ApiDocMetadataEntry` which fulfils the requirements + // Creates a "mimic" of an `MetadataEntry` which fulfils the requirements // for generating the `tableOfContents` with the `tableOfContents.parseNavigationNode` parser const sideNavigationFromValues = entries.map(entry => ({ api: entry.api, diff --git a/src/generators/legacy-html-all/types.d.ts b/src/generators/legacy-html-all/types.d.ts index e005d1ff..ed3978fa 100644 --- a/src/generators/legacy-html-all/types.d.ts +++ b/src/generators/legacy-html-all/types.d.ts @@ -12,5 +12,5 @@ export type Generator = GeneratorMetadata< { templatePath: string; }, - Generate, Promise> + Generate, Promise> >; diff --git a/src/generators/legacy-html/types.d.ts b/src/generators/legacy-html/types.d.ts index e0222dfa..90ddcbe4 100644 --- a/src/generators/legacy-html/types.d.ts +++ b/src/generators/legacy-html/types.d.ts @@ -1,3 +1,5 @@ +import type { MetadataEntry } from '../metadata/types'; + export interface TemplateValues { api: string; added: string; @@ -12,12 +14,12 @@ export type Generator = GeneratorMetadata< templatePath: string; additionalPathsToCopy: Array; }, - Generate, AsyncGenerator>, + Generate, AsyncGenerator>, ProcessChunk< { - head: ApiDocMetadataEntry; - nodes: Array; - headNodes: Array; + head: MetadataEntry; + nodes: Array; + headNodes: Array; }, TemplateValues, string diff --git a/src/generators/legacy-html/utils/buildContent.mjs b/src/generators/legacy-html/utils/buildContent.mjs index af22e8e3..7b9b8a86 100644 --- a/src/generators/legacy-html/utils/buildContent.mjs +++ b/src/generators/legacy-html/utils/buildContent.mjs @@ -10,19 +10,19 @@ import { GITHUB_BLOB_URL, populate, } from '../../../utils/configuration/templates.mjs'; -import createQueries from '../../../utils/queries/index.mjs'; +import { QUERIES, UNIST } from '../../../utils/queries/index.mjs'; /** * Builds a Markdown heading for a given node * - * @param {ApiDocMetadataEntry['heading']} node The node to build the Markdown heading for + * @param {import('../../metadata/types').HeadingNode} node The node to build the Markdown heading for * @param {number} index The index of the current node * @param {import('unist').Parent} parent The parent node of the current node * @returns {import('hast').Element} The HTML AST tree of the heading content */ -const buildHeading = ({ data, children }, index, parent) => { +const buildHeading = ({ data, children, depth }, index, parent) => { // Creates the heading element with the heading text and the link to the heading - const headingElement = createElement(`h${data.depth + 1}`, [ + const headingElement = createElement(`h${depth + 1}`, [ // The inner Heading markdown content is still using Remark nodes, and they need // to be converted into Rehype nodes ...children, @@ -48,7 +48,7 @@ const buildHeading = ({ data, children }, index, parent) => { /** * Builds an HTML Stability element * - * @param {import('@types/mdast').Blockquote} node The HTML AST tree of the Stability Index content + * @param {import('../../metadata/types').StabilityNode} node The HTML AST tree of the Stability Index content * @param {number} index The index of the current node * @param {import('unist').Parent} parent The parent node of the current node */ @@ -75,7 +75,7 @@ const buildStability = ({ children, data }, index, parent) => { */ const buildHtmlTypeLink = node => { node.value = node.value.replace( - createQueries.QUERIES.linksWithTypes, + QUERIES.linksWithTypes, (_, type, link) => `<${type}>` ); }; @@ -83,7 +83,7 @@ const buildHtmlTypeLink = node => { /** * Creates a history table row. * - * @param {ApiDocMetadataChange} change + * @param {import('../../metadata/types').ChangeEntry} change * @param {import('unified').Processor} remark */ const createHistoryTableRow = ( @@ -104,7 +104,7 @@ const createHistoryTableRow = ( /** * Builds the Metadata Properties into content * - * @param {ApiDocMetadataEntry} node The node to build the properties from + * @param {import('../../metadata/types').MetadataEntry} node The node to build the properties from * @param {import('unified').Processor} remark The Remark instance to be used to process changes table * @returns {import('unist').Parent} The HTML AST tree of the properties content */ @@ -129,10 +129,10 @@ const buildMetadataElement = (node, remark) => { } // We use a `span` element to display the added in version - if (typeof node.added_in !== 'undefined') { - const addedIn = Array.isArray(node.added_in) - ? node.added_in.join(', ') - : node.added_in; + if (typeof node.added !== 'undefined') { + const addedIn = Array.isArray(node.added) + ? node.added.join(', ') + : node.added; // Creates the added in element with the added in version const addedinElement = createElement('span', ['Added in: ', addedIn]); @@ -142,10 +142,10 @@ const buildMetadataElement = (node, remark) => { } // We use a `span` element to display the deprecated in version - if (typeof node.deprecated_in !== 'undefined') { - const deprecatedIn = Array.isArray(node.deprecated_in) - ? node.deprecated_in.join(', ') - : node.deprecated_in; + if (typeof node.deprecated !== 'undefined') { + const deprecatedIn = Array.isArray(node.deprecated) + ? node.deprecated.join(', ') + : node.deprecated; // Creates the deprecated in element with the deprecated in version const deprecatedInElement = createElement('span', [ @@ -158,10 +158,10 @@ const buildMetadataElement = (node, remark) => { } // We use a `span` element to display the removed in version - if (typeof node.removed_in !== 'undefined') { - const removedIn = Array.isArray(node.removed_in) - ? node.removed_in.join(', ') - : node.removed_in; + if (typeof node.removed !== 'undefined') { + const removedIn = Array.isArray(node.removed) + ? node.removed.join(', ') + : node.removed; // Creates the removed in element with the removed in version const removedInElement = createElement('span', ['Removed in: ', removedIn]); @@ -171,11 +171,11 @@ const buildMetadataElement = (node, remark) => { } // We use a `span` element to display the N-API version if it is available - if (typeof node.n_api_version === 'number') { + if (typeof node.napiVersion === 'number') { // Creates the N-API version element with the N-API version const nApiVersionElement = createElement('span', [ createElement('b', 'N-API Version: '), - node.n_api_version, + node.napiVersion, ]); // Appends the source n-api element to the metadata element @@ -215,8 +215,8 @@ const buildMetadataElement = (node, remark) => { /** * Builds the whole content of a given node (API module) * - * @param {Array} headNodes The API metadata Nodes that are considered the "head" of each module - * @param {Array} metadataEntries The API metadata Nodes to be transformed into HTML content + * @param {Array} headNodes The API metadata Nodes that are considered the "head" of each module + * @param {Array} metadataEntries The API metadata Nodes to be transformed into HTML content * @param {import('unified').Processor} remark The Remark instance to be used to process */ export default (headNodes, metadataEntries, remark) => { @@ -229,16 +229,16 @@ export default (headNodes, metadataEntries, remark) => { const content = structuredClone(entry.content); // Parses the Heading nodes into Heading elements - visit(content, createQueries.UNIST.isHeading, buildHeading); + visit(content, UNIST.isHeading, buildHeading); // Parses the Blockquotes into Stability elements // This is treated differently as we want to preserve the position of a Stability Index // within the content, so we can't just remove it and append it to the metadata - visit(content, createQueries.UNIST.isStabilityNode, buildStability); + visit(content, UNIST.isStabilityNode, buildStability); // Parses the type references that got replaced into Markdown links (raw) // into actual HTML links, these then get parsed into HAST nodes on `runSync` - visit(content, createQueries.UNIST.isHtmlWithType, buildHtmlTypeLink); + visit(content, UNIST.isHtmlWithType, buildHtmlTypeLink); // Splits the content into the Heading node and the rest of the content const [headingNode, ...restNodes] = content.children; diff --git a/src/generators/legacy-html/utils/buildDropdowns.mjs b/src/generators/legacy-html/utils/buildDropdowns.mjs index b73226db..d47a8fce 100644 --- a/src/generators/legacy-html/utils/buildDropdowns.mjs +++ b/src/generators/legacy-html/utils/buildDropdowns.mjs @@ -50,7 +50,7 @@ export const buildNavigation = navigationContents => * * @param {string} api The current API node name * @param {string} added The version the API was added - * @param {Array} versions All available Node.js releases + * @param {Array} versions All available Node.js releases */ export const buildVersions = (api, added, versions) => { const config = getConfig('legacy-html'); diff --git a/src/generators/legacy-html/utils/buildExtraContent.mjs b/src/generators/legacy-html/utils/buildExtraContent.mjs index fa54fc92..c1f1a84d 100644 --- a/src/generators/legacy-html/utils/buildExtraContent.mjs +++ b/src/generators/legacy-html/utils/buildExtraContent.mjs @@ -6,12 +6,10 @@ import { u as createTree } from 'unist-builder'; /** * Generates the Stability Overview table based on the API metadata nodes. * - * @param {Array} headMetadata The API metadata nodes to be used for the Stability Overview + * @param {Array} headMetadata The API metadata nodes to be used for the Stability Overview */ const buildStabilityOverview = headMetadata => { - const headNodesWithStability = headMetadata.filter(entry => - Boolean(entry.stability.children.length) - ); + const headNodesWithStability = headMetadata.filter(entry => entry.stability); const mappedHeadNodesIntoTable = headNodesWithStability.map( ({ heading, api, stability }) => { @@ -52,8 +50,8 @@ const buildStabilityOverview = headMetadata => { /** * Generates extra "special" HTML content based on extra metadata that a node may have. * - * @param {Array} headNodes The API metadata nodes to be used for the Stability Overview - * @param {ApiDocMetadataEntry} node The current API metadata node to be transformed into HTML content + * @param {Array} headNodes The API metadata nodes to be used for the Stability Overview + * @param {import('../../metadata/types').MetadataEntry} node The current API metadata node to be transformed into HTML content * @returns {import('unist').Parent} The HTML AST tree for the extra content */ export default (headNodes, node) => { diff --git a/src/generators/legacy-html/utils/tableOfContents.mjs b/src/generators/legacy-html/utils/tableOfContents.mjs index 12e92295..09a4fc62 100644 --- a/src/generators/legacy-html/utils/tableOfContents.mjs +++ b/src/generators/legacy-html/utils/tableOfContents.mjs @@ -8,8 +8,8 @@ * * This generates a Markdown string containing a list as the ToC for the API documentation. * - * @param {Array} entries The API metadata nodes to be used for the ToC - * @param {{ maxDepth: number; parser: (metadata: ApiDocMetadataEntry) => string }} options The optional ToC options + * @param {Array} entries The API metadata nodes to be used for the ToC + * @param {{ maxDepth: number; parser: (metadata: import('../../metadata/types').MetadataEntry) => string }} options The optional ToC options */ const tableOfContents = (entries, options) => { // Filter out the entries that have a name property / or that have empty content @@ -18,9 +18,9 @@ const tableOfContents = (entries, options) => { // Generate the ToC based on the API headings (sections) return validEntries.reduce((acc, entry) => { // Check if the depth of the heading is less than or equal to the maximum depth - if (entry.heading.data.depth <= options.maxDepth) { + if (entry.heading.depth <= options.maxDepth) { // Generate the indentation based on the depth of the heading - const indent = ' '.repeat(entry.heading.data.depth - 1); + const indent = ' '.repeat(entry.heading.depth - 1); // Append the ToC entry to the accumulator acc += `${indent}- ${options.parser(entry)}\n`; @@ -33,7 +33,7 @@ const tableOfContents = (entries, options) => { /** * Builds the Label with extra metadata to be used in the ToC * - * @param {ApiDocMetadataEntry} metadata The current node that is being parsed + * @param {import('../../metadata/types').MetadataEntry} metadata The current node that is being parsed */ tableOfContents.parseNavigationNode = ({ api, heading }) => `${heading.data.name}`; @@ -41,7 +41,7 @@ tableOfContents.parseNavigationNode = ({ api, heading }) => /** * Builds the Label with extra metadata to be used in the ToC * - * @param {ApiDocMetadataEntry} metadata + * @param {import('../../metadata/types').MetadataEntry} metadata */ tableOfContents.parseToCNode = ({ stability, api, heading }) => { const fullSlug = `${api}.html#${heading.data.slug}`; diff --git a/src/generators/legacy-json/types.d.ts b/src/generators/legacy-json/types.d.ts index 16649acc..ed92c6ce 100644 --- a/src/generators/legacy-json/types.d.ts +++ b/src/generators/legacy-json/types.d.ts @@ -1,45 +1,24 @@ import { ListItem } from '@types/mdast'; +import { MetadataEntry } from '../metadata/types'; /** - * Represents an entry in a hierarchical structure, extending from ApiDocMetadataEntry. + * Represents an entry in a hierarchical structure, extending from MetadataEntry. * It includes children entries organized in a hierarchy. */ -export interface HierarchizedEntry extends ApiDocMetadataEntry { +export interface HierarchizedEntry extends MetadataEntry { /** * List of child entries that are part of this entry's hierarchy. */ - hierarchyChildren: ApiDocMetadataEntry[]; + hierarchyChildren: MetadataEntry[]; } /** * Contains metadata related to changes, additions, removals, and deprecated statuses of an entry. */ -export interface Meta { - /** - * A list of changes associated with the entry. - */ - changes: ApiDocMetadataChange[]; - - /** - * A list of added versions or entities for the entry. - */ - added: string[]; - - /** - * A list of NAPI (Node API) versions related to the entry. - */ - napiVersion: string[]; - - /** - * A list of versions where the entry was deprecated. - */ - deprecated: string[]; - - /** - * A list of versions where the entry was removed. - */ - removed: string[]; -} +export type Meta = Pick< + MetadataEntry, + 'changes' | 'added' | 'napiVersion' | 'deprecated' | 'removed' +>; /** * Base interface for sections in the API documentation, representing common properties. @@ -281,9 +260,6 @@ export interface ParameterList { export type Generator = GeneratorMetadata< {}, - Generate, AsyncGenerator
>, - ProcessChunk< - { head: ApiDocMetadataEntry; nodes: Array }, - Section - > + Generate, AsyncGenerator
>, + ProcessChunk<{ head: MetadataEntry; nodes: Array }, Section> >; diff --git a/src/generators/legacy-json/utils/buildHierarchy.mjs b/src/generators/legacy-json/utils/buildHierarchy.mjs index f7ad87cd..0f79125a 100644 --- a/src/generators/legacy-json/utils/buildHierarchy.mjs +++ b/src/generators/legacy-json/utils/buildHierarchy.mjs @@ -1,8 +1,8 @@ /** * Recursively finds the most suitable parent entry for a given `entry` based on heading depth. * - * @param {ApiDocMetadataEntry} entry - * @param {ApiDocMetadataEntry[]} entries + * @param {import('../../metadata/types').MetadataEntry} entry + * @param {import('../../metadata/types').MetadataEntry[]} entries * @param {number} startIdx * @returns {import('../types.d.ts').HierarchizedEntry} */ @@ -43,7 +43,7 @@ export function findParent(entry, entries, startIdx) { * can do this by just looping through entries in reverse starting at the * current index - 1. * - * @param {Array} entries + * @param {Array} entries * @returns {Array} */ export function buildHierarchy(entries) { diff --git a/src/generators/legacy-json/utils/buildSection.mjs b/src/generators/legacy-json/utils/buildSection.mjs index d3f710ba..433c0b89 100644 --- a/src/generators/legacy-json/utils/buildSection.mjs +++ b/src/generators/legacy-json/utils/buildSection.mjs @@ -39,30 +39,30 @@ export const createSectionBuilder = () => { * @returns {import('../types.d.ts').Meta | undefined} The created metadata, or undefined if all fields are empty. */ const createMeta = ({ - added_in = [], - n_api_version = [], - deprecated_in = [], - removed_in = [], + added = [], + napiVersion = [], + deprecated = [], + removed = [], changes, }) => { const meta = {}; - if (added_in?.length) { - meta.added = enforceArray(added_in); + if (added?.length) { + meta.added = enforceArray(added); } meta.changes = changes; - if (typeof n_api_version === 'number' || n_api_version?.length) { - meta.napiVersion = enforceArray(n_api_version); + if (typeof napiVersion === 'number' || napiVersion?.length) { + meta.napiVersion = enforceArray(napiVersion); } - if (deprecated_in?.length) { - meta.deprecated = enforceArray(deprecated_in); + if (deprecated?.length) { + meta.deprecated = enforceArray(deprecated); } - if (removed_in?.length) { - meta.removed = enforceArray(removed_in); + if (removed?.length) { + meta.removed = enforceArray(deprecated); } // Check if there are any non-empty fields in the meta object @@ -76,7 +76,7 @@ export const createSectionBuilder = () => { /** * Creates a section from an entry and its heading. * @param {import('../types.d.ts').HierarchizedEntry} entry - The AST entry. - * @param {HeadingMetadataParent} head - The head node of the entry. + * @param {import('../../metadata/types').HeadingNode} head - The head node of the entry. * @returns {import('../types.d.ts').Section} The created section. */ const createSection = (entry, head) => { @@ -103,11 +103,9 @@ export const createSectionBuilder = () => { * @param {import('../types.d.ts').HierarchizedEntry} entry - The entry providing stability information. */ const parseStability = (section, nodes, { stability, content }) => { - const stabilityNode = stability.children[0]; - - if (stabilityNode) { - section.stability = Number(stabilityNode.data.index); - section.stabilityText = stabilityNode.data.description; + if (stability) { + section.stability = Number(stability.data.index); + section.stabilityText = stability.data.description; const stabilityIdx = content.children.indexOf(stability.children[0]); @@ -139,7 +137,7 @@ export const createSectionBuilder = () => { * Adds additional metadata to the section based on its type. * @param {import('../types.d.ts').Section} section - The section to update. * @param {import('../types.d.ts').Section} parent - The parent section. - * @param {import('../../types.d.ts').NodeWithData} heading - The heading node of the section. + * @param {import('../../metadata/types').HeadingNode} heading - The heading node of the section. */ const addAdditionalMetadata = (section, parent, heading) => { if (!section.type || section.type === 'module') { @@ -192,15 +190,16 @@ export const createSectionBuilder = () => { /** * Builds the module section from head metadata and entries. - * @param {ApiDocMetadataEntry} head - The head metadata entry. - * @param {Array} entries - The list of metadata entries. + * @param {import('../../metadata/types').MetadataEntry} head - The head metadata entry. + * @param {Array} entries - The list of metadata entries. * @returns {import('../types.d.ts').ModuleSection} The constructed module section. */ return (head, entries) => { const rootModule = { type: 'module', api: head.api, - source: head.api_doc_source, + // TODO(@avivkeller): This should be configurable + source: `doc/api/${head.api}.md`, }; buildHierarchy(entries).forEach(entry => handleEntry(entry, rootModule)); diff --git a/src/generators/legacy-json/utils/parseList.mjs b/src/generators/legacy-json/utils/parseList.mjs index 8f874b13..5e1a5d89 100644 --- a/src/generators/legacy-json/utils/parseList.mjs +++ b/src/generators/legacy-json/utils/parseList.mjs @@ -6,7 +6,7 @@ import { } from '../constants.mjs'; import parseSignature from './parseSignature.mjs'; import { leftHandAssign } from '../../../utils/generators.mjs'; -import createQueries from '../../../utils/queries/index.mjs'; +import { QUERIES, UNIST } from '../../../utils/queries/index.mjs'; import { transformNodesToString } from '../../../utils/unist.mjs'; /** @@ -47,7 +47,7 @@ export const extractPattern = (text, pattern, key, current) => { export function parseListItem(child) { const current = {}; - const subList = child.children.find(createQueries.UNIST.isLooselyTypedList); + const subList = child.children.find(UNIST.isLooselyTypedList); // Extract and clean raw text from the node, excluding nested lists current.textRaw = transformTypeReferences( @@ -59,7 +59,7 @@ export function parseListItem(child) { let text = current.textRaw; // Identify return items or extract key properties (name, type, default) from the text - const starter = text.match(createQueries.QUERIES.typedListStarters); + const starter = text.match(QUERIES.typedListStarters); if (starter) { current.name = starter[1] === 'Returns' ? 'return' : starter[1].toLowerCase(); @@ -89,7 +89,7 @@ export function parseListItem(child) { * @param {import('@types/mdast').RootContent[]} nodes */ export function parseList(section, nodes) { - const listIdx = nodes.findIndex(createQueries.UNIST.isStronglyTypedList); + const listIdx = nodes.findIndex(UNIST.isStronglyTypedList); const list = nodes[listIdx]; const values = list ? list.children.map(parseListItem) : []; diff --git a/src/generators/llms-txt/types.d.ts b/src/generators/llms-txt/types.d.ts index 693195fe..613f8f36 100644 --- a/src/generators/llms-txt/types.d.ts +++ b/src/generators/llms-txt/types.d.ts @@ -1,6 +1,8 @@ +import { MetadataEntry } from '../metadata/types'; + export type Generator = GeneratorMetadata< { templatePath: string; }, - Generate, Promise> + Generate, Promise> >; diff --git a/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs b/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs index bb65a02c..12070967 100644 --- a/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs +++ b/src/generators/llms-txt/utils/__tests__/buildApiDocLink.test.mjs @@ -65,24 +65,25 @@ describe('buildApiDocLink', () => { it('builds markdown link with description', () => { const entry = { heading: { data: { name: 'Test API' } }, - api_doc_source: 'doc/api/test.md', + api: 'test', llm_description: 'Test description', }; const result = buildApiDocLink(entry, 'https://example.com'); - assert.ok(result.includes('[Test API]')); - assert.ok(result.includes('/docs/latest/api/test.md')); - assert.ok(result.includes('Test description')); + assert.strictEqual( + result, + '[Test API](https://example.com/docs/latest/test.md): Test description' + ); }); it('handles doc path replacement', () => { const entry = { heading: { data: { name: 'API Method' } }, - api_doc_source: 'doc/some/path.md', + api: 'path', content: { children: [] }, }; const result = buildApiDocLink(entry, 'https://example.com'); - assert.ok(result.includes('/docs/latest/some/path.md')); + assert.ok(result.includes('https://example.com/docs/latest/path.md')); }); }); diff --git a/src/generators/llms-txt/utils/buildApiDocLink.mjs b/src/generators/llms-txt/utils/buildApiDocLink.mjs index 452d849d..ff84554d 100644 --- a/src/generators/llms-txt/utils/buildApiDocLink.mjs +++ b/src/generators/llms-txt/utils/buildApiDocLink.mjs @@ -6,7 +6,7 @@ import { transformNodeToString } from '../../../utils/unist.mjs'; * the entry has a llm_description property. If not, it extracts the first * paragraph from the entry's content. * - * @param {ApiDocMetadataEntry} entry + * @param {import('../../metadata/types').MetadataEntry} entry * @returns {string} */ export const getEntryDescription = entry => { @@ -32,7 +32,7 @@ export const getEntryDescription = entry => { /** * Builds a markdown link for an API doc entry * - * @param {ApiDocMetadataEntry} entry + * @param {import('../../metadata/types').MetadataEntry} entry * @param {string} baseURL * @returns {string} */ diff --git a/src/generators/man-page/generate.mjs b/src/generators/man-page/generate.mjs index d7947f62..4ab8ba5a 100644 --- a/src/generators/man-page/generate.mjs +++ b/src/generators/man-page/generate.mjs @@ -10,10 +10,10 @@ import { import getConfig from '../../utils/configuration/index.mjs'; /** - * @param {Array} components + * @param {Array} components * @param {number} start * @param {number} end - * @param {(element: ApiDocMetadataEntry) => string} convert + * @param {(element: import('../metadata/types').MetadataEntry) => string} convert * @returns {string} */ function extractMandoc(components, start, end, convert) { @@ -41,11 +41,11 @@ export async function generate(input) { // Find the appropriate headers const optionsStart = components.findIndex( - ({ slug }) => slug === config.cliOptionsHeaderSlug + ({ heading }) => heading.slug === config.cliOptionsHeaderSlug ); const environmentStart = components.findIndex( - ({ slug }) => slug === config.envVarsHeaderSlug + ({ heading }) => heading.slug === config.envVarsHeaderSlug ); // The first header that is <3 in depth after environmentStart diff --git a/src/generators/man-page/types.d.ts b/src/generators/man-page/types.d.ts index a6e26f29..3bab3f6f 100644 --- a/src/generators/man-page/types.d.ts +++ b/src/generators/man-page/types.d.ts @@ -1,3 +1,5 @@ +import { MetadataEntry } from '../metadata/types'; + export type Generator = GeneratorMetadata< { fileName: string; @@ -5,5 +7,5 @@ export type Generator = GeneratorMetadata< envVarsHeaderSlug: string; templatePath: string; }, - Generate, Promise> + Generate, Promise> >; diff --git a/src/generators/man-page/utils/converter.mjs b/src/generators/man-page/utils/converter.mjs index 0f822541..2662ad7d 100644 --- a/src/generators/man-page/utils/converter.mjs +++ b/src/generators/man-page/utils/converter.mjs @@ -122,7 +122,7 @@ const formatFlag = flag => * This function formats command-line options, including flags and descriptions, * for display in Unix manual pages using Mandoc. * - * @param {ApiDocMetadataEntry} element - The metadata entry containing details about the API option. + * @param {import('../../metadata/types').MetadataEntry} element - The metadata entry containing details about the API option. * @returns {string} The Mandoc formatted string representing the API option, including flags and content. */ export function convertOptionToMandoc(element) { @@ -146,7 +146,7 @@ export function convertOptionToMandoc(element) { * This function formats environment variables for Unix manual pages, converting * the variable name and value, along with any associated descriptions, into Mandoc. * - * @param {ApiDocMetadataEntry} element - The metadata entry containing details about the environment variable. + * @param {import('../../metadata/types').MetadataEntry} element - The metadata entry containing details about the environment variable. * @returns {string} The Mandoc formatted representation of the environment variable and its content. */ export function convertEnvVarToMandoc(element) { diff --git a/src/generators/metadata/constants.mjs b/src/generators/metadata/constants.mjs index 88b8a2d0..b02d8750 100644 --- a/src/generators/metadata/constants.mjs +++ b/src/generators/metadata/constants.mjs @@ -1,3 +1,132 @@ // On "About this Documentation", we define the stability indices, and thus // we don't need to check it for stability references export const IGNORE_STABILITY_STEMS = ['documentation']; + +import globals from 'globals'; + +// These are string replacements specific to Node.js API docs for anchor IDs +export const DOC_API_SLUGS_REPLACEMENTS = [ + { from: /node.js/i, to: 'nodejs' }, // Replace Node.js + { from: /&/, to: '-and-' }, // Replace & + { from: /[/_,:;\\ ]/g, to: '-' }, // Replace /_,:;\. and whitespace + { from: /^-+(?!-*$)/g, to: '' }, // Remove any leading hyphens + { from: /(? [e, e])), + AsyncGeneratorFunction: 'AsyncGeneratorFunction', + AsyncIterator: 'AsyncIterator', + AsyncFunction: 'AsyncFunction', + TypedArray: 'TypedArray', + ErrorEvent: 'ErrorEvent', + 'WebAssembly.Instance': 'WebAssembly/Instance', +}; + +// This is a mapping for miscellaneous types within the Markdown content and their respective +// external reference on appropriate 3rd-party vendors/documentation sites. +export const DOC_TYPES_MAPPING_OTHER = { + any: `${DOC_MDN_BASE_URL_JS_PRIMITIVES}#Data_types`, + this: `${DOC_MDN_BASE_URL_JS}Reference/Operators/this`, + + ArrayBufferView: `${DOC_MDN_BASE_URL}/API/ArrayBufferView`, + + AsyncIterable: 'https://tc39.github.io/ecma262/#sec-asynciterable-interface', + + 'Module Namespace Object': + 'https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects', + + Iterable: `${DOC_MDN_BASE_URL_JS}Reference/Iteration_protocols#The_iterable_protocol`, + + CloseEvent: `${DOC_MDN_BASE_URL}/API/CloseEvent`, + EventSource: `${DOC_MDN_BASE_URL}/API/EventSource`, + MessageEvent: `${DOC_MDN_BASE_URL}/API/MessageEvent`, + + DOMException: `${DOC_MDN_BASE_URL}/API/DOMException`, + Storage: `${DOC_MDN_BASE_URL}/API/Storage`, + WebSocket: `${DOC_MDN_BASE_URL}/API/WebSocket`, + + FormData: `${DOC_MDN_BASE_URL}API/FormData`, + Headers: `${DOC_MDN_BASE_URL}/API/Headers`, + Response: `${DOC_MDN_BASE_URL}/API/Response`, + Request: `${DOC_MDN_BASE_URL}/API/Request`, +}; diff --git a/src/generators/metadata/types.d.ts b/src/generators/metadata/types.d.ts index 403170db..6da44dac 100644 --- a/src/generators/metadata/types.d.ts +++ b/src/generators/metadata/types.d.ts @@ -1,9 +1,144 @@ -import type { Root } from 'mdast'; +import type { + Root, + Position, + Node, + Data, + Parent, + Blockquote, + Heading, +} from 'mdast'; export type Generator = GeneratorMetadata< { + /** Type mapping configuration for AST node processing */ typeMap: string | URL; }, - Generate, AsyncGenerator>, - ProcessChunk> + Generate, AsyncGenerator>, + ProcessChunk> >; + +/** + * Utility type to augment AST nodes with typed data properties. + * + * This ensures type safety when working with nodes that have been + * enhanced with additional metadata during processing. + * + * @template T - The base AST node type + * @template J - The data structure to attach + */ +type NodeWithData = T & { + data: J; +}; + +export interface ChangeEntry { + // The Node.js version or versions where said change was introduced simultaneously + version: string | Array; + // The GitHub PR URL of said change + 'pr-url': string | undefined; + // The description of said change + description: string; +} + +/** + * YAML frontmatter properties commonly found in API documentation. + * + * These properties provide metadata about API elements including + * versioning, deprecation status, stability indicators, and + * additional documentation context. + */ +export interface YAMLProperties { + /** Source position information for the YAML block */ + yamlPosition?: Position; + + // === Core Metadata === + /** Type classification of the API element */ + type?: string; + /** Link to the source code implementation */ + source_link?: string; + /** Human-readable description for LLM processing */ + llm_description?: string; + /** Associated tags for categorization */ + tags?: Array; + + /** Version when this API was first added */ + added?: string; + /** Version when this API was first added */ // TODO(@avivkeller): Merge this with `added` + introduced_in?: string; + /** Version when this API was deprecated */ + deprecated?: string; + /** Version when this API was removed */ + removed?: string; + + /** N-API version requirement (legacy format) */ + napiVersion?: string; + + /** Array of change records with version info */ + changes?: Array; + + /** Allow for additional YAML properties not explicitly defined */ + [key: string]: unknown; +} + +export type HeadingType = + | 'event' + | 'global' + | 'method' + | 'property' + | 'class' + | 'module' + | 'classMethod' + | 'ctor'; + +/** + * Data structure for enhanced heading nodes. + * Extracted as a separate interface for reusability and clarity. + */ +export interface HeadingData extends Data { + /** Extracted plain text content of the heading */ + text: string; + /** The name of the module that this heading refers to */ + name: string; + /** URL-safe slug derived from heading text */ + slug: string; + /** Optional type classification */ + type?: HeadingType; +} + +/** + * Represents a processed heading that includes both the original + * AST structure and computed metadata like text content and URL slug. + */ +export type HeadingNode = NodeWithData; + +/** + * Data structure for stability index entries. + */ +export interface StabilityData extends Data { + /** Numeric stability index (0-3) */ + index: string; + /** Human-readable stability description */ + description: string; +} + +/** + * Individual stability entry with metadata. + */ +export type StabilityNode = NodeWithData; + +/** + * Complete metadata entry for a single API documentation element. + * + * This represents the processed result of parsing a section of API + * documentation, containing all the structured information needed + * for documentation generation, search indexing, and validation. + */ +export interface MetadataEntry extends YAMLProperties { + /** API identifier/name */ + api: string; + /** Processed heading with metadata */ + heading: HeadingNode; + /** Stability classification information */ + stability: StabilityNode; + /** Main content as markdown AST */ + content: Root; +} diff --git a/src/generators/metadata/utils/__tests__/parse.test.mjs b/src/generators/metadata/utils/__tests__/parse.test.mjs index 8139589a..bb12549d 100644 --- a/src/generators/metadata/utils/__tests__/parse.test.mjs +++ b/src/generators/metadata/utils/__tests__/parse.test.mjs @@ -31,27 +31,12 @@ describe('parseApiDoc', () => { assert.strictEqual(results.length, 1); }); - it('sets api and api_doc_source from the file', () => { - const tree = u('root', [h('fs')]); - const [entry] = parseApiDoc({ file, tree }, typeMap); - - assert.strictEqual(entry.api, 'fs'); - assert.strictEqual(entry.api_doc_source, 'doc/api/fs.md'); - }); - - it('generates a slug from the heading text', () => { - const tree = u('root', [h('File System')]); - const [entry] = parseApiDoc({ file, tree }, typeMap); - - assert.strictEqual(entry.slug, 'file-system'); - }); - it('populates heading data with text and depth', () => { const tree = u('root', [h('File System')]); const [entry] = parseApiDoc({ file, tree }, typeMap); assert.strictEqual(entry.heading.data.text, 'File System'); - assert.strictEqual(entry.heading.data.depth, 1); + assert.strictEqual(entry.heading.depth, 1); }); }); @@ -89,7 +74,7 @@ describe('parseApiDoc', () => { const tree = u('root', [h('fs'), yaml('added: v0.1.0')]); const [entry] = parseApiDoc({ file, tree }, typeMap); - assert.strictEqual(entry.added_in, 'v0.1.0'); + assert.strictEqual(entry.added, 'v0.1.0'); }); it('extracts deprecated_in', () => { @@ -99,15 +84,15 @@ describe('parseApiDoc', () => { ]); const [entry] = parseApiDoc({ file, tree }, typeMap); - assert.strictEqual(entry.added_in, 'v1.0.0'); - assert.strictEqual(entry.deprecated_in, 'v2.0.0'); + assert.strictEqual(entry.added, 'v1.0.0'); + assert.strictEqual(entry.deprecated, 'v2.0.0'); }); it('extracts removed_in', () => { const tree = u('root', [h('removedMethod'), yaml('removed: v3.0.0')]); const [entry] = parseApiDoc({ file, tree }, typeMap); - assert.strictEqual(entry.removed_in, 'v3.0.0'); + assert.strictEqual(entry.removed, 'v3.0.0'); }); it('extracts changes', () => { @@ -137,14 +122,6 @@ describe('parseApiDoc', () => { assert.deepStrictEqual(entry.tags, ['legacy']); }); - - it('defaults to empty arrays when YAML block is absent', () => { - const tree = u('root', [h('fs')]); - const [entry] = parseApiDoc({ file, tree }, typeMap); - - assert.deepStrictEqual(entry.changes, []); - assert.deepStrictEqual(entry.tags, []); - }); }); describe('stability index', () => { @@ -152,12 +129,8 @@ describe('parseApiDoc', () => { const tree = u('root', [h('fs'), stability('Stability: 2 - Stable')]); const [entry] = parseApiDoc({ file, tree }, typeMap); - assert.strictEqual(entry.stability.children.length, 1); - assert.strictEqual(entry.stability.children[0].data.index, '2'); - assert.strictEqual( - entry.stability.children[0].data.description, - 'Stable' - ); + assert.strictEqual(entry.stability.data.index, '2'); + assert.strictEqual(entry.stability.data.description, 'Stable'); }); it('captures multi-word stability description', () => { @@ -168,7 +141,7 @@ describe('parseApiDoc', () => { const [entry] = parseApiDoc({ file, tree }, typeMap); assert.strictEqual( - entry.stability.children[0].data.description, + entry.stability.data.description, 'Experimental: This API is experimental.' ); }); @@ -183,14 +156,14 @@ describe('parseApiDoc', () => { typeMap ); - assert.strictEqual(entry.stability.children.length, 0); + assert.ok(!('stability' in entry)); }); it('has empty stability when no blockquote is present', () => { const tree = u('root', [h('fs')]); const [entry] = parseApiDoc({ file, tree }, typeMap); - assert.strictEqual(entry.stability.children.length, 0); + assert.ok(!('stability' in entry)); }); }); diff --git a/src/generators/metadata/utils/__tests__/transformers.mjs b/src/generators/metadata/utils/__tests__/transformers.mjs new file mode 100644 index 00000000..0665f795 --- /dev/null +++ b/src/generators/metadata/utils/__tests__/transformers.mjs @@ -0,0 +1,29 @@ +import { strictEqual } from 'node:assert'; +import { describe, it } from 'node:test'; + +import { transformTypeToReferenceLink } from '../transformers.mjs'; + +describe('transformTypeToReferenceLink', () => { + it('should transform a JavaScript primitive type into a Markdown link', () => { + strictEqual( + transformTypeToReferenceLink('string'), + '[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type)' + ); + }); + + it('should transform a JavaScript global type into a Markdown link', () => { + strictEqual( + transformTypeToReferenceLink('Array'), + '[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)' + ); + }); + + it('should transform a type into a Markdown link', () => { + strictEqual( + transformTypeToReferenceLink('SomeOtherType', { + SomeOtherType: 'fromTypeMap', + }), + '[``](fromTypeMap)' + ); + }); +}); diff --git a/src/utils/parser/__tests__/index.test.mjs b/src/generators/metadata/utils/__tests__/yaml.test.mjs similarity index 61% rename from src/utils/parser/__tests__/index.test.mjs rename to src/generators/metadata/utils/__tests__/yaml.test.mjs index ae411291..79389d08 100644 --- a/src/utils/parser/__tests__/index.test.mjs +++ b/src/generators/metadata/utils/__tests__/yaml.test.mjs @@ -1,37 +1,7 @@ import { strictEqual, deepStrictEqual } from 'node:assert'; import { describe, it } from 'node:test'; -import { - parseYAMLIntoMetadata, - transformTypeToReferenceLink, - parseHeadingIntoMetadata, - normalizeYamlSyntax, -} from '../index.mjs'; - -describe('transformTypeToReferenceLink', () => { - it('should transform a JavaScript primitive type into a Markdown link', () => { - strictEqual( - transformTypeToReferenceLink('string'), - '[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type)' - ); - }); - - it('should transform a JavaScript global type into a Markdown link', () => { - strictEqual( - transformTypeToReferenceLink('Array'), - '[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)' - ); - }); - - it('should transform a type into a Markdown link', () => { - strictEqual( - transformTypeToReferenceLink('SomeOtherType', { - SomeOtherType: 'fromTypeMap', - }), - '[``](fromTypeMap)' - ); - }); -}); +import { parseYAMLIntoMetadata, normalizeYamlSyntax } from '../yaml.mjs'; describe('normalizeYamlSyntax', () => { it('should normalize YAML syntax by fixing noncompliant properties', () => { @@ -94,14 +64,4 @@ describe('parseYAMLIntoMetadata', () => { }; deepStrictEqual(parseYAMLIntoMetadata(input), expectedOutput); }); - - it('should parse a raw Heading string into Heading metadata', () => { - const input = '## test'; - const expectedOutput = { - text: '## test', - name: '## test', - depth: 2, - }; - deepStrictEqual(parseHeadingIntoMetadata(input, 2), expectedOutput); - }); }); diff --git a/src/generators/metadata/utils/parse.mjs b/src/generators/metadata/utils/parse.mjs index bb3efeb4..fb4908c3 100644 --- a/src/generators/metadata/utils/parse.mjs +++ b/src/generators/metadata/utils/parse.mjs @@ -6,14 +6,21 @@ import { remove } from 'unist-util-remove'; import { selectAll } from 'unist-util-select'; import { SKIP, visit } from 'unist-util-visit'; -import createMetadata from '../../../metadata.mjs'; -import createNodeSlugger from '../../../utils/parser/slugger.mjs'; -import createQueries from '../../../utils/queries/index.mjs'; +import createNodeSlugger from './slugger.mjs'; +import { transformNodeToHeading } from './transformers.mjs'; +import { + visitLinkReference, + visitMarkdownLink, + visitStability, + visitTextWithTypeNode, + visitTextWithUnixManualNode, + visitYAML, +} from './visitors.mjs'; +import { UNIST } from '../../../utils/queries/index.mjs'; import { getRemark } from '../../../utils/remark.mjs'; import { IGNORE_STABILITY_STEMS } from '../constants.mjs'; // Creates an instance of the Remark processor with GFM support -// which is used for stringifying the AST tree back to Markdown const remarkProcessor = getRemark(); /** @@ -21,31 +28,15 @@ const remarkProcessor = getRemark(); * * @param {ParserOutput} input * @param {Record} typeMap - * @returns {Promise>} + * @returns {Promise>} */ export const parseApiDoc = ({ file, tree }, typeMap) => { /** - * This holds references to all the Metadata entries for a given file - * this is used so we can traverse the AST tree and keep mutating things - * and then stringify the whole api doc file at once without creating sub traversals - * - * Then once we have the whole file parsed, we can split the resulting string into sections - * and seal the Metadata Entries (`.create()`) and return the result to the caller of parae. - * - * @type {Array} + * Collection of metadata entries for the file + * @type {Array} */ const metadataCollection = []; - const { - setHeadingMetadata, - addYAMLMetadata, - updateMarkdownLink, - updateTypeReference, - updateUnixManualReference, - updateLinkReference, - addStabilityMetadata, - } = createQueries(typeMap); - // Creates a new Slugger instance for the current API doc file const nodeSlugger = createNodeSlugger(); @@ -56,22 +47,17 @@ export const parseApiDoc = ({ file, tree }, typeMap) => { const headingNodes = selectAll('heading', tree); // Handles Markdown link references and updates them to be plain links - visit(tree, createQueries.UNIST.isLinkReference, node => - updateLinkReference(node, markdownDefinitions) + visit(tree, UNIST.isLinkReference, node => + visitLinkReference(node, markdownDefinitions) ); - // Removes all the original definitions from the tree as they are not needed - // anymore, since all link references got updated to be plain links + // Removes all the original definitions from the tree as they are not needed anymore remove(tree, markdownDefinitions); // Handles the normalisation URLs that reference to API doc files with .md extension - // to replace the .md into .html, since the API doc files get eventually compiled as HTML - visit(tree, createQueries.UNIST.isMarkdownUrl, node => - updateMarkdownLink(node) - ); + visit(tree, UNIST.isMarkdownUrl, node => visitMarkdownLink(node)); // If the document has no headings but it has content, we add a fake heading to the top - // so that our parsing logic can work correctly, and generate content for the whole file if (headingNodes.length === 0 && tree.children.length > 0) { tree.children.unshift(createTree('heading', { depth: 1 }, [])); } @@ -80,80 +66,63 @@ export const parseApiDoc = ({ file, tree }, typeMap) => { // we don't need to check it for stability references const ignoreStability = IGNORE_STABILITY_STEMS.includes(file.stem); - // Handles iterating the tree and creating subtrees for each API doc entry - // where an API doc entry is defined by a Heading Node - // (so all elements after a Heading until the next Heading) - // and then it creates and updates a Metadata entry for each API doc entry - // and then generates the final content for each API doc entry and pushes it to the collection - visit(tree, createQueries.UNIST.isHeading, (headingNode, index) => { - // Creates a new Metadata entry for the current API doc file - const apiEntryMetadata = createMetadata(nodeSlugger); - - // Adds the Metadata of the current Heading Node to the Metadata entry - setHeadingMetadata(headingNode, apiEntryMetadata); - - // We retrieve the immediate next Heading if it exists - // This is used for ensuring that we don't include items that would - // belong only to the next heading to the current Heading metadata - // Note that if there is no next heading, we use the current node as the next one + // Process each heading and create metadata entries + visit(tree, UNIST.isHeading, (headingNode, index) => { + // Initialize heading + headingNode.data = transformNodeToHeading(headingNode); + + // Initialize the metadata + const metadata = /** @type {import('../types').MetadataEntry} */ ({ + api: file.stem, + heading: headingNode, + }); + + // Generate slug and update heading data + metadata.heading.data.slug = nodeSlugger.slug(metadata.heading.data.text); + + // Find the next heading to determine section boundaries const nextHeadingNode = - findAfter(tree, index, createQueries.UNIST.isHeading) ?? headingNode; + findAfter(tree, index, UNIST.isHeading) ?? headingNode; - // This is the cutover index of the subtree that we should get - // of all the Nodes within the AST tree that belong to this section - // If `next` is equals the current heading, it means there's no next heading - // and we are reaching the end of the document, hence the cutover should be the end of - // the document itself. const stop = headingNode === nextHeadingNode ? tree.children.length : tree.children.indexOf(nextHeadingNode); - // Retrieves all the nodes that should belong to the current API docs section - // `index + 1` is used to skip the current Heading Node + // Create subtree for this section const subTree = createTree('root', tree.children.slice(index, stop)); - // Visits all Stability Index nodes from the current subtree if there's any - // and then apply the Stability Index metadata to the current metadata entry - visit(subTree, createQueries.UNIST.isStabilityNode, node => - addStabilityMetadata(node, ignoreStability ? undefined : apiEntryMetadata) + visit(subTree, UNIST.isStabilityNode, node => + visitStability(node, ignoreStability ? undefined : metadata) ); - // Visits all HTML nodes from the current subtree and if there's any that matches - // our YAML metadata structure, it transforms into YAML metadata - // and then apply the YAML Metadata to the current Metadata entry - visit(subTree, createQueries.UNIST.isYamlNode, node => { - // TODO: Is there always only one YAML node? - apiEntryMetadata.setYamlPosition(node.position); - addYAMLMetadata(node, apiEntryMetadata); - }); + // Process YAML nodes directly - merge all YAML data + visit(subTree, UNIST.isYamlNode, node => visitYAML(node, metadata)); - // 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, _, parent) => - updateTypeReference(node, parent) + // If YAML data contains a 'type', use it to override heading type + if (metadata.type) { + metadata.heading.data.type = metadata.type; + } + + // Process type references + visit(subTree, UNIST.isTextWithType, (node, _, parent) => + visitTextWithTypeNode(node, parent, typeMap) ); - // Visits all Unix manual references, and replaces them with links - visit( - subTree, - createQueries.UNIST.isTextWithUnixManual, - (node, _, parent) => updateUnixManualReference(node, parent) + // Process Unix manual references + visit(subTree, UNIST.isTextWithUnixManual, (node, _, parent) => + visitTextWithUnixManualNode(node, parent) ); - // Removes already parsed items from the subtree so that they aren't included in the final content - remove(subTree, [createQueries.UNIST.isYamlNode]); + // Remove processed YAML nodes from the content + remove(subTree, [UNIST.isYamlNode]); - // Applies the AST transformations to the subtree based on the API doc entry Metadata - // Note that running the transformation on the subtree isn't costly as it is a reduced tree - // and the GFM transformations aren't that heavy + // Apply AST transformations const parsedSubTree = remarkProcessor.runSync(subTree); + metadata.content = parsedSubTree; - // We seal and create the API doc entry Metadata and push them to the collection - const parsedApiEntryMetadata = apiEntryMetadata.create(file, parsedSubTree); - - // We push the parsed API doc entry Metadata to the collection - metadataCollection.push(parsedApiEntryMetadata); + // Add to collection + metadataCollection.push(metadata); return SKIP; }); diff --git a/src/utils/parser/slugger.mjs b/src/generators/metadata/utils/slugger.mjs similarity index 93% rename from src/utils/parser/slugger.mjs rename to src/generators/metadata/utils/slugger.mjs index d194cb62..40c7ec7c 100644 --- a/src/utils/parser/slugger.mjs +++ b/src/generators/metadata/utils/slugger.mjs @@ -2,7 +2,7 @@ import GitHubSlugger, { slug as defaultSlugFn } from 'github-slugger'; -import { DOC_API_SLUGS_REPLACEMENTS } from './constants.mjs'; +import { DOC_API_SLUGS_REPLACEMENTS } from '../constants.mjs'; /** * Creates a modified version of the GitHub Slugger diff --git a/src/utils/parser/index.mjs b/src/generators/metadata/utils/transformers.mjs similarity index 66% rename from src/utils/parser/index.mjs rename to src/generators/metadata/utils/transformers.mjs index 1fdda080..858235f8 100644 --- a/src/utils/parser/index.mjs +++ b/src/generators/metadata/utils/transformers.mjs @@ -1,49 +1,14 @@ -'use strict'; - -import yaml from 'yaml'; - +import { transformNodesToString } from '../../../utils/unist.mjs'; import { - DOC_API_HEADING_TYPES, DOC_MDN_BASE_URL_JS_GLOBALS, DOC_MDN_BASE_URL_JS_PRIMITIVES, DOC_TYPES_MAPPING_GLOBALS, DOC_TYPES_MAPPING_OTHER, DOC_TYPES_MAPPING_PRIMITIVES, DOC_MAN_BASE_URL, -} from './constants.mjs'; + DOC_API_HEADING_TYPES, +} from '../constants.mjs'; import { slug } from './slugger.mjs'; -import createQueries from '../queries/index.mjs'; - -/** - * Extracts raw YAML content from a node - * - * @param {import('mdast').Node} node A HTML node containing the YAML content - * @returns {string} The extracted raw YAML content - */ -export const extractYamlContent = node => { - return node.value.replace( - createQueries.QUERIES.yamlInnerContent, - // Either capture a YAML multinline block, or a simple single-line YAML block - (_, simple, yaml) => simple || yaml - ); -}; - -/** - * Normalizes YAML syntax by fixing some non-cool formatted properties of the - * docs schema - * - * @param {string} yamlContent The raw YAML content to normalize - * @returns {string} The normalized YAML content - */ -export const normalizeYamlSyntax = yamlContent => { - return yamlContent - .replace('introduced_in=', 'introduced_in: ') - .replace('source_link=', 'source_link: ') - .replace('type=', 'type: ') - .replace('name=', 'name: ') - .replace('llm_description=', 'llm_description: ') - .replace(/^[\r\n]+|[\r\n]+$/g, ''); // Remove initial and final line breaks -}; /** * @param {string} text The inner text @@ -139,54 +104,31 @@ export const transformTypeToReferenceLink = (type, record) => { return markdownLinks || type; }; -/** - * Parses Markdown YAML source into a JavaScript object containing all the metadata - * (this is forwarded to the parser so it knows what to do with said metadata) - * - * @param {string} yamlString The YAML string to be parsed - * @returns {ApiDocRawMetadataEntry} The parsed YAML metadata - */ -export const parseYAMLIntoMetadata = yamlString => { - const normalizedYaml = normalizeYamlSyntax(yamlString); - - // Ensures that the parsed YAML is an object, because even if it is not - // i.e. a plain string or an array, it will simply not result into anything - /** @type {ApiDocRawMetadataEntry | string} */ - let parsedYaml = yaml.parse(normalizedYaml); - - // Ensure that only Objects get parsed on Object.keys(), since some `