From 678a15c4528369dbb7666b8cf6ec80802bdf27a7 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Wed, 25 Jun 2025 18:31:19 -0400 Subject: [PATCH 1/2] fix(legacy-json): verify list type --- .../legacy-json/utils/parseList.mjs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/generators/legacy-json/utils/parseList.mjs b/src/generators/legacy-json/utils/parseList.mjs index bf8cafea..62afcd9b 100644 --- a/src/generators/legacy-json/utils/parseList.mjs +++ b/src/generators/legacy-json/utils/parseList.mjs @@ -37,6 +37,25 @@ export const extractPattern = (text, pattern, key, current) => { return text.replace(pattern, ''); }; +/** + * Determines if the input List node is a typed list + * @param {import('@types/mdast').List} list + */ +export const isTypedList = list => { + const children = list?.children?.[0]?.children?.[0]?.children; + + // The first element must be a code block + return ( + children?.[0]?.type === 'inlineCode' && + // Followed by a space + children?.[1]?.value.trim() === '' && + // Followed by a link (type) + children?.[2]?.type === 'link' && + // Types start with `<` + children?.[2]?.children?.[0]?.value?.[0] === '<' + ); +}; + /** * Parses an individual list item node to extract its properties * @@ -71,7 +90,7 @@ export function parseListItem(child) { // Parse nested lists (options) recursively if present const optionsNode = child.children.find(node => node.type === 'list'); - if (optionsNode) { + if (isTypedList(optionsNode)) { current.options = optionsNode.children.map(parseListItem); } From b1f44a27ed8551e01ff3fb656b67d24ef9e765a1 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Thu, 26 Jun 2025 11:33:48 -0400 Subject: [PATCH 2/2] add test --- .../utils/__tests__/parseList.test.mjs | 46 +++++++++++++++++++ .../legacy-json/utils/parseList.mjs | 16 +++++-- 2 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/generators/legacy-json/utils/__tests__/parseList.test.mjs b/src/generators/legacy-json/utils/__tests__/parseList.test.mjs index 1806cb84..11b2fee3 100644 --- a/src/generators/legacy-json/utils/__tests__/parseList.test.mjs +++ b/src/generators/legacy-json/utils/__tests__/parseList.test.mjs @@ -123,4 +123,50 @@ describe('parseList', () => { parseList(section, nodes); assert.ok(Array.isArray(section.params)); }); + + it('processes recursive lists', () => { + const section = { type: 'event' }; + const nodes = [ + { + type: 'list', + children: [ + { + children: [ + { + type: 'paragraph', + children: [ + { type: 'text', value: 'param1 {string} first parameter' }, + ], + }, + // This is a nested typed list + { + type: 'list', + children: [ + { + children: [ + { + type: 'paragraph', + children: [ + { type: 'inlineCode', value: 'option' }, // inline code + { type: 'text', value: ' ' }, // space + { + type: 'link', + children: [{ type: 'text', value: '' }], // link with < value + }, + { type: 'text', value: ' option description' }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ]; + + parseList(section, nodes); + assert.equal(section.params[0].options.length, 1); + }); }); diff --git a/src/generators/legacy-json/utils/parseList.mjs b/src/generators/legacy-json/utils/parseList.mjs index 62afcd9b..0d8a1c33 100644 --- a/src/generators/legacy-json/utils/parseList.mjs +++ b/src/generators/legacy-json/utils/parseList.mjs @@ -42,10 +42,15 @@ export const extractPattern = (text, pattern, key, current) => { * @param {import('@types/mdast').List} list */ export const isTypedList = list => { + if (list.type !== 'list') { + // Exit early if not a list + return false; + } + const children = list?.children?.[0]?.children?.[0]?.children; - // The first element must be a code block return ( + // The first element must be a code block children?.[0]?.type === 'inlineCode' && // Followed by a space children?.[1]?.value.trim() === '' && @@ -65,9 +70,11 @@ export const isTypedList = list => { export function parseListItem(child) { const current = {}; + const subList = child.children.find(isTypedList); + // Extract and clean raw text from the node, excluding nested lists current.textRaw = transformTypeReferences( - transformNodesToString(child.children.filter(node => node.type !== 'list')) + transformNodesToString(child.children.filter(node => node !== subList)) .replace(/\s+/g, ' ') .replace(//gs, '') ); @@ -89,9 +96,8 @@ export function parseListItem(child) { current.desc = text.replace(LEADING_HYPHEN, '').trim() || undefined; // Parse nested lists (options) recursively if present - const optionsNode = child.children.find(node => node.type === 'list'); - if (isTypedList(optionsNode)) { - current.options = optionsNode.children.map(parseListItem); + if (subList) { + current.options = subList.children.map(parseListItem); } return current;