Skip to content

Commit fc40b12

Browse files
committed
merge signatures
1 parent 9a500cf commit fc40b12

File tree

2 files changed

+125
-56
lines changed

2 files changed

+125
-56
lines changed

src/generators/jsx-ast/utils/buildContent.mjs

Lines changed: 89 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,10 @@ const createChangeElement = entry => {
3737
// Process lifecycle changes (added, deprecated, etc.)
3838
...Object.entries(LIFECYCLE_LABELS)
3939
.filter(([field]) => entry[field])
40-
.map(([field, label]) => {
41-
const versions = enforceArray(entry[field]);
42-
return {
43-
versions,
44-
label: `${label}: ${versions.join(', ')}`,
45-
};
46-
}),
40+
.map(([field, label]) => ({
41+
versions: enforceArray(entry[field]),
42+
label: `${label}: ${enforceArray(entry[field]).join(', ')}`,
43+
})),
4744

4845
// Process explicit changes if they exist
4946
...(entry.changes?.map(change => ({
@@ -53,7 +50,7 @@ const createChangeElement = entry => {
5350
})) || []),
5451
];
5552

56-
if (changeEntries.length === 0) {
53+
if (!changeEntries.length) {
5754
return null;
5855
}
5956

@@ -69,20 +66,17 @@ const createChangeElement = entry => {
6966
* @param {string|undefined} sourceLink - The source link path
7067
* @returns {import('hastscript').Element|null} The source link element or null if no source link
7168
*/
72-
const createSourceLink = sourceLink => {
73-
if (!sourceLink) {
74-
return null;
75-
}
76-
77-
return createElement('span', [
78-
INTERNATIONALIZABLE.sourceCode,
79-
createElement(
80-
'a',
81-
{ href: `${DOC_NODE_BLOB_BASE_URL}${sourceLink}` },
82-
sourceLink
83-
),
84-
]);
85-
};
69+
const createSourceLink = sourceLink =>
70+
sourceLink
71+
? createElement('span', [
72+
INTERNATIONALIZABLE.sourceCode,
73+
createElement(
74+
'a',
75+
{ href: `${DOC_NODE_BLOB_BASE_URL}${sourceLink}` },
76+
sourceLink
77+
),
78+
])
79+
: null;
8680

8781
/**
8882
* Creates a heading element with appropriate styling and metadata
@@ -102,30 +96,25 @@ const createHeadingElement = (content, changeElement) => {
10296
{ trimWhitespace: true }
10397
).children;
10498

105-
const headingChildren = [
106-
createElement('div', [
107-
createElement(`h${depth}`, [
108-
createElement(`a#${slug}`, { href: `#${slug}` }, headingContent),
109-
]),
99+
const headingWrapper = createElement('div', [
100+
createElement(`h${depth}`, [
101+
createElement(`a#${slug}`, { href: `#${slug}` }, headingContent),
110102
]),
111-
];
103+
]);
112104

113105
// Add type icon if available
114106
if (type && type !== 'misc') {
115-
headingChildren[0].children.unshift(
116-
createJSXElement(JSX_IMPORTS.DataTag.name, {
117-
kind: type,
118-
size: 'sm',
119-
})
107+
headingWrapper.children.unshift(
108+
createJSXElement(JSX_IMPORTS.DataTag.name, { kind: type, size: 'sm' })
120109
);
121110
}
122111

123112
// Add change history if available
124113
if (changeElement) {
125-
headingChildren[0].children.push(changeElement);
114+
headingWrapper.children.push(changeElement);
126115
}
127116

128-
return createElement('div', headingChildren);
117+
return headingWrapper;
129118
};
130119

131120
/**
@@ -159,10 +148,11 @@ const transformStabilityNode = (node, index, parent) => {
159148
* @returns {[typeof SKIP]} Visitor instruction to skip the node
160149
*/
161150
const transformHeadingNode = (entry, node, index, parent) => {
162-
const changeElement = createChangeElement(entry);
163-
164151
// Replace node with new heading and anchor
165-
parent.children[index] = createHeadingElement(node, changeElement);
152+
parent.children[index] = createHeadingElement(
153+
node,
154+
createChangeElement(entry)
155+
);
166156

167157
// Add source link if available
168158
const sourceLink = createSourceLink(entry.source_link);
@@ -172,11 +162,7 @@ const transformHeadingNode = (entry, node, index, parent) => {
172162

173163
// Add method signatures for appropriate types
174164
if (TYPES_WITH_METHOD_SIGNATURES.includes(node.data.type)) {
175-
parent.children.splice(
176-
index + (sourceLink ? 2 : 1),
177-
1,
178-
...createSignatureElements(entry)
179-
);
165+
createSignatureElements(parent, node.data.entries || [entry]);
180166
}
181167

182168
return [SKIP];
@@ -222,6 +208,62 @@ const createDocumentLayout = (entries, sideBarProps, metaBarProps) => {
222208
]);
223209
};
224210

211+
/**
212+
* Identifies and removes duplicate headings across metadata entries while tracking them
213+
* @param {Array<ApiDocMetadataEntry>} metadataEntries - API documentation metadata entries
214+
* @returns {Array<ApiDocMetadataEntry>} Processed entries with duplicates removed
215+
*/
216+
const removeDuplicates = metadataEntries => {
217+
// Group entries by their heading's full name
218+
const entriesByName = {};
219+
220+
// First pass: identify headings with method signatures
221+
metadataEntries.forEach(entry => {
222+
visit(entry.content, createQueries.UNIST.isHeading, node => {
223+
if (TYPES_WITH_METHOD_SIGNATURES.includes(node.data.type)) {
224+
const fullName = getFullName(node.data);
225+
(entriesByName[fullName] ??= []).push({ entry, node });
226+
}
227+
});
228+
});
229+
230+
// Second pass: remove duplicates, keeping only the last occurrence
231+
for (const matches of Object.values(entriesByName)) {
232+
if (matches.length > 1) {
233+
// Get the last match which will be kept
234+
const lastMatch = matches[matches.length - 1];
235+
236+
// Add all entries to the last entry's node data
237+
lastMatch.node.data.entries = matches.map(match => match.entry);
238+
239+
// Remove all but the last duplicate from their parent nodes
240+
matches.slice(0, -1).forEach(match => {
241+
const { entry, node } = match;
242+
243+
// Find the parent of the node to remove
244+
visit(entry.content, parent => {
245+
// Check if this parent contains our node
246+
const index = (parent.children || []).indexOf(node);
247+
if (index !== -1) {
248+
// Remove the node from its parent's children
249+
parent.children.splice(index, 1);
250+
return true; // Stop traversal once found and removed
251+
}
252+
return false;
253+
});
254+
});
255+
}
256+
}
257+
258+
// Filter out any entries that are now empty after removal
259+
return metadataEntries.filter(
260+
entry =>
261+
entry.content &&
262+
entry.content.children &&
263+
entry.content.children.length > 0
264+
);
265+
};
266+
225267
/**
226268
* @typedef {import('estree').Node & { data: ApiDocMetadataEntry }} JSXContent
227269
*
@@ -233,9 +275,11 @@ const createDocumentLayout = (entries, sideBarProps, metaBarProps) => {
233275
* @returns {JSXContent} The processed MDX content
234276
*/
235277
const buildContent = (metadataEntries, head, sideBarProps, remark) => {
236-
const metaBarProps = buildMetaBarProps(head, metadataEntries);
278+
const processedEntries = removeDuplicates(metadataEntries);
279+
const metaBarProps = buildMetaBarProps(head, processedEntries);
280+
237281
const root = createDocumentLayout(
238-
metadataEntries,
282+
processedEntries,
239283
sideBarProps,
240284
metaBarProps
241285
);

src/generators/jsx-ast/utils/createSignatureElements.mjs

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ const createPropertyTable = node => {
129129
*/
130130
const generateSignatures = (functionName, signature) => {
131131
const { params, return: returnType } = signature;
132-
const returnStr = returnType ? `: ${returnType.name}` : '';
132+
const returnStr = returnType ? `: ${returnType.type}` : '';
133133

134134
// Handle case with no optional parameters
135135
const optionalParams = params.filter(param => param.optional);
@@ -200,18 +200,43 @@ export const getFullName = ({ name, text }) => {
200200
};
201201

202202
/**
203-
* Creates documentation from an API metadata entry
204-
* @param {ApiDocMetadataEntry} entry - API documentation metadata
203+
* Creates documentation from API metadata entries
204+
* @param {import('@types/mdast').Parent} parent - The parent node
205+
* @param {ApiDocMetadataEntry[]} entries - Array of API documentation metadata entries
205206
*/
206-
export default entry => {
207-
const list = entry.content.children.find(isTypedList);
207+
export default (parent, entries) => {
208+
// Find the list index in the parent for later replacement
209+
const listIdx = parent.children.findIndex(isTypedList);
210+
if (listIdx === -1) {
211+
return;
212+
}
213+
214+
// Create array for all elements from all entries
215+
const elements = [];
208216

209-
const params = list ? list.children.map(parseListItem) : [];
210-
const signature = parseSignature(entry.heading.data.text, params);
211-
const displayName = getFullName(entry.heading.data);
217+
// Process all entries
218+
for (const entry of entries) {
219+
// Find the list in the container
220+
const list = entry.content.children.find(isTypedList);
212221

213-
const signatureCodeBlock = createSignatureCodeBlock(displayName, signature);
214-
const propertyTable = list ? createPropertyTable(list) : null;
222+
// Process the entry
223+
const params = list ? list.children.map(parseListItem) : [];
224+
const signature = parseSignature(entry.heading.data.text, params);
225+
const displayName = getFullName(entry.heading.data);
215226

216-
return [signatureCodeBlock, propertyTable].filter(Boolean);
227+
// Create signature code block
228+
const signatureCodeBlock = createSignatureCodeBlock(displayName, signature);
229+
if (signatureCodeBlock) elements.push(signatureCodeBlock);
230+
231+
// Create property table
232+
if (list) {
233+
const propertyTable = createPropertyTable(list);
234+
elements.push(propertyTable);
235+
}
236+
}
237+
238+
// Replace the list in the parent with all collected elements
239+
if (elements.length > 0) {
240+
parent.children.splice(listIdx, 1, ...elements);
241+
}
217242
};

0 commit comments

Comments
 (0)