Skip to content

Commit 00d8c39

Browse files
committed
refactor(metadata): extract parseType logic into a separated module
1 parent c69122a commit 00d8c39

2 files changed

Lines changed: 123 additions & 125 deletions

File tree

src/generators/metadata/utils/transformers.mjs

Lines changed: 2 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { DOC_MAN_BASE_URL, DOC_API_HEADING_TYPES } from '../constants.mjs';
22
import { slug } from './slugger.mjs';
3+
import { parseType } from './typeParser.mjs';
34
import { transformNodesToString } from '../../../utils/unist.mjs';
45
import BUILTIN_TYPE_MAP from '../maps/builtin.json' with { type: 'json' };
56
import MDN_TYPE_MAP from '../maps/mdn.json' with { type: 'json' };
@@ -19,129 +20,6 @@ export const transformUnixManualToLink = (
1920
return `[\`${text}\`](${DOC_MAN_BASE_URL}${sectionNumber}/${command}.${sectionNumber}${sectionLetter}.html)`;
2021
};
2122

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-
const splitByOuterSeparator = (str, separator) => {
30-
const pieces = [];
31-
let current = '';
32-
let depth = 0;
33-
34-
for (let i = 0; i < str.length; i++) {
35-
const char = str[i];
36-
37-
// Track depth using brackets and parentheses
38-
if (char === '<' || char === '(') {
39-
depth++;
40-
} else if ((char === '>' && str[i - 1] !== '=') || char === ')') {
41-
depth--;
42-
}
43-
44-
// Check for multi-character separators like '=>'
45-
const isArrow = separator === '=>' && char === '=' && str[i + 1] === '>';
46-
// Check for single-character separators
47-
const isCharSeparator = separator === char;
48-
49-
if (depth === 0 && (isCharSeparator || isArrow)) {
50-
pieces.push(current.trim());
51-
current = '';
52-
if (isArrow) {
53-
i++;
54-
} // skip the '>' part of '=>'
55-
continue;
56-
}
57-
58-
current += char;
59-
}
60-
61-
pieces.push(current.trim());
62-
return pieces;
63-
};
64-
65-
/**
66-
* Recursively parses advanced TypeScript types, including Unions, Intersections, Functions, and Nested Generics.
67-
* * @param {string} typeString The plain type string to evaluate
68-
* @param {Function} transformType The function used to resolve individual types into links
69-
* @returns {string|null} The formatted Markdown link(s), or null if the base type doesn't map
70-
*/
71-
const parseAdvancedType = (typeString, transformType) => {
72-
const trimmed = typeString.trim();
73-
if (!trimmed) {
74-
return null;
75-
}
76-
77-
// Handle Unions (|)
78-
if (trimmed.includes('|')) {
79-
const parts = splitByOuterSeparator(trimmed, '|');
80-
if (parts.length > 1) {
81-
// Re-evaluate each part recursively and join with ' | '
82-
const resolvedParts = parts.map(
83-
p => parseAdvancedType(p, transformType) || `\`<${p}>\``
84-
);
85-
return resolvedParts.join(' | ');
86-
}
87-
}
88-
89-
// Handle Intersections (&)
90-
if (trimmed.includes('&')) {
91-
const parts = splitByOuterSeparator(trimmed, '&');
92-
if (parts.length > 1) {
93-
// Re-evaluate each part recursively and join with ' & '
94-
const resolvedParts = parts.map(
95-
p => parseAdvancedType(p, transformType) || `\`<${p}>\``
96-
);
97-
return resolvedParts.join(' & ');
98-
}
99-
}
100-
101-
// Handle Functions (=>)
102-
if (trimmed.includes('=>')) {
103-
const parts = splitByOuterSeparator(trimmed, '=>');
104-
if (parts.length === 2) {
105-
const params = parts[0];
106-
const returnType = parts[1];
107-
108-
// Preserve the function signature, just link the return type for now
109-
// (Mapping param types inside the signature string is complex and often unnecessary for simple docs)
110-
const parsedReturn =
111-
parseAdvancedType(returnType, transformType) || `\`<${returnType}>\``;
112-
return `${params} =&gt; ${parsedReturn}`;
113-
}
114-
}
115-
116-
// 3. Handle Generics (Base<Inner, Inner>)
117-
if (trimmed.includes('<') && trimmed.endsWith('>')) {
118-
const firstBracketIndex = trimmed.indexOf('<');
119-
const baseType = trimmed.slice(0, firstBracketIndex).trim();
120-
const innerType = trimmed.slice(firstBracketIndex + 1, -1).trim();
121-
122-
const baseResult = transformType(baseType.replace(/\[\]$/, ''));
123-
const baseFormatted = baseResult
124-
? `[\`<${baseType}>\`](${baseResult})`
125-
: `\`<${baseType}>\``;
126-
127-
// Split arguments safely by comma
128-
const innerArgs = splitByOuterSeparator(innerType, ',');
129-
const innerFormatted = innerArgs
130-
.map(arg => parseAdvancedType(arg, transformType) || `\`<${arg}>\``)
131-
.join(', ');
132-
133-
return `${baseFormatted}&lt;${innerFormatted}&gt;`;
134-
}
135-
136-
// Base Case: Plain Type (e.g., string, Buffer, Function)
137-
const result = transformType(trimmed.replace(/\[\]$/, ''));
138-
if (trimmed.length && result) {
139-
return `[\`<${trimmed}>\`](${result})`;
140-
}
141-
142-
return null;
143-
};
144-
14523
/**
14624
* This method replaces plain text Types within the Markdown content into Markdown links
14725
* that link to the actual relevant reference for such type (either internal or external link)
@@ -192,8 +70,7 @@ export const transformTypeToReferenceLink = (type, record) => {
19270
return '';
19371
};
19472

195-
// Kick off the recursive parser on the cleaned input
196-
const markdownLinks = parseAdvancedType(typeInput, transformType);
73+
const markdownLinks = parseType(typeInput, transformType);
19774

19875
// Return the replaced links or the original content if they all failed to be replaced
19976
// Note that if some failed to get replaced, only the valid ones will be returned
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Safely splits a string by a given set of separators at depth 0 (ignoring those inside < > or ( )).
3+
*
4+
* @param {string} str The string to split
5+
* @param {string} separator The separator to split by (e.g., '|', '&', ',', '=>')
6+
* @returns {string[]} The split pieces
7+
*/
8+
const splitByOuterSeparator = (str, separator) => {
9+
const pieces = [];
10+
let current = '';
11+
let depth = 0;
12+
13+
for (let i = 0; i < str.length; i++) {
14+
const char = str[i];
15+
16+
// Track depth using brackets and parentheses
17+
if (char === '<' || char === '(') {
18+
depth++;
19+
} else if ((char === '>' && str[i - 1] !== '=') || char === ')') {
20+
depth--;
21+
}
22+
23+
// Check for multi-character separators like '=>'
24+
const isArrow = separator === '=>' && char === '=' && str[i + 1] === '>';
25+
// Check for single-character separators
26+
const isCharSeparator = separator === char;
27+
28+
if (depth === 0 && (isCharSeparator || isArrow)) {
29+
pieces.push(current.trim());
30+
current = '';
31+
if (isArrow) {
32+
i++;
33+
} // skip the '>' part of '=>'
34+
continue;
35+
}
36+
37+
current += char;
38+
}
39+
40+
pieces.push(current.trim());
41+
return pieces;
42+
};
43+
/**
44+
* Recursively parses advanced TypeScript types, including Unions, Intersections, Functions, and Nested Generics.
45+
* * @param {string} typeString The plain type string to evaluate
46+
* @param {Function} transformType The function used to resolve individual types into links
47+
* @returns {string|null} The formatted Markdown link(s), or null if the base type doesn't map
48+
*/
49+
export const parseType = (typeString, transformType) => {
50+
const trimmed = typeString.trim();
51+
if (!trimmed) {
52+
return null;
53+
}
54+
55+
// Handle Unions (|)
56+
if (trimmed.includes('|')) {
57+
const parts = splitByOuterSeparator(trimmed, '|');
58+
if (parts.length > 1) {
59+
// Re-evaluate each part recursively and join with ' | '
60+
const resolvedParts = parts.map(
61+
p => parseType(p, transformType) || `\`<${p}>\``
62+
);
63+
return resolvedParts.join(' | ');
64+
}
65+
}
66+
67+
// Handle Intersections (&)
68+
if (trimmed.includes('&')) {
69+
const parts = splitByOuterSeparator(trimmed, '&');
70+
if (parts.length > 1) {
71+
// Re-evaluate each part recursively and join with ' & '
72+
const resolvedParts = parts.map(
73+
p => parseType(p, transformType) || `\`<${p}>\``
74+
);
75+
return resolvedParts.join(' & ');
76+
}
77+
}
78+
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];
85+
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} =&gt; ${parsedReturn}`;
91+
}
92+
}
93+
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();
99+
100+
const baseResult = transformType(baseType.replace(/\[\]$/, ''));
101+
const baseFormatted = baseResult
102+
? `[\`<${baseType}>\`](${baseResult})`
103+
: `\`<${baseType}>\``;
104+
105+
// Split arguments safely by comma
106+
const innerArgs = splitByOuterSeparator(innerType, ',');
107+
const innerFormatted = innerArgs
108+
.map(arg => parseType(arg, transformType) || `\`<${arg}>\``)
109+
.join(', ');
110+
111+
return `${baseFormatted}&lt;${innerFormatted}&gt;`;
112+
}
113+
114+
// Base Case: Plain Type (e.g., string, Buffer, Function)
115+
const result = transformType(trimmed.replace(/\[\]$/, ''));
116+
if (trimmed.length && result) {
117+
return `[\`<${trimmed}>\`](${result})`;
118+
}
119+
120+
return null;
121+
};

0 commit comments

Comments
 (0)