Skip to content

Commit 580bc4d

Browse files
authored
fix(legacy-json): verify list type (#322)
1 parent 2faca0b commit 580bc4d

File tree

2 files changed

+75
-4
lines changed

2 files changed

+75
-4
lines changed

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,4 +123,50 @@ describe('parseList', () => {
123123
parseList(section, nodes);
124124
assert.ok(Array.isArray(section.params));
125125
});
126+
127+
it('processes recursive lists', () => {
128+
const section = { type: 'event' };
129+
const nodes = [
130+
{
131+
type: 'list',
132+
children: [
133+
{
134+
children: [
135+
{
136+
type: 'paragraph',
137+
children: [
138+
{ type: 'text', value: 'param1 {string} first parameter' },
139+
],
140+
},
141+
// This is a nested typed list
142+
{
143+
type: 'list',
144+
children: [
145+
{
146+
children: [
147+
{
148+
type: 'paragraph',
149+
children: [
150+
{ type: 'inlineCode', value: 'option' }, // inline code
151+
{ type: 'text', value: ' ' }, // space
152+
{
153+
type: 'link',
154+
children: [{ type: 'text', value: '<boolean>' }], // link with < value
155+
},
156+
{ type: 'text', value: ' option description' },
157+
],
158+
},
159+
],
160+
},
161+
],
162+
},
163+
],
164+
},
165+
],
166+
},
167+
];
168+
169+
parseList(section, nodes);
170+
assert.equal(section.params[0].options.length, 1);
171+
});
126172
});

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

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,30 @@ export const extractPattern = (text, pattern, key, current) => {
3737
return text.replace(pattern, '');
3838
};
3939

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+
4064
/**
4165
* Parses an individual list item node to extract its properties
4266
*
@@ -46,9 +70,11 @@ export const extractPattern = (text, pattern, key, current) => {
4670
export function parseListItem(child) {
4771
const current = {};
4872

73+
const subList = child.children.find(isTypedList);
74+
4975
// Extract and clean raw text from the node, excluding nested lists
5076
current.textRaw = transformTypeReferences(
51-
transformNodesToString(child.children.filter(node => node.type !== 'list'))
77+
transformNodesToString(child.children.filter(node => node !== subList))
5278
.replace(/\s+/g, ' ')
5379
.replace(/<!--.*?-->/gs, '')
5480
);
@@ -70,9 +96,8 @@ export function parseListItem(child) {
7096
current.desc = text.replace(LEADING_HYPHEN, '').trim() || undefined;
7197

7298
// Parse nested lists (options) recursively if present
73-
const optionsNode = child.children.find(node => node.type === 'list');
74-
if (optionsNode) {
75-
current.options = optionsNode.children.map(parseListItem);
99+
if (subList) {
100+
current.options = subList.children.map(parseListItem);
76101
}
77102

78103
return current;

0 commit comments

Comments
 (0)