Skip to content

Commit 8020c31

Browse files
committed
add layout, changehistory element
1 parent 5e7fe69 commit 8020c31

File tree

7 files changed

+150
-36
lines changed

7 files changed

+150
-36
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"acorn": "^8.14.1",
4545
"commander": "^13.1.0",
4646
"dedent": "^1.5.3",
47+
"estree-util-value-to-estree": "^3.4.0",
4748
"estree-util-visit": "^2.0.0",
4849
"github-slugger": "^2.0.0",
4950
"glob": "^11.0.1",

pnpm-lock.yaml

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/generators/jsx/utils/ast.mjs

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,74 @@
11
'use strict';
22

33
import { u as createTree } from 'unist-builder';
4+
import { valueToEstree } from 'estree-util-value-to-estree';
45

56
/**
6-
* Creates an MDX JSX element.
7+
* Creates an MDX JSX element with support for complex attribute values.
78
*
89
* @param {string} name - The name of the JSX element
910
* @param {{
1011
* inline?: boolean,
1112
* children?: string | import('unist').Node[],
12-
* [key: string]: string
13+
* [key: string]: any
1314
* }} [options={}] - Options including type, children, and JSX attributes
1415
* @returns {import('unist').Node} The created MDX JSX element node
1516
*/
1617
export const createJSXElement = (
1718
name,
1819
{ inline = true, children = [], ...attributes } = {}
1920
) => {
21+
// Process children: convert string to text node or use array as is
2022
const processedChildren =
2123
typeof children === 'string'
2224
? [createTree('text', { value: children })]
2325
: (children ?? []);
2426

27+
// Create attribute nodes, handling complex objects and primitive values differently
2528
const attrs = Object.entries(attributes).map(([key, value]) =>
26-
createTree('mdxJsxAttribute', { name: key, value: String(value) })
29+
createAttributeNode(key, value)
2730
);
2831

32+
// Create and return the appropriate JSX element type
2933
return createTree(inline ? 'mdxJsxTextElement' : 'mdxJsxFlowElement', {
3034
name,
3135
attributes: attrs,
3236
children: processedChildren,
3337
});
3438
};
39+
40+
/**
41+
* Creates an MDX JSX attribute node from the input.
42+
*
43+
* @param {string} name - The attribute name
44+
* @param {any} value - The attribute value (can be any valid JS value)
45+
* @returns {import('unist').Node} The MDX JSX attribute node
46+
*/
47+
function createAttributeNode(name, value) {
48+
// For objects and arrays, create expression nodes to preserve structure
49+
if (value !== null && typeof value === 'object') {
50+
return createTree('mdxJsxAttribute', {
51+
name,
52+
value: createTree('mdxJsxAttributeValueExpression', {
53+
data: {
54+
estree: {
55+
type: 'Program',
56+
body: [
57+
{
58+
type: 'ExpressionStatement',
59+
expression: valueToEstree(value),
60+
},
61+
],
62+
sourceType: 'module',
63+
},
64+
},
65+
}),
66+
});
67+
}
68+
69+
// For primitives, use simple string conversion
70+
return createTree('mdxJsxAttribute', {
71+
name,
72+
value: String(value),
73+
});
74+
}

src/generators/jsx/utils/buildContent.mjs

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
'use strict';
22

3-
// External dependencies
43
import { h as createElement } from 'hastscript';
54
import { u as createTree } from 'unist-builder';
65
import { SKIP, visit } from 'unist-util-visit';
6+
import { rcompare } from 'semver';
77

8-
// Internal dependencies
9-
import createQueries from '../../../utils/queries/index.mjs';
8+
import { UNIST } from '../../../utils/queries/index.mjs';
109
import { createJSXElement } from './ast.mjs';
1110
import { ICON_SYMBOL_MAP, STABILITY_LEVELS } from '../constants.mjs';
1211
import { DOC_NODE_BLOB_BASE_URL } from '../../../constants.mjs';
12+
import { enforceArray } from '../../../utils/generators.mjs';
1313

