-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy pathparseList.mjs
More file actions
147 lines (125 loc) · 4.25 KB
/
parseList.mjs
File metadata and controls
147 lines (125 loc) · 4.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
import {
DEFAULT_EXPRESSION,
LEADING_HYPHEN,
NAME_EXPRESSION,
RETURN_EXPRESSION,
TYPE_EXPRESSION,
} from '../constants.mjs';
import parseSignature from './parseSignature.mjs';
import { transformNodesToString } from '../../../utils/unist.mjs';
/**
* Modifies type references in a string by replacing template syntax (`<...>`) with curly braces `{...}`
* and normalizing formatting.
* @param {string} string
* @returns {string}
*/
export function transformTypeReferences(string) {
return string.replace(/`<([^>]+)>`/g, '{$1}').replaceAll('} | {', '|');
}
/**
* Extracts and removes a specific pattern from a text string while storing the result in a key of the `current` object.
* @param {string} text
* @param {RegExp} pattern
* @param {string} key
* @param {Object} current
* @returns {string}
*/
export const extractPattern = (text, pattern, key, current) => {
const match = text.match(pattern)?.[1]?.trim().replace(/\.$/, '');
if (!match) {
return text;
}
current[key] = match;
return text.replace(pattern, '');
};
/**
* Determines if the input List node is a typed list
* @param {import('@types/mdast').List} list
*/
export const isTypedList = list => {
if (list.type !== 'list') {
// Exit early if not a list
return false;
}
const children = list?.children?.[0]?.children?.[0]?.children;
return (
// The first element must be a code block
children?.[0]?.type === 'inlineCode' &&
// Followed by a space
children?.[1]?.value.trim() === '' &&
// Followed by a link (type)
children?.[2]?.type === 'link' &&
// Types start with `<`
children?.[2]?.children?.[0]?.value?.[0] === '<'
);
};
/**
* Parses an individual list item node to extract its properties
*
* @param {import('@types/mdast').ListItem} child
* @returns {import('../types').ParameterList}
*/
export function parseListItem(child) {
const current = {};
const subList = child.children.find(isTypedList);
// Extract and clean raw text from the node, excluding nested lists
current.textRaw = transformTypeReferences(
transformNodesToString(child.children.filter(node => node !== subList))
.replace(/\s+/g, ' ')
.replace(/<!--.*?-->/gs, '')
);
let text = current.textRaw;
// Identify return items or extract key properties (name, type, default) from the text
if (RETURN_EXPRESSION.test(text)) {
current.name = 'return';
text = text.replace(RETURN_EXPRESSION, '');
} else {
text = extractPattern(text, NAME_EXPRESSION, 'name', current);
}
text = extractPattern(text, TYPE_EXPRESSION, 'type', current);
text = extractPattern(text, DEFAULT_EXPRESSION, 'default', current);
// Set the remaining text as the description, removing any leading hyphen
current.desc = text.replace(LEADING_HYPHEN, '').trim() || undefined;
// Parse nested lists (options) recursively if present
if (subList) {
current.options = subList.children.map(parseListItem);
}
return current;
}
/**
* Parses a list of nodes and updates the corresponding section object with the extracted information.
* Handles different section types such as methods, properties, and events differently.
* @param {import('../types').Section} section
* @param {import('@types/mdast').RootContent[]} nodes
*/
export function parseList(section, nodes) {
const list = nodes[0]?.type === 'list' ? nodes.shift() : null;
const values = list ? list.children.map(parseListItem) : [];
// Update the section based on its type and parsed values
switch (section.type) {
case 'ctor':
case 'classMethod':
case 'method':
// For methods and constructors, parse and attach signatures
section.signatures = [parseSignature(section.textRaw, values)];
break;
case 'property':
// For properties, update type and other details if values exist
if (values.length) {
const { type, ...rest } = values[0];
section.type = type;
Object.assign(section, rest);
section.textRaw = `\`${section.name}\` ${section.textRaw}`;
}
break;
case 'event':
// For events, assign parsed values as parameters
section.params = values;
break;
default:
// If no specific handling, re-add the list for further processing
if (list) {
nodes.unshift(list);
}
}
}