Skip to content

Commit f377b33

Browse files
committed
support malformed returns
1 parent fc40b12 commit f377b33

File tree

4 files changed

+167
-125
lines changed

4 files changed

+167
-125
lines changed

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,11 @@ const transformHeadingNode = (entry, node, index, parent) => {
162162

163163
// Add method signatures for appropriate types
164164
if (TYPES_WITH_METHOD_SIGNATURES.includes(node.data.type)) {
165-
createSignatureElements(parent, node.data.entries || [entry]);
165+
createSignatureElements(
166+
parent,
167+
node.data.entries || [entry],
168+
index + (sourceLink ? 2 : 1)
169+
);
166170
}
167171

168172
return [SKIP];
Lines changed: 135 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,160 @@
11
import { h as createElement } from 'hastscript';
22

3-
import {
4-
isTypedList,
5-
parseListItem,
6-
} from '../../legacy-json/utils/parseList.mjs';
3+
import { isTypedList } from '../../../utils/generators.mjs';
4+
import { parseListItem } from '../../legacy-json/utils/parseList.mjs';
75
import parseSignature from '../../legacy-json/utils/parseSignature.mjs';
86

97
/**
108
* Extracts types and description from content nodes
11-
* @param {Array} contentNodes - Content nodes to process
12-
* @returns {Object} Object containing types and description
9+
* @param {import('@types/mdast').Node[]} contentNodes - Content nodes to process
1310
*/
1411
const extractTypesAndDescription = contentNodes => {
15-
if (!contentNodes.length) return { types: [], description: [] };
12+
const types = [];
13+
let i = 0;
1614

17-
const types = [contentNodes[0]];
18-
let descIndex = 1;
15+
// Skip initial whitespace if present
16+
if (contentNodes[0]?.value?.trim() === '') {
17+
i++;
18+
}
19+
20+
// Extract type information
21+
while (i < contentNodes.length) {
22+
const current = contentNodes[i];
23+
const next = contentNodes[i + 1];
1924

20-
// Process union types (separated by " | ")
21-
while (descIndex < contentNodes.length) {
22-
const current = contentNodes[descIndex];
23-
const next = contentNodes[descIndex + 1];
25+
const isTypeSeparator =
26+
current.type === 'text' && current.value === ' | ' && next;
27+
const isGenericType =
28+
current.type === 'link' &&
29+
current.children?.[0]?.type === 'inlineCode' &&
30+
current.children[0].value?.startsWith('<');
2431

25-
if (current.type === 'text' && current.value === ' | ' && next) {
32+
if (isTypeSeparator) {
2633
types.push(current, next);
27-
descIndex += 2;
28-
}
29-
// Handle link as the entire type
30-
else if (current.type === 'link' && types.length === 1) {
31-
types[0] = current;
32-
descIndex++;
34+
i += 2;
35+
} else if (isGenericType) {
36+
types.push(current);
37+
i++;
3338
} else {
3439
break;
3540
}
3641
}
3742

3843
return {
3944
types,
40-
description: contentNodes.slice(descIndex),
45+
description: contentNodes.slice(i),
4146
};
4247
};
4348

4449
/**
45-
* Creates a table from a list of properties
46-
* @param {import('mdast').List} node
50+
* Recursively processes list items into property data objects
51+
* @param {import('@types/mdast').ListItem[]} items - List items to process
52+
* @param {string} prefix - Property name prefix for nested properties
4753
*/
48-
const createPropertyTable = node => {
49-
const tableRows = [];
50-
let hasAnyDescription = false;
51-
const allProperties = [];
52-
53-
/**
54-
* Recursively processes list items into table rows
55-
* @param {Array} items - List items to process
56-
* @param {string} prefix - Property name prefix for nested properties
57-
*/
58-
const processItems = (items, prefix = '') => {
59-
for (const item of items) {
60-
if (!item.children.length) continue;
61-
62-
const [paragraph, ...nestedContent] = item.children;
63-
64-
// Extract property name
65-
const propNode = paragraph.children[0];
66-
const propName = propNode.value || '';
67-
const fullPropName = prefix ? `${prefix}.${propName}` : propName;
68-
const displayName = prefix
69-
? { ...propNode, value: fullPropName }
70-
: propNode;
71-
72-
// Process types and description
73-
const { types, description } = extractTypesAndDescription(
74-
paragraph.children.slice(1)
75-
);
54+
const processListItemsIntoProperties = (items, prefix = '') => {
55+
const properties = [];
7656

77-
// Check if this property has a description
78-
if (description && description.length > 0) {
79-
hasAnyDescription = true;
80-
}
57+
for (const item of items) {
58+
if (!item.children.length) {
59+
continue;
60+
}
61+
62+
const [paragraph, ...nestedContent] = item.children;
63+
64+
// Extract property name
65+
const propNode = paragraph.children[0];
66+
let propName = propNode.value || '';
67+
let displayName = propNode;
68+
let additionalDescription = [];
69+
let types = [];
70+
let description = [];
71+
72+
// Special handling for "Returns: " nodes with a description, and no type
73+
if (propName.startsWith('Returns: ') && propName.length > 9) {
74+
description = propName.substring(9).trim();
75+
propName = 'Returns';
76+
displayName = { ...propNode, value: 'Returns' };
77+
} else {
78+
const extracted = extractTypesAndDescription(paragraph.children.slice(1));
79+
types = extracted.types;
80+
description = extracted.description;
81+
}
82+
83+
const fullPropName = prefix ? `${prefix}.${propName}` : propName;
84+
if (prefix) {
85+
displayName = { ...propNode, value: fullPropName };
86+
}
8187

82-
// Store property data
83-
allProperties.push({
84-
nameCell: createElement('td', displayName),
85-
typeCell: createElement('td', types),
86-
descriptionCell: createElement('td', description),
87-
});
88-
89-
// Process nested properties
90-
const nestedList = nestedContent.find(isTypedList);
91-
if (nestedList) {
92-
processItems(nestedList.children, fullPropName);
88+
// Combine any additional description
89+
const combinedDescription = [...additionalDescription];
90+
if (description && description.length > 0) {
91+
if (combinedDescription.length > 0) {
92+
combinedDescription.push({ type: 'text', value: ' ' });
9393
}
94+
combinedDescription.push(
95+
...(Array.isArray(description) ? description : [description])
96+
);
9497
}
95-
};
9698

97-
processItems(node.children);
99+
// Store property data
100+
properties.push({
101+
nameCell: createElement('td', displayName),
102+
typeCell: createElement('td', types),
103+
descriptionCell: createElement('td', combinedDescription),
104+
hasDescription: combinedDescription.length > 0,
105+
hasTypes: types && types.length > 0,
106+
});
98107

99-
// Create table rows based on whether any property has a description
100-
for (const prop of allProperties) {
101-
const rowCells = [prop.nameCell, prop.typeCell];
102-
if (hasAnyDescription) {
103-
rowCells.push(prop.descriptionCell);
108+
// Process nested properties
109+
const nestedList = nestedContent.find(isTypedList);
110+
if (nestedList) {
111+
const nestedProperties = processListItemsIntoProperties(
112+
nestedList.children,
113+
fullPropName
114+
);
115+
properties.push(...nestedProperties);
104116
}
105-
tableRows.push(createElement('tr', rowCells));
106117
}
107118

108-
// Create table headers based on whether any property has a description
109-
const headerCells = [
110-
createElement('th', 'Property'),
111-
createElement('th', 'Type'),
112-
];
119+
return properties;
120+
};
121+
122+
/**
123+
* Creates a table from a list of properties
124+
* @param {import('mdast').List} node
125+
*/
126+
const createPropertyTable = node => {
127+
// Process all properties
128+
const allProperties = processListItemsIntoProperties(node.children);
129+
130+
// Determine if any properties have descriptions or types
131+
const hasAnyDescription = allProperties.some(prop => prop.hasDescription);
132+
const hasAnyTypes = allProperties.some(prop => prop.hasTypes);
113133

134+
// Create table headers
135+
const headerCells = [createElement('th', 'Property')];
136+
if (hasAnyTypes) {
137+
headerCells.push(createElement('th', 'Type'));
138+
}
114139
if (hasAnyDescription) {
115140
headerCells.push(createElement('th', 'Description'));
116141
}
117142

143+
// Create table rows
144+
const tableRows = allProperties.map(prop => {
145+
const rowCells = [prop.nameCell];
146+
147+
if (hasAnyTypes) {
148+
rowCells.push(prop.typeCell);
149+
}
150+
151+
if (hasAnyDescription) {
152+
rowCells.push(prop.descriptionCell);
153+
}
154+
155+
return createElement('tr', rowCells);
156+
});
157+
118158
return createElement('table', [
119159
createElement('thead', [createElement('tr', headerCells)]),
120160
createElement('tbody', tableRows),
@@ -125,7 +165,6 @@ const createPropertyTable = node => {
125165
* Generates all valid function signatures based on optional parameters
126166
* @param {string} functionName - Name of the function
127167
* @param {import('../../legacy-json/types.d.ts').MethodSignature} signature - Signature object
128-
* @returns {string[]} Array of signature strings
129168
*/
130169
const generateSignatures = (functionName, signature) => {
131170
const { params, return: returnType } = signature;
@@ -150,7 +189,9 @@ const generateSignatures = (functionName, signature) => {
150189
for (let i = 0; i < totalCombinations; i++) {
151190
const includedParams = params.filter((param, index) => {
152191
// Always include required parameters
153-
if (!param.optional) return true;
192+
if (!param.optional) {
193+
return true;
194+
}
154195

155196
// For optional parameters, check if this combination includes it
156197
const optionalIndex = optionalIndexes.indexOf(index);
@@ -160,7 +201,9 @@ const generateSignatures = (functionName, signature) => {
160201
const paramsStr = includedParams
161202
.map(param => {
162203
let paramStr = param.name;
163-
if (param.optional || param.default) paramStr += '?';
204+
if (param.optional || param.default) {
205+
paramStr += '?';
206+
}
164207
return paramStr;
165208
})
166209
.join(', ');
@@ -175,16 +218,10 @@ const generateSignatures = (functionName, signature) => {
175218
* Creates a code block with function signatures
176219
* @param {string} functionName - Name of the function
177220
* @param {import('../../legacy-json/types.d.ts').MethodSignature} signature - Signature object
178-
* @returns {Object} Code block node
179221
*/
180222
const createSignatureCodeBlock = (functionName, signature) => {
181-
// Generate all valid signatures
182223
const signatures = generateSignatures(functionName, signature);
183-
184-
// Join all signatures with newlines
185224
const codeContent = signatures.join('\n');
186-
187-
// Create code block
188225
return createElement('pre', codeContent);
189226
};
190227

@@ -203,15 +240,11 @@ export const getFullName = ({ name, text }) => {
203240
* Creates documentation from API metadata entries
204241
* @param {import('@types/mdast').Parent} parent - The parent node
205242
* @param {ApiDocMetadataEntry[]} entries - Array of API documentation metadata entries
243+
* @param {number} backupIndex - If there isn't a list, use this index
206244
*/
207-
export default (parent, entries) => {
245+
export default (parent, entries, backupIndex) => {
208246
// Find the list index in the parent for later replacement
209247
const listIdx = parent.children.findIndex(isTypedList);
210-
if (listIdx === -1) {
211-
return;
212-
}
213-
214-
// Create array for all elements from all entries
215248
const elements = [];
216249

217250
// Process all entries
@@ -225,18 +258,20 @@ export default (parent, entries) => {
225258
const displayName = getFullName(entry.heading.data);
226259

227260
// Create signature code block
228-
const signatureCodeBlock = createSignatureCodeBlock(displayName, signature);
229-
if (signatureCodeBlock) elements.push(signatureCodeBlock);
261+
elements.push(createSignatureCodeBlock(displayName, signature));
230262

231-
// Create property table
263+
// Create property table if a list exists
232264
if (list) {
233-
const propertyTable = createPropertyTable(list);
234-
elements.push(propertyTable);
265+
elements.push(createPropertyTable(list));
235266
}
236267
}
237268

238269
// Replace the list in the parent with all collected elements
239270
if (elements.length > 0) {
240-
parent.children.splice(listIdx, 1, ...elements);
271+
if (listIdx > -1) {
272+
parent.children.splice(listIdx, 1, ...elements);
273+
} else {
274+
parent.children.splice(backupIndex, 0, ...elements);
275+
}
241276
}
242277
};

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

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
TYPE_EXPRESSION,
77
} from '../constants.mjs';
88
import parseSignature from './parseSignature.mjs';
9+
import { isTypedList } from '../../../utils/generators.mjs';
910
import { transformNodesToString } from '../../../utils/unist.mjs';
1011

1112
/**
@@ -37,30 +38,6 @@ export const extractPattern = (text, pattern, key, current) => {
3738
return text.replace(pattern, '');
3839
};
3940

40-
/**
41-
* Determines if the input List node is a typed list
42-
* @param {import('@types/mdast').List} list
43-
*/
44-
export const isTypedList = list => {
45-
if (list.type !== 'list') {
46-
// Exit early if not a list
47-
return false;
48-
}
49-
50-
const children = list?.children?.[0]?.children?.[0]?.children;
51-
52-
return (
53-
// The first element must be a code block
54-
children?.[0]?.type === 'inlineCode' &&
55-
// Followed by a space
56-
children?.[1]?.value.trim() === '' &&
57-
// Followed by a link (type)
58-
children?.[2]?.type === 'link' &&
59-
// Types start with `<`
60-
children?.[2]?.children?.[0]?.value?.[0] === '<'
61-
);
62-
};
63-
6441
/**
6542
* Parses an individual list item node to extract its properties
6643
*

0 commit comments

Comments
 (0)