1414
/**
1515
* Transforms a stability node into an AlertBox JSX element
@@ -28,28 +28,75 @@ function visitStabilityNode(node, index, parent) {
2828
}
2929

3030
/**
31-
* Adds the metadata around the header node
31+
* Builds a history of changes for an API element
3232
*
33-
* @param {ApiDocMetadataEntry} entry - The content object to process
34-
* @param {import('mdast').Heading} node - The stability node to transform
33+
* @param {ApiDocMetadataEntry} entry - The metadata entry containing change information
34+
*/
35+
function buildChangeElement(entry) {
36+
// Create change entries from version fields (added, deprecated, etc.)
37+
const changeTypes = {
38+
added_in: 'Added in',
39+
deprecated_in: 'Deprecated in',
40+
removed_in: 'Removed in',
41+
introduced_in: 'Introduced in',
42+
};
43+
44+
const history = Object.entries(changeTypes)
45+
.filter(([field]) => entry[field])
46+
.map(([field, label]) => {
47+
const versions = enforceArray(entry[field]);
48+
return {
49+
versions,
50+
label: `${label}: ${versions.join(', ')}`,
51+
};
52+
});
53+
54+
// Add explicit changes
55+
if (entry.changes) {
56+
const changesHistory = entry.changes.map(change => ({
57+
versions: enforceArray(change.version),
58+
label: change.description,
59+
url: change['pr-url'],
60+
}));
61+
history.push(...changesHistory);
62+
}
63+
64+
// Sort by version, newest first
65+
return createJSXElement('ChangeHistory', {
66+
changes: history.sort((a, b) => rcompare(a.versions[0], b.versions[0])),
67+
});
68+
}
69+
70+
/**
71+
* Enhances a heading node with metadata, source links, and styling
72+
*
73+
* @param {ApiDocMetadataEntry} entry - The API metadata entry
74+
* @param {import('mdast').Heading} node - The heading node to transform
3575
* @param {number} index - The index of the node in its parent's children array
36-
* @param {import('unist').Node} parent - The parent node containing the stability node
76+
* @param {import('unist').Node} parent - The parent node containing the heading
3777
*/
38-
function visitHeadingNode(entry, { data, children }, index, parent) {
39-
console.error(data);
78+
function visitHeadingNode(entry, node, index, parent) {
79+
const { data, children } = node;
80+
const headerChildren = [
81+
createElement(`h${data.depth + 1}`, [
82+
createElement(`a.mark#${data.slug}`, { href: `#${data.slug}` }, children),
83+
]),
84+
];
85+
4086
// Add type icon if available
4187
if (ICON_SYMBOL_MAP[data.type]) {
42-
// TODO: This hasn't been implemented yet. This is an assumption of what a
43-
// potential implementation could look like
44-
children.unshift(
88+
headerChildren.unshift(
4589
createJSXElement('CircularIcon', ICON_SYMBOL_MAP[data.type])
4690
);
4791
}
4892

49-
// Replace node with proper heading and anchor
50-
parent.children[index] = createElement(`h${data.depth + 1}`, [
51-
createElement(`a.mark#${data.slug}`, { href: `#${data.slug}` }, children),
52-
]);
93+
const changeElement = buildChangeElement(entry);
94+
if (changeElement) {
95+
headerChildren.push(changeElement);
96+
}
97+
98+
// Replace node with new heading and anchor
99+
parent.children[index] = createElement('div', headerChildren);
53100

54101
// Add source link if available
55102
if (entry.source_link) {
@@ -71,14 +118,13 @@ function visitHeadingNode(entry, { data, children }, index, parent) {
71118
}
72119

73120
/**
74-
* Processes content by transforming stability nodes
121+
* Processes an API documentation entry by applying transformations to its content
75122
*
76-
* @param {ApiDocMetadataEntry} entry - The entry
123+
* @param {ApiDocMetadataEntry} entry - The API metadata entry to process
77124
*/
78125
function process(entry) {
79-
// Create a shallow copy to avoid modifying the original
126+
// Create a deep copy to avoid modifying the original
80127
const content = structuredClone(entry.content);
81-
const { UNIST } = createQueries;
82128

83129
// Apply all transformations to the content
84130
visit(content, UNIST.isStabilityNode, visitStabilityNode);
@@ -92,10 +138,23 @@ function process(entry) {
92138
/**
93139
* Transforms API metadata entries into processed MDX content
94140
*
95-
* @param {Array<ApiDocMetadataEntry>} metadataEntries - API documentation metadata entries
141+
* @param {ApiDocMetadataEntry[]} metadataEntries - API documentation metadata entries
96142
* @param {import('unified').Processor} remark - Remark processor instance for markdown processing
97143
*/
98144
export default function buildContent(metadataEntries, remark) {
99-
const rootNode = createTree('root', metadataEntries.map(process));
100-
return remark.stringify(remark.runSync(rootNode));
145+
const root = createTree('root', [
146+
createJSXElement('NavBar'),
147+
createJSXElement('MainLayout', {
148+
children: [
149+
createJSXElement('SideBar'),
150+
createElement('div', [
151+
createElement('main', metadataEntries.map(process)),
152+
createElement('MetaBar'),
153+
]),
154+
createJSXElement('Footer'),
155+
],
156+
}),
157+
]);
158+
159+
return remark.stringify(remark.runSync(root));
101160
}

src/generators/legacy-json/utils/buildSection.mjs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,7 @@ import { getRemarkRehype } from '../../../utils/remark.mjs';
33
import { transformNodesToString } from '../../../utils/unist.mjs';
44
import { parseList } from './parseList.mjs';
55
import { SECTION_TYPE_PLURALS, UNPROMOTED_KEYS } from '../constants.mjs';
6-
7-
/**
8-
* Converts a value to an array.
9-
* @template T
10-
* @param {T | T[]} val - The value to convert.
11-
* @returns {T[]} The value as an array.
12-
*/
13-
const enforceArray = val => (Array.isArray(val) ? val : [val]);
14-
6+
import { enforceArray } from '../../../utils/generators.mjs';
157
/**
168
*
179
*/

src/linter/rules/invalid-change-version.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { LINT_MESSAGES } from '../constants.mjs';
22
import { valid, parse } from 'semver';
33
import { env } from 'node:process';
4+
import { enforceArray } from '../../utils/generators.mjs';
45

56
const NODE_RELEASED_VERSIONS = env.NODE_RELEASED_VERSIONS?.split(',');
67

@@ -56,7 +57,7 @@ const isInvalid = NODE_RELEASED_VERSIONS
5657
export const invalidChangeVersion = entries =>
5758
entries.flatMap(({ changes, api_doc_source, yaml_position }) =>
5859
changes.flatMap(({ version }) =>
59-
(Array.isArray(version) ? version : [version])
60+
enforceArray(version)
6061
.filter(isInvalid)
6162
.map(version => ({
6263
level: 'error',

src/utils/generators.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,3 +53,11 @@ export const coerceSemVer = version => {
5353

5454
return coercedVersion;
5555
};
56+
57+
/**
58+
* Converts a value to an array.
59+
* @template T
60+
* @param {T | T[]} val - The value to convert.
61+
* @returns {T[]} The value as an array.
62+
*/
63+
export const enforceArray = val => (Array.isArray(val) ? val : [val]);

0 commit comments

Comments
 (0)