From f3947198f5cf9048576e68b21d4db5b11a6eece4 Mon Sep 17 00:00:00 2001 From: avivkeller Date: Mon, 15 Jun 2026 15:31:21 -0500 Subject: [PATCH] chore(parsing): better use of code spans --- .../utils/__tests__/transformers.test.mjs | 28 ++++++------- src/generators/metadata/utils/typeParser.mjs | 41 ++++++++++++------- 2 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/generators/metadata/utils/__tests__/transformers.test.mjs b/src/generators/metadata/utils/__tests__/transformers.test.mjs index cbb5836e..46d02037 100644 --- a/src/generators/metadata/utils/__tests__/transformers.test.mjs +++ b/src/generators/metadata/utils/__tests__/transformers.test.mjs @@ -7,14 +7,14 @@ describe('transformTypeToReferenceLink', () => { it('should transform a JavaScript primitive type into a Markdown link', () => { strictEqual( transformTypeToReferenceLink('string'), - '[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)' + '<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)>' ); }); it('should transform a JavaScript global type into a Markdown link', () => { strictEqual( transformTypeToReferenceLink('Array'), - '[``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)' + '<[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)>' ); }); @@ -23,70 +23,70 @@ describe('transformTypeToReferenceLink', () => { transformTypeToReferenceLink('SomeOtherType', { SomeOtherType: 'fromTypeMap', }), - '[``](fromTypeMap)' + '<[SomeOtherType](fromTypeMap)>' ); }); it('should transform a basic Generic type into a Markdown link', () => { strictEqual( transformTypeToReferenceLink('{Promise}'), - '[``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)>' + '<[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[string](https://developer.mozilla.org/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/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/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) | [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)>' + '<[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) | [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)>>' ); }); it('should transform multi-parameter generics like {Map}', () => { strictEqual( transformTypeToReferenceLink('{Map}', {}), - '[``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type), [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type)>' + '<[Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type), [number](https://developer.mozilla.org/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/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) | [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type)> | [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)' + '<[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) | [number](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type)> | [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)>' ); }); it('should transform an intersection type joined with & into linked parts', () => { strictEqual( transformTypeToReferenceLink('{string&boolean}', {}), - '[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) & [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)' + '<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) & [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)>' ); }); it('should handle an intersection with generics like {Map&Array}', () => { strictEqual( transformTypeToReferenceLink('{Map&Array}', {}), - '[``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type), [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type)> & [``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)>' + '<[Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type), [number](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type)> & [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)>>' ); }); it('should transform a function returning a Generic type', () => { strictEqual( transformTypeToReferenceLink('(err: Error) => Promise', {}), - '(err: [``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)) => [``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)>' + '<(err: [Error](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error)) => [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[boolean](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)>>' ); }); it('should respect precedence: Unions (|) are weaker than Intersections (&)', () => { strictEqual( transformTypeToReferenceLink('string | number & boolean', {}), - '[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) | [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type) & [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)' + '<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) | [number](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type) & [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)>' ); }); @@ -95,7 +95,7 @@ describe('transformTypeToReferenceLink', () => { '(str: string[]) => Promise, Map>'; const expected = - '(str: [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)[]) => [``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type), [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type) & [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)>, [``](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)<[``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) | [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type)>>'; + '<(str: [string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)[]) => [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)<[Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type), [number](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type) & [string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)>, [Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)<[string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type) | [number](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#number_type)>>>'; strictEqual(transformTypeToReferenceLink(input, {}), expected); }); @@ -105,7 +105,7 @@ describe('transformTypeToReferenceLink', () => { '(cb: ([first, second]: string[]) => void) => ({ id, name }: User) => boolean'; const expected = - '(cb: ([first, second]: [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)[]) => ``) => ({ id, name }: [``](userLink)) => [``](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)'; + '<(cb: ([first, second]: [string](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#string_type)[]) => void) => ({ id, name }: [User](userLink)) => [boolean](https://developer.mozilla.org/docs/Web/JavaScript/Data_structures#boolean_type)>'; strictEqual( transformTypeToReferenceLink(input, { User: 'userLink' }), diff --git a/src/generators/metadata/utils/typeParser.mjs b/src/generators/metadata/utils/typeParser.mjs index 5d977379..31ca894e 100644 --- a/src/generators/metadata/utils/typeParser.mjs +++ b/src/generators/metadata/utils/typeParser.mjs @@ -31,15 +31,15 @@ const walkAtDepthZero = (str, onToken) => { } }; -/** Format a known type as a Markdown link, or as a bare code span. */ +/** Resolve a bare type name to its inner rendering with URL */ const formatType = (name, transformType) => { const url = transformType(name); - return url ? `[\`<${name}>\`](${url})` : `\`<${name}>\``; + return url ? `[${name}](${url})` : name; }; -/** Resolve a sub-expression recursively, falling back to a code span. */ +/** Resolve a sub-expression recursively, falling back to the bare text. */ const resolveOr = (part, transformType) => - parseType(part, transformType) || `\`<${part.trim()}>\``; + parseInner(part, transformType) ?? part.trim(); /** * Splits `str` by `separator` at depth 0. `separator` is a single @@ -75,8 +75,6 @@ const splitByOuterSeparator = (str, separator) => { const stripOuterParentheses = typeString => { let s = typeString.trim(); while (s.length >= 2 && s.startsWith('(') && s.endsWith(')')) { - // The outer `(` matches the outer `)` if depth doesn't hit 0 - // anywhere before the final character. let wrapsWhole = true; walkAtDepthZero(s.slice(0, -1), i => { if (i > 0) { @@ -172,20 +170,18 @@ const parseFunctionSignature = (signature, transformType) => { const paramType = colonParts.slice(1).join(':'); return `${paramName}: ${resolveOr(paramType, transformType)}`; } - return parseType(arg, transformType) || arg; + return parseInner(arg, transformType) ?? arg; }); return `${prefix}(${parsedArgs.join(', ')})`; }; /** - * Recursively parses TypeScript types into Markdown links. + * Recursively parses a type into its inner rendering. * - * @param {string} typeString The type string to evaluate. - * @param {(name: string) => string | null | undefined} transformType Resolves a bare type name to a URL, or returns falsy. - * @returns {string | null} Markdown for the type, or null when the base type doesn't resolve. + * @returns {string | null} Inner markup */ -export const parseType = (typeString, transformType) => { +const parseInner = (typeString, transformType) => { const trimmed = stripOuterParentheses(typeString); if (!trimmed) { return null; @@ -197,7 +193,7 @@ export const parseType = (typeString, transformType) => { const left = trimmed.slice(0, op.index).trim(); const right = trimmed.slice(op.index + op.width).trim(); const sig = parseFunctionSignature(left, transformType); - return `${sig} => ${resolveOr(right, transformType)}`; + return `${sig} => ${resolveOr(right, transformType)}`; } // Union / intersection @@ -219,7 +215,7 @@ export const parseType = (typeString, transformType) => { const inner = splitByOuterSeparator(innerType, ',') .map(arg => resolveOr(arg, transformType)) .join(', '); - return `${formatType(baseType, transformType)}<${inner}>${arrayTail}`; + return `${formatType(baseType, transformType)}<${inner}>${arrayTail}`; } // Plain base type. @@ -232,5 +228,20 @@ export const parseType = (typeString, transformType) => { return null; } - return `[\`<${core}>\`](${url})${arrayTail}`; + return `${formatType(core, transformType)}${arrayTail}`; +}; + +/** + * Recursively parses TypeScript types into Markdown, wrapped in a code span. + * + * @param {string} typeString The type string to evaluate. + * @param {(name: string) => string | null | undefined} transformType Resolves a bare type name to a URL, or returns falsy. + * @returns {string | null} Markdown for the type, or null when the base type doesn't resolve. + */ +export const parseType = (typeString, transformType) => { + const inner = parseInner(typeString, transformType); + if (inner === null) { + return null; + } + return `<${inner}>`; };