Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions src/generators/jsx-ast/generate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ const remarkRecma = getRemarkRecma();
*
* @type {import('./types').Generator['processChunk']}
*/
export async function processChunk(slicedInput, itemIndices, docPages) {
export async function processChunk(
slicedInput,
itemIndices,
{ docPages, stabilityOverviewEntries }
) {
const results = [];

for (const idx of itemIndices) {
Expand All @@ -28,7 +32,8 @@ export async function processChunk(slicedInput, itemIndices, docPages) {
entries,
head,
sideBarProps,
remarkRecma
remarkRecma,
stabilityOverviewEntries
);

results.push(content);
Expand All @@ -54,14 +59,30 @@ export async function* generate(input, worker) {
? config.index.map(({ section, api }) => [section, `${api}.html`])
: headNodes.map(node => [node.heading.data.name, `${node.api}.html`]);

// Pre-compute stability overview data once — avoid serialising full AST nodes to workers
const stabilityOverviewEntries = headNodes
.filter(node => node.stability?.children?.length)
.map(({ api, heading, stability }) => {
const [{ data }] = stability.children;
return {
api,
name: heading.data.name,
stabilityIndex: parseInt(data.index, 10),
stabilityDescription: data.description.split('. ')[0],
};
});

// Create sliced input: each item contains head + its module's entries
// This avoids sending all 4700+ entries to every worker
const entries = headNodes.map(head => ({
head,
entries: groupedModules.get(head.api),
}));

for await (const chunkResult of worker.stream(entries, entries, docPages)) {
for await (const chunkResult of worker.stream(entries, entries, {
docPages,
stabilityOverviewEntries,
})) {
yield chunkResult;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';

import buildStabilityOverview from '../buildStabilityOverview.mjs';

const getAttribute = (node, name) =>
node.attributes.find(attribute => attribute.name === name)?.value;

describe('buildStabilityOverview', () => {
it('builds a table with expected headings and rows', () => {
const entries = [
{
api: 'fs',
name: 'File system',
stabilityIndex: 2,
stabilityDescription: 'Stable',
},
{
api: 'async_context',
name: 'Async context',
stabilityIndex: 1,
stabilityDescription: 'Experimental',
},
];

const result = buildStabilityOverview(entries);

assert.equal(result.tagName, 'table');

const thead = result.children[0];
const tbody = result.children[1];

assert.equal(thead.tagName, 'thead');
assert.equal(tbody.tagName, 'tbody');
assert.equal(tbody.children.length, 2);

const headerCells = thead.children[0].children;
assert.equal(headerCells[0].children[0].value, 'API');
assert.equal(headerCells[1].children[0].value, 'Stability');
});

it('creates links and BadgeGroup cells with mapped props', () => {
const [row] = buildStabilityOverview([
{
api: 'fs',
name: 'File system',
stabilityIndex: 0,
stabilityDescription: 'Deprecated: use fs/promises',
},
]).children[1].children;

const link = row.children[0].children[0];
const badgeGroup = row.children[1].children[0];

assert.equal(link.tagName, 'a');
assert.equal(link.properties.href, 'fs.html');
assert.equal(link.children[0].value, 'File system');

assert.equal(badgeGroup.name, 'BadgeGroup');
assert.equal(getAttribute(badgeGroup, 'as'), 'span');
assert.equal(getAttribute(badgeGroup, 'size'), 'small');
assert.equal(getAttribute(badgeGroup, 'kind'), 'error');
assert.equal(getAttribute(badgeGroup, 'badgeText'), '0');
assert.equal(badgeGroup.children[0].value, 'Deprecated: use fs/promises');
});

it('falls back to success kind for unknown stability index', () => {
const [row] = buildStabilityOverview([
{
api: 'custom',
name: 'Custom API',
stabilityIndex: 9,
stabilityDescription: 'Unknown status',
},
]).children[1].children;

const badgeGroup = row.children[1].children[0];

assert.equal(getAttribute(badgeGroup, 'kind'), 'success');
assert.equal(getAttribute(badgeGroup, 'badgeText'), '9');
});
});
29 changes: 24 additions & 5 deletions src/generators/jsx-ast/utils/buildContent.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { SKIP, visit } from 'unist-util-visit';

import { createJSXElement } from './ast.mjs';
import { buildMetaBarProps } from './buildBarProps.mjs';
import buildStabilityOverview from './buildStabilityOverview.mjs';
import { enforceArray } from '../../../utils/array.mjs';
import createQueries from '../../../utils/queries/index.mjs';
import { JSX_IMPORTS } from '../../web/constants.mjs';
Expand Down Expand Up @@ -256,7 +257,7 @@ export const transformHeadingNode = async (
* @param {ApiDocMetadataEntry} entry - The API metadata entry to process
* @param {import('unified').Processor} remark - The remark processor
*/
export const processEntry = (entry, remark) => {
export const processEntry = (entry, remark, stabilityOverviewEntries = []) => {
// Deep copy content to avoid mutations on original
const content = structuredClone(entry.content);

Expand All @@ -276,6 +277,14 @@ export const processEntry = (entry, remark) => {
(parent.children[idx] = createSignatureTable(node, remark))
);

// Inject the stability overview table where the slot tag is present
if (
stabilityOverviewEntries.length &&
entry.tags.includes('STABILITY_OVERVIEW_SLOT_BEGIN')
) {
content.children.push(buildStabilityOverview(stabilityOverviewEntries));
}

return content;
};

Expand All @@ -290,7 +299,8 @@ export const createDocumentLayout = (
entries,
sideBarProps,
metaBarProps,
remark
remark,
stabilityOverviewEntries = []
) =>
createTree('root', [
createJSXElement(JSX_IMPORTS.NavBar.name),
Expand All @@ -306,7 +316,9 @@ export const createDocumentLayout = (
createElement('br'),
createElement(
'main',
entries.map(entry => processEntry(entry, remark))
entries.map(entry =>
processEntry(entry, remark, stabilityOverviewEntries)
)
),
]),
createJSXElement(JSX_IMPORTS.MetaBar.name, metaBarProps),
Expand All @@ -325,7 +337,13 @@ export const createDocumentLayout = (
* @param {import('unified').Processor} remark - Remark processor instance for markdown processing
* @returns {Promise<JSXContent>}
*/
const buildContent = async (metadataEntries, head, sideBarProps, remark) => {
const buildContent = async (
metadataEntries,
head,
sideBarProps,
remark,
stabilityOverviewEntries = []
) => {
// Build props for the MetaBar from head and entries
const metaBarProps = buildMetaBarProps(head, metadataEntries);

Expand All @@ -334,7 +352,8 @@ const buildContent = async (metadataEntries, head, sideBarProps, remark) => {
metadataEntries,
sideBarProps,
metaBarProps,
remark
remark,
stabilityOverviewEntries
);

// Run remark processor to transform AST (parse markdown, plugins, etc.)
Expand Down
49 changes: 49 additions & 0 deletions src/generators/jsx-ast/utils/buildStabilityOverview.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { h as createElement } from 'hastscript';

import { createJSXElement } from './ast.mjs';
import { JSX_IMPORTS } from '../../web/constants.mjs';

const STABILITY_KINDS = ['error', 'warning', 'default', 'info'];

/**
*
*/
const createBadge = (stabilityIndex, stabilityDescription) => {
const kind = STABILITY_KINDS[stabilityIndex] ?? 'success';

return createJSXElement(JSX_IMPORTS.BadgeGroup.name, {
as: 'span',
size: 'small',
kind,
badgeText: stabilityIndex,
children: stabilityDescription,
});
};

/**
* Builds a static Stability Overview table.
*
* @param {Array<{ api: string, name: string, stabilityIndex: number, stabilityDescription: string }>} entries
* @returns {import('hast').Element}
*/
const buildStabilityOverview = entries => {
const rows = entries.map(
({ api, name, stabilityIndex, stabilityDescription }) =>
createElement('tr', [
createElement('td', createElement('a', { href: `${api}.html` }, name)),
createElement('td', createBadge(stabilityIndex, stabilityDescription)),
])
);

return createElement('table', [
createElement('thead', [
createElement('tr', [
createElement('th', 'API'),
createElement('th', 'Stability'),
]),
]),
createElement('tbody', rows),
]);
};

export default buildStabilityOverview;
4 changes: 4 additions & 0 deletions src/generators/web/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export const JSX_IMPORTS = {
name: 'TableOfContents',
source: '@node-core/ui-components/Common/TableOfContents',
},
BadgeGroup: {
name: 'BadgeGroup',
source: '@node-core/ui-components/Common/BadgeGroup',
},
ChangeHistory: {
name: 'ChangeHistory',
source: '@node-core/ui-components/Common/ChangeHistory',
Expand Down
Loading