-
Notifications
You must be signed in to change notification settings - Fork 45
feat(metadata): support advanced generics using recursion #763
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 4 commits
9975bb3
bb6c438
c69122a
00d8c39
bb43bc8
d343adb
e746538
9d04e81
0115291
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| /** | ||
| * Safely splits a string by a given set of separators at depth 0 (ignoring those inside < > or ( )). | ||
| * | ||
| * @param {string} str The string to split | ||
| * @param {string} separator The separator to split by (e.g., '|', '&', ',', '=>') | ||
| * @returns {string[]} The split pieces | ||
| */ | ||
| const splitByOuterSeparator = (str, separator) => { | ||
| const pieces = []; | ||
| let current = ''; | ||
| let depth = 0; | ||
|
|
||
| for (let i = 0; i < str.length; i++) { | ||
| const char = str[i]; | ||
|
|
||
| // Track depth using brackets and parentheses | ||
| if (char === '<' || char === '(') { | ||
| depth++; | ||
| } else if ((char === '>' && str[i - 1] !== '=') || char === ')') { | ||
| depth--; | ||
| } | ||
|
|
||
| // Check for multi-character separators like '=>' | ||
| const isArrow = separator === '=>' && char === '=' && str[i + 1] === '>'; | ||
| // Check for single-character separators | ||
| const isCharSeparator = separator === char; | ||
|
|
||
| if (depth === 0 && (isCharSeparator || isArrow)) { | ||
| pieces.push(current.trim()); | ||
| current = ''; | ||
| if (isArrow) { | ||
| i++; | ||
| } // skip the '>' part of '=>' | ||
| continue; | ||
| } | ||
|
|
||
| current += char; | ||
| } | ||
|
|
||
| pieces.push(current.trim()); | ||
| return pieces; | ||
| }; | ||
| /** | ||
| * Recursively parses advanced TypeScript types, including Unions, Intersections, Functions, and Nested Generics. | ||
| * * @param {string} typeString The plain type string to evaluate | ||
| * @param {Function} transformType The function used to resolve individual types into links | ||
| * @returns {string|null} The formatted Markdown link(s), or null if the base type doesn't map | ||
| */ | ||
| export const parseType = (typeString, transformType) => { | ||
| const trimmed = typeString.trim(); | ||
| if (!trimmed) { | ||
| return null; | ||
| } | ||
|
|
||
| // Handle Unions (|) | ||
| if (trimmed.includes('|')) { | ||
| const parts = splitByOuterSeparator(trimmed, '|'); | ||
| if (parts.length > 1) { | ||
| // Re-evaluate each part recursively and join with ' | ' | ||
| const resolvedParts = parts.map( | ||
| p => parseType(p, transformType) || `\`<${p}>\`` | ||
| ); | ||
| return resolvedParts.join(' | '); | ||
| } | ||
| } | ||
|
|
||
| // Handle Intersections (&) | ||
| if (trimmed.includes('&')) { | ||
| const parts = splitByOuterSeparator(trimmed, '&'); | ||
| if (parts.length > 1) { | ||
| // Re-evaluate each part recursively and join with ' & ' | ||
| const resolvedParts = parts.map( | ||
| p => parseType(p, transformType) || `\`<${p}>\`` | ||
| ); | ||
| return resolvedParts.join(' & '); | ||
| } | ||
| } | ||
|
|
||
| // Handle Functions (=>) | ||
| if (trimmed.includes('=>')) { | ||
| const parts = splitByOuterSeparator(trimmed, '=>'); | ||
| if (parts.length === 2) { | ||
| const params = parts[0]; | ||
| const returnType = parts[1]; | ||
|
|
||
| // Preserve the function signature, just link the return type for now | ||
| // (Mapping param types inside the signature string is complex and often unnecessary for simple docs) | ||
| const parsedReturn = | ||
| parseType(returnType, transformType) || `\`<${returnType}>\``; | ||
| return `${params} => ${parsedReturn}`; | ||
| } | ||
| } | ||
|
moshams272 marked this conversation as resolved.
|
||
|
|
||
| // 3. Handle Generics (Base<Inner, Inner>) | ||
| if (trimmed.includes('<') && trimmed.endsWith('>')) { | ||
| const firstBracketIndex = trimmed.indexOf('<'); | ||
| const baseType = trimmed.slice(0, firstBracketIndex).trim(); | ||
| const innerType = trimmed.slice(firstBracketIndex + 1, -1).trim(); | ||
|
|
||
| const baseResult = transformType(baseType.replace(/\[\]$/, '')); | ||
| const baseFormatted = baseResult | ||
| ? `[\`<${baseType}>\`](${baseResult})` | ||
| : `\`<${baseType}>\``; | ||
|
|
||
| // Split arguments safely by comma | ||
| const innerArgs = splitByOuterSeparator(innerType, ','); | ||
| const innerFormatted = innerArgs | ||
| .map(arg => parseType(arg, transformType) || `\`<${arg}>\``) | ||
| .join(', '); | ||
|
|
||
| return `${baseFormatted}<${innerFormatted}>`; | ||
| } | ||
|
|
||
| // Base Case: Plain Type (e.g., string, Buffer, Function) | ||
| const result = transformType(trimmed.replace(/\[\]$/, '')); | ||
| if (trimmed.length && result) { | ||
| return `[\`<${trimmed}>\`](${result})`; | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Array type link text formatting changed from old behaviorLow Severity For plain array types like Reviewed by Cursor Bugbot for commit d343adb. Configure here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think |
||
|
|
||
| return null; | ||
| }; | ||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MyTypeshould be handled, no?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, Parsing individual types inside the parameter string (handling colons, commas, optional params ?, etc.) requires a much deeper level of AST-like parsing and I focused on generics and left the parameter string for now as I will open another PR soon to handle this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just wanted to drop a quick update: regarding your question about
myTypenot being parsed, I went ahead and implemented the function signature parsing.