@@ -40,18 +40,73 @@ const splitByOuterSeparator = (str, separator) => {
4040 pieces . push ( current . trim ( ) ) ;
4141 return pieces ;
4242} ;
43+ /**
44+ * Safely removes outer parentheses from a type string if they wrap the entire string.
45+ * This prevents "depth blindness" in the parser by unwrapping types like `(string | number)`
46+ * into `string | number`, while safely ignoring disconnected groups like `(A) | (B)`.
47+ *
48+ * @param {string } typeString The type string to evaluate and potentially unwrap.
49+ * @returns {string } The unwrapped type string, or the original string if not fully wrapped.
50+ */
51+ export const stripOuterParentheses = typeString => {
52+ let trimmed = typeString . trim ( ) ;
53+
54+ if ( trimmed . startsWith ( '(' ) && trimmed . endsWith ( ')' ) ) {
55+ let depth = 0 ;
56+ let isValidWrapper = true ;
57+
58+ // Iterate through the string, ignoring the last closing parenthesis
59+ for ( let i = 0 ; i < trimmed . length - 1 ; i ++ ) {
60+ if ( trimmed [ i ] === '(' ) {
61+ depth ++ ;
62+ } else if ( trimmed [ i ] === ')' ) {
63+ depth -- ;
64+ }
65+
66+ // If depth hits 0 before the end, it means the parentheses don't wrap the whole string
67+ if ( depth === 0 ) {
68+ isValidWrapper = false ;
69+ break ;
70+ }
71+ }
72+
73+ if ( isValidWrapper ) {
74+ return trimmed . slice ( 1 , - 1 ) . trim ( ) ;
75+ }
76+ }
77+
78+ return trimmed ;
79+ } ;
4380/**
4481 * Recursively parses advanced TypeScript types, including Unions, Intersections, Functions, and Nested Generics.
4582 * * @param {string } typeString The plain type string to evaluate
4683 * @param {Function } transformType The function used to resolve individual types into links
4784 * @returns {string|null } The formatted Markdown link(s), or null if the base type doesn't map
4885 */
4986export const parseType = ( typeString , transformType ) => {
50- const trimmed = typeString . trim ( ) ;
87+ // Clean the string and strip unnecessary outer parentheses to prevent depth blindness (e.g., "(string | number)" -> "string | number")
88+ const trimmed = stripOuterParentheses ( typeString ) ;
5189 if ( ! trimmed ) {
5290 return null ;
5391 }
5492
93+ // Handle Functions (=>)
94+ if ( trimmed . includes ( '=>' ) ) {
95+ const parts = splitByOuterSeparator ( trimmed , '=>' ) ;
96+ if ( parts . length > 1 ) {
97+ const params = parts [ 0 ] ;
98+
99+ // Join the rest back together to handle higher-order functions
100+ const returnType = parts . slice ( 1 ) . join ( ' => ' ) ;
101+
102+ // Preserve the function signature, just link the return type for now
103+ // (Mapping param types inside the signature string is complex and often unnecessary for simple docs)
104+ const parsedReturn =
105+ parseType ( returnType , transformType ) || `\`<${ returnType } >\`` ;
106+ return `${ params } => ${ parsedReturn } ` ;
107+ }
108+ }
109+
55110 // Handle Unions (|)
56111 if ( trimmed . includes ( '|' ) ) {
57112 const parts = splitByOuterSeparator ( trimmed , '|' ) ;
@@ -76,45 +131,39 @@ export const parseType = (typeString, transformType) => {
76131 }
77132 }
78133
79- // Handle Functions (=>)
80- if ( trimmed . includes ( '=>' ) ) {
81- const parts = splitByOuterSeparator ( trimmed , '=>' ) ;
82- if ( parts . length === 2 ) {
83- const params = parts [ 0 ] ;
84- const returnType = parts [ 1 ] ;
134+ // Handle Generics (Base<Inner, Inner>)
135+ // Check if it's a generic wrapped in an array (e.g., Promise<string>[])
136+ const isGenericArray = trimmed . endsWith ( '[]' ) ;
137+ const genericTarget = isGenericArray ? trimmed . slice ( 0 , - 2 ) . trim ( ) : trimmed ;
85138
86- // Preserve the function signature, just link the return type for now
87- // (Mapping param types inside the signature string is complex and often unnecessary for simple docs)
88- const parsedReturn =
89- parseType ( returnType , transformType ) || `\`<${ returnType } >\`` ;
90- return `${ params } => ${ parsedReturn } ` ;
91- }
92- }
139+ if ( genericTarget . includes ( '<' ) && genericTarget . endsWith ( '>' ) ) {
140+ const firstBracketIndex = genericTarget . indexOf ( '<' ) ;
141+ const baseType = genericTarget . slice ( 0 , firstBracketIndex ) . trim ( ) ;
142+ const innerType = genericTarget . slice ( firstBracketIndex + 1 , - 1 ) . trim ( ) ;
93143
94- // 3. Handle Generics (Base<Inner, Inner>)
95- if ( trimmed . includes ( '<' ) && trimmed . endsWith ( '>' ) ) {
96- const firstBracketIndex = trimmed . indexOf ( '<' ) ;
97- const baseType = trimmed . slice ( 0 , firstBracketIndex ) . trim ( ) ;
98- const innerType = trimmed . slice ( firstBracketIndex + 1 , - 1 ) . trim ( ) ;
144+ const cleanBaseType = baseType . replace ( / \[ \] $ / , '' ) ; // Just in case of Base[]<Inner>
145+ const baseResult = transformType ( cleanBaseType ) ;
99146
100- const baseResult = transformType ( baseType . replace ( / \[ \] $ / , '' ) ) ;
101147 const baseFormatted = baseResult
102- ? `[\`<${ baseType } >\`](${ baseResult } )`
103- : `\`<${ baseType } >\`` ;
148+ ? `[\`<${ cleanBaseType } >\`](${ baseResult } )`
149+ : `\`<${ cleanBaseType } >\`` ;
104150
105- // Split arguments safely by comma
106151 const innerArgs = splitByOuterSeparator ( innerType , ',' ) ;
107152 const innerFormatted = innerArgs
108153 . map ( arg => parseType ( arg , transformType ) || `\`<${ arg } >\`` )
109154 . join ( ', ' ) ;
110155
111- return `${ baseFormatted } <${ innerFormatted } >` ;
156+ return `${ baseFormatted } <${ innerFormatted } >${ isGenericArray ? '[]' : '' } ` ;
112157 }
113158
114159 // Base Case: Plain Type (e.g., string, Buffer, Function)
115- const result = transformType ( trimmed . replace ( / \[ \] $ / , '' ) ) ;
116- if ( trimmed . length && result ) {
117- return `[\`<${ trimmed } >\`](${ result } )` ;
160+ // Preserve array notation for base types
161+ const isArray = trimmed . endsWith ( '[]' ) ;
162+ const cleanType = trimmed . replace ( / \[ \] $ / , '' ) ;
163+
164+ const result = transformType ( cleanType ) ;
165+ if ( cleanType . length && result ) {
166+ return `[\`<${ cleanType } >\`](${ result } )${ isArray ? '[]' : '' } ` ;
118167 }
119168
120169 return null ;
0 commit comments