1- import {
2- DOC_MAN_BASE_URL ,
3- DOC_API_HEADING_TYPES ,
4- TYPE_GENERIC_REGEX ,
5- } from '../constants.mjs' ;
1+ import { DOC_MAN_BASE_URL , DOC_API_HEADING_TYPES } from '../constants.mjs' ;
62import { slug } from './slugger.mjs' ;
73import { transformNodesToString } from '../../../utils/unist.mjs' ;
84import BUILTIN_TYPE_MAP from '../maps/builtin.json' with { type : 'json' } ;
@@ -22,84 +18,134 @@ export const transformUnixManualToLink = (
2218) => {
2319 return `[\`${ text } \`](${ DOC_MAN_BASE_URL } ${ sectionNumber } /${ command } .${ sectionNumber } ${ sectionLetter } .html)` ;
2420} ;
21+
22+ /**
23+ * Safely splits a string by a given set of separators at depth 0 (ignoring those inside < > or ( )).
24+ *
25+ * @param {string } str The string to split
26+ * @param {string } separator The separator to split by (e.g., '|', '&', ',', '=>')
27+ * @returns {string[] } The split pieces
28+ */
29+
2530/**
26- * Safely splits the string by `|` or `&` at the top level (ignoring those
27- * inside `< >`), and returns both the pieces and the separator used.
2831 *
29- * @param {string } str The type string to split
30- * @returns {{ pieces: string[], separator: string } } The split pieces and the separator string used to join them (` | ` or ` & `)
3132 */
32- const splitByOuterSeparator = str => {
33+ const splitByOuterSeparator = ( str , separator ) => {
3334 const pieces = [ ] ;
3435 let current = '' ;
3536 let depth = 0 ;
36- let separator ;
3737
38- for ( const char of str ) {
39- if ( char === '<' ) {
38+ for ( let i = 0 ; i < str . length ; i ++ ) {
39+ const char = str [ i ] ;
40+
41+ // Track depth using brackets and parentheses
42+ if ( char === '<' || char === '(' ) {
4043 depth ++ ;
41- } else if ( char === '>' ) {
44+ } else if ( char === '>' || char === ')' ) {
4245 depth -- ;
43- } else if ( ( char === '|' || char === '&' ) && depth === 0 ) {
44- pieces . push ( current ) ;
46+ }
47+
48+ // Check for multi-character separators like '=>'
49+ const isArrow = separator === '=>' && char === '=' && str [ i + 1 ] === '>' ;
50+ // Check for single-character separators
51+ const isCharSeparator = separator === char ;
52+
53+ if ( depth === 0 && ( isCharSeparator || isArrow ) ) {
54+ pieces . push ( current . trim ( ) ) ;
4555 current = '' ;
46- separator ??= ` ${ char } ` ;
56+ if ( isArrow ) {
57+ i ++ ;
58+ } // skip the '>' part of '=>'
4759 continue ;
4860 }
61+
4962 current += char ;
5063 }
5164
52- pieces . push ( current ) ;
53- return { pieces, separator } ;
65+ pieces . push ( current . trim ( ) ) ;
66+ return pieces ;
5467} ;
5568
5669/**
57- * Attempts to parse and format a basic Generic type (e.g., Promise<string>).
58- * It also supports union and multi-parameter types within the generic brackets.
59- *
60- * @param {string } typePiece The plain type piece to be evaluated
70+ * Recursively parses advanced TypeScript types, including Unions, Intersections, Functions, and Nested Generics.
71+ * * @param {string } typeString The plain type string to evaluate
6172 * @param {Function } transformType The function used to resolve individual types into links
62- * @returns {string|null } The formatted Markdown link, or null if no match is found
73+ * @returns {string|null } The formatted Markdown link(s) , or null if the base type doesn't map
6374 */
64- const formatBasicGeneric = ( typePiece , transformType ) => {
65- const genericMatch = typePiece . match ( TYPE_GENERIC_REGEX ) ;
75+ const parseAdvancedType = ( typeString , transformType ) => {
76+ const trimmed = typeString . trim ( ) ;
77+ if ( ! trimmed ) {
78+ return null ;
79+ }
80+
81+ // Handle Unions (|)
82+ if ( trimmed . includes ( '|' ) ) {
83+ const parts = splitByOuterSeparator ( trimmed , '|' ) ;
84+ if ( parts . length > 1 ) {
85+ // Re-evaluate each part recursively and join with ' | '
86+ const resolvedParts = parts . map (
87+ p => parseAdvancedType ( p , transformType ) || `\`<${ p } >\``
88+ ) ;
89+ return resolvedParts . join ( ' | ' ) ;
90+ }
91+ }
92+
93+ // Handle Intersections (&)
94+ if ( trimmed . includes ( '&' ) ) {
95+ const parts = splitByOuterSeparator ( trimmed , '&' ) ;
96+ if ( parts . length > 1 ) {
97+ // Re-evaluate each part recursively and join with ' & '
98+ const resolvedParts = parts . map (
99+ p => parseAdvancedType ( p , transformType ) || `\`<${ p } >\``
100+ ) ;
101+ return resolvedParts . join ( ' & ' ) ;
102+ }
103+ }
66104
67- if ( genericMatch ) {
68- const baseType = genericMatch [ 1 ] . trim ( ) ;
69- const innerType = genericMatch [ 2 ] . trim ( ) ;
105+ // Handle Functions (=>)
106+ if ( trimmed . includes ( '=>' ) ) {
107+ const parts = splitByOuterSeparator ( trimmed , '=>' ) ;
108+ if ( parts . length === 2 ) {
109+ const params = parts [ 0 ] ;
110+ const returnType = parts [ 1 ] ;
111+
112+ // Preserve the function signature, just link the return type for now
113+ // (Mapping param types inside the signature string is complex and often unnecessary for simple docs)
114+ const parsedReturn =
115+ parseAdvancedType ( returnType , transformType ) || `\`<${ returnType } >\`` ;
116+ return `${ params } => ${ parsedReturn } ` ;
117+ }
118+ }
119+
120+ // 3. Handle Generics (Base<Inner, Inner>)
121+ if ( trimmed . includes ( '<' ) && trimmed . endsWith ( '>' ) ) {
122+ const firstBracketIndex = trimmed . indexOf ( '<' ) ;
123+ const baseType = trimmed . slice ( 0 , firstBracketIndex ) . trim ( ) ;
124+ const innerType = trimmed . slice ( firstBracketIndex + 1 , - 1 ) . trim ( ) ;
70125
71126 const baseResult = transformType ( baseType . replace ( / \[ \] $ / , '' ) ) ;
72127 const baseFormatted = baseResult
73128 ? `[\`<${ baseType } >\`](${ baseResult } )`
74129 : `\`<${ baseType } >\`` ;
75130
76- // Split while capturing delimiters (| or ,) to preserve original syntax
77- const parts = innerType . split ( / ( [ | , ] ) / ) ;
78-
79- const innerFormatted = parts
80- . map ( part => {
81- const trimmed = part . trim ( ) ;
82- // If it is a delimiter, return it as is
83- if ( trimmed === '|' ) {
84- return ' | ' ;
85- }
86-
87- if ( trimmed === ',' ) {
88- return ', ' ;
89- }
90-
91- const innerRes = transformType ( trimmed . replace ( / \[ \] $ / , '' ) ) ;
92- return innerRes
93- ? `[\`<${ trimmed } >\`](${ innerRes } )`
94- : `\`<${ trimmed } >\`` ;
95- } )
96- . join ( '' ) ;
131+ // Split arguments safely by comma
132+ const innerArgs = splitByOuterSeparator ( innerType , ',' ) ;
133+ const innerFormatted = innerArgs
134+ . map ( arg => parseAdvancedType ( arg , transformType ) || `\`<${ arg } >\`` )
135+ . join ( ', ' ) ;
97136
98137 return `${ baseFormatted } <${ innerFormatted } >` ;
99138 }
100139
140+ // Base Case: Plain Type (e.g., string, Buffer, Function)
141+ const result = transformType ( trimmed . replace ( / \[ \] $ / , '' ) ) ;
142+ if ( trimmed . length && result ) {
143+ return `[\`<${ trimmed } >\`](${ result } )` ;
144+ }
145+
101146 return null ;
102147} ;
148+
103149/**
104150 * This method replaces plain text Types within the Markdown content into Markdown links
105151 * that link to the actual relevant reference for such type (either internal or external link)
@@ -150,32 +196,8 @@ export const transformTypeToReferenceLink = (type, record) => {
150196 return '' ;
151197 } ;
152198
153- const { pieces : outerPieces , separator } = splitByOuterSeparator ( typeInput ) ;
154-
155- const typePieces = outerPieces . map ( piece => {
156- // This is the content to render as the text of the Markdown link
157- const trimmedPiece = piece . trim ( ) ;
158-
159- // 1. Attempt to format as a basic Generic type first
160- const genericMarkdown = formatBasicGeneric ( trimmedPiece , transformType ) ;
161- if ( genericMarkdown ) {
162- return genericMarkdown ;
163- }
164-
165- // 2. Fallback to the logic for plain types
166- // This is what we will compare against the API types mappings
167- // The ReGeX below is used to remove `[]` from the end of the type
168- const result = transformType ( trimmedPiece . replace ( / \[ \] $ / , '' ) ) ;
169-
170- // If we have a valid result and the piece is not empty, we return the Markdown link
171- if ( trimmedPiece . length && result . length ) {
172- return `[\`<${ trimmedPiece } >\`](${ result } )` ;
173- }
174- } ) ;
175-
176- // Filter out pieces that we failed to map and then join the valid ones
177- // using the same separator that appeared in the original type string
178- const markdownLinks = typePieces . filter ( Boolean ) . join ( separator ) ;
199+ // Kick off the recursive parser on the cleaned input
200+ const markdownLinks = parseAdvancedType ( typeInput , transformType ) ;
179201
180202 // Return the replaced links or the original content if they all failed to be replaced
181203 // Note that if some failed to get replaced, only the valid ones will be returned
0 commit comments