Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions src/utils/parser/__tests__/index.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,41 @@ describe('transformTypeToReferenceLink', () => {
'[`<SomeOtherType>`](fromTypeMap)'
);
});

it('should transform a basic Generic type into a Markdown link', () => {
strictEqual(
transformTypeToReferenceLink('{Promise<string>}'),
'[`<Promise>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[`<string>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type)&gt;'
);
});

it('should partially transform a Generic type if only one part is known', () => {
strictEqual(
transformTypeToReferenceLink('{CustomType<string>}', {}),
'`<CustomType>`&lt;[`<string>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type)&gt;'
);
});

it('should transform a Generic type with an inner union like {Promise<string|boolean>}', () => {
strictEqual(
transformTypeToReferenceLink('{Promise<string|boolean>}', {}),
'[`<Promise>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[`<string>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) | [`<boolean>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#boolean_type)&gt;'
);
});

it('should transform multi-parameter generics like {Map<string, number>}', () => {
strictEqual(
transformTypeToReferenceLink('{Map<string, number>}', {}),
'[`<Map>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)&lt;[`<string>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type), [`<number>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type)&gt;'
);
});

it('should handle outer unions with generics like {Promise<string|number> | boolean}', () => {
strictEqual(
transformTypeToReferenceLink('{Promise<string|number> | boolean}', {}),
'[`<Promise>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[`<string>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#string_type) | [`<number>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#number_type)&gt; | [`<boolean>`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#boolean_type)'
);
});
});

describe('normalizeYamlSyntax', () => {
Expand Down
3 changes: 3 additions & 0 deletions src/utils/parser/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ export const DOC_API_HEADING_TYPES = [
},
];

// This regex is used to match basic TypeScript generic types (e.g., Promise<string>)
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
Expand Down
93 changes: 88 additions & 5 deletions src/utils/parser/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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<string>).
* 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}&lt;${innerFormatted}&gt;`;
}

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)
Expand All @@ -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
Expand Down Expand Up @@ -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];
}

Expand All @@ -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) {
Expand Down
Loading