diff --git a/src/utils/parser/__tests__/index.test.mjs b/src/utils/parser/__tests__/index.test.mjs index ae411291..96702b0f 100644 --- a/src/utils/parser/__tests__/index.test.mjs +++ b/src/utils/parser/__tests__/index.test.mjs @@ -31,6 +31,41 @@ describe('transformTypeToReferenceLink', () => { '[``](fromTypeMap)' ); }); + + it('should transform a basic Generic type into a Markdown link', () => { + strictEqual( + transformTypeToReferenceLink('{Promise}'), + '[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type)>' + ); + }); + + it('should partially transform a Generic type if only one part is known', () => { + strictEqual( + transformTypeToReferenceLink('{CustomType}', {}), + '``<[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type)>' + ); + }); + + it('should transform a Generic type with an inner union like {Promise}', () => { + strictEqual( + transformTypeToReferenceLink('{Promise}', {}), + '[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) | [``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#boolean_type)>' + ); + }); + + it('should transform multi-parameter generics like {Map}', () => { + strictEqual( + transformTypeToReferenceLink('{Map}', {}), + '[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)<[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type), [``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type)>' + ); + }); + + it('should handle outer unions with generics like {Promise | boolean}', () => { + strictEqual( + transformTypeToReferenceLink('{Promise | boolean}', {}), + '[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) | [``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type)> | [``](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#boolean_type)' + ); + }); }); describe('normalizeYamlSyntax', () => { diff --git a/src/utils/parser/constants.mjs b/src/utils/parser/constants.mjs index 3e54973c..e7965b80 100644 --- a/src/utils/parser/constants.mjs +++ b/src/utils/parser/constants.mjs @@ -69,6 +69,9 @@ export const DOC_API_HEADING_TYPES = [ }, ]; +// This regex is used to match basic TypeScript generic types (e.g., Promise) +export const TYPE_GENERIC_REGEX = /^([^<]+)<([^>]+)>$/; + // This is a mapping for types within the Markdown content and their respective // JavaScript primitive types within the MDN JavaScript docs // @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Data_structures#primitive_values diff --git a/src/utils/parser/index.mjs b/src/utils/parser/index.mjs index 1fdda080..c1a9a024 100644 --- a/src/utils/parser/index.mjs +++ b/src/utils/parser/index.mjs @@ -10,6 +10,7 @@ import { DOC_TYPES_MAPPING_OTHER, DOC_TYPES_MAPPING_PRIMITIVES, DOC_MAN_BASE_URL, + TYPE_GENERIC_REGEX, } from './constants.mjs'; import { slug } from './slugger.mjs'; import createQueries from '../queries/index.mjs'; @@ -59,7 +60,81 @@ export const transformUnixManualToLink = ( ) => { return `[\`${text}\`](${DOC_MAN_BASE_URL}${sectionNumber}/${command}.${sectionNumber}${sectionLetter}.html)`; }; +/** + * Safely splits the string by `|`, ignoring pipes that are inside `< >` + * + * @param {string} str The type string to split + * @returns {string[]} An array of type pieces + */ +const splitByOuterUnion = str => { + const result = []; + let current = ''; + let depth = 0; + + for (const char of str) { + if (char === '<') { + depth++; + } else if (char === '>') { + depth--; + } else if (char === '|' && depth === 0) { + result.push(current); + current = ''; + continue; + } + current += char; + } + + result.push(current); + return result; +}; +/** + * Attempts to parse and format a basic Generic type (e.g., Promise). + * It also supports union and multi-parameter types within the generic brackets. + * + * @param {string} typePiece The plain type piece to be evaluated + * @param {Function} transformType The function used to resolve individual types into links + * @returns {string|null} The formatted Markdown link, or null if no match is found + */ +const formatBasicGeneric = (typePiece, transformType) => { + const genericMatch = typePiece.match(TYPE_GENERIC_REGEX); + + if (genericMatch) { + const baseType = genericMatch[1].trim(); + const innerType = genericMatch[2].trim(); + + const baseResult = transformType(baseType.replace(/\[\]$/, '')); + const baseFormatted = baseResult + ? `[\`<${baseType}>\`](${baseResult})` + : `\`<${baseType}>\``; + + // Split while capturing delimiters (| or ,) to preserve original syntax + const parts = innerType.split(/([|,])/); + + const innerFormatted = parts + .map(part => { + const trimmed = part.trim(); + // If it is a delimiter, return it as is + if (trimmed === '|') { + return ' | '; + } + + if (trimmed === ',') { + return ', '; + } + + const innerRes = transformType(trimmed.replace(/\[\]$/, '')); + return innerRes + ? `[\`<${trimmed}>\`](${innerRes})` + : `\`<${trimmed}>\``; + }) + .join(''); + + return `${baseFormatted}<${innerFormatted}>`; + } + + return null; +}; /** * This method replaces plain text Types within the Markdown content into Markdown links * that link to the actual relevant reference for such type (either internal or external link) @@ -69,8 +144,9 @@ export const transformUnixManualToLink = ( * @returns {string} The Markdown link as a string (formatted in Markdown) */ export const transformTypeToReferenceLink = (type, record) => { - // Removes the wrapping tags that wrap the type references such as `<>` and `{}` - const typeInput = type.replace(/[{}<>]/g, ''); + // Removes the wrapping curly braces that wrap the type references + // We keep the angle brackets `<>` intact here to parse Generics later + const typeInput = type.replace(/[{}]/g, ''); /** * Handles the mapping (if there's a match) of the input text @@ -100,7 +176,7 @@ export const transformTypeToReferenceLink = (type, record) => { // Transform Node.js type/module references into Markdown links // that refer to other API docs pages within the Node.js API docs - if (lookupPiece in record) { + if (record && lookupPiece in record) { return record[lookupPiece]; } @@ -115,13 +191,20 @@ export const transformTypeToReferenceLink = (type, record) => { return ''; }; - const typePieces = typeInput.split('|').map(piece => { + const typePieces = splitByOuterUnion(typeInput).map(piece => { // This is the content to render as the text of the Markdown link const trimmedPiece = piece.trim(); + // 1. Attempt to format as a basic Generic type first + const genericMarkdown = formatBasicGeneric(trimmedPiece, transformType); + if (genericMarkdown) { + return genericMarkdown; + } + + // 2. Fallback to the logic for plain types // This is what we will compare against the API types mappings // The ReGeX below is used to remove `[]` from the end of the type - const result = transformType(trimmedPiece.replace('[]', '')); + const result = transformType(trimmedPiece.replace(/\[\]$/, '')); // If we have a valid result and the piece is not empty, we return the Markdown link if (trimmedPiece.length && result.length) {