1
+ /**
2
+ * @typedef Options
3
+ * @property {Test } [ignore]
4
+ *
5
+ * @typedef {import('hast').Text } Text
6
+ * @typedef {import('hast').Parent } Parent
7
+ * @typedef {import('hast').Root } Root
8
+ * @typedef {import('hast').Element['children'][number] } Content
9
+ * @typedef {Parent['children'][number]|Root } Node
10
+ *
11
+ * @typedef {import('hast-util-is-element').Test } Test
12
+ * @typedef {import('unist-util-visit-parents').VisitorResult } VisitorResult
13
+ *
14
+ * @typedef RegExpMatchObject
15
+ * @property {number } index
16
+ * @property {string } input
17
+ *
18
+ * @typedef {string|RegExp } Find
19
+ * @typedef {string|ReplaceFunction } Replace
20
+ *
21
+ * @typedef {[Find, Replace] } FindAndReplaceTuple
22
+ * @typedef {Object.<string, Replace> } FindAndReplaceSchema
23
+ * @typedef {Array.<FindAndReplaceTuple> } FindAndReplaceList
24
+ *
25
+ * @typedef {[RegExp, ReplaceFunction] } Pair
26
+ * @typedef {Array.<Pair> } Pairs
27
+ */
28
+
29
+ /**
30
+ * @callback Handler
31
+ * @param {Text } node
32
+ * @param {Parent } parent
33
+ * @returns {VisitorResult }
34
+ */
35
+
36
+ /**
37
+ * @callback ReplaceFunction
38
+ * @param {...unknown } parameters
39
+ * @returns {Array.<Content>|Content|string|false|undefined|null }
40
+ */
41
+
1
42
import { visitParents } from 'unist-util-visit-parents'
2
43
import { convertElement } from 'hast-util-is-element'
3
44
import escape from 'escape-string-regexp'
4
45
5
- var splice = [ ] . splice
6
46
var own = { } . hasOwnProperty
7
47
8
48
export const defaultIgnore = [ 'title' , 'script' , 'style' , 'svg' , 'math' ]
9
49
50
+ /**
51
+ * @param {Node } tree
52
+ * @param {Find|FindAndReplaceSchema|FindAndReplaceList } find
53
+ * @param {Replace|Options } [replace]
54
+ * @param {Options } [options]
55
+ */
10
56
export function findAndReplace ( tree , find , replace , options ) {
57
+ /** @type {Options } */
11
58
var settings
59
+ /** @type {FindAndReplaceSchema|FindAndReplaceList } */
12
60
var schema
13
61
14
- if ( typeof find === 'string' || ( find && typeof find . exec === 'function' ) ) {
62
+ if ( typeof find === 'string' || find instanceof RegExp ) {
63
+ // @ts -expect-error don’t expect options twice.
15
64
schema = [ [ find , replace ] ]
65
+ settings = options
16
66
} else {
17
67
schema = find
18
- options = replace
68
+ // @ts -expect-error don’t expect replace twice.
69
+ settings = replace
19
70
}
20
71
21
- settings = options || { }
72
+ if ( ! settings ) {
73
+ settings = { }
74
+ }
22
75
23
76
search ( tree , settings , handlerFactory ( toPairs ( schema ) ) )
24
77
25
78
return tree
26
79
80
+ /**
81
+ * @param {Pairs } pairs
82
+ * @returns {Handler }
83
+ */
27
84
function handlerFactory ( pairs ) {
28
85
var pair = pairs [ 0 ]
29
86
30
87
return handler
31
88
89
+ /**
90
+ * @type {Handler }
91
+ */
32
92
function handler ( node , parent ) {
33
93
var find = pair [ 0 ]
34
94
var replace = pair [ 1 ]
95
+ /** @type {Array.<Content> } */
35
96
var nodes = [ ]
36
97
var start = 0
37
98
var index = parent . children . indexOf ( node )
99
+ /** @type {number } */
38
100
var position
101
+ /** @type {RegExpMatchArray } */
39
102
var match
103
+ /** @type {Handler } */
40
104
var subhandler
105
+ /** @type {Content } */
106
+ var child
107
+ /** @type {Array.<Content>|Content|string|false|undefined|null } */
41
108
var value
42
109
43
110
find . lastIndex = 0
@@ -46,19 +113,20 @@ export function findAndReplace(tree, find, replace, options) {
46
113
47
114
while ( match ) {
48
115
position = match . index
116
+ // @ts -expect-error this is perfectly fine, typescript.
49
117
value = replace ( ...match , { index : match . index , input : match . input } )
50
118
119
+ if ( typeof value === 'string' && value . length > 0 ) {
120
+ value = { type : 'text' , value}
121
+ }
122
+
51
123
if ( value !== false ) {
52
124
if ( start !== position ) {
53
125
nodes . push ( { type : 'text' , value : node . value . slice ( start , position ) } )
54
126
}
55
127
56
- if ( typeof value === 'string' && value . length > 0 ) {
57
- value = { type : 'text' , value}
58
- }
59
-
60
128
if ( value ) {
61
- nodes . push ( value )
129
+ nodes = [ ] . concat ( nodes , value )
62
130
}
63
131
64
132
start = position + match [ 0 ] . length
@@ -79,21 +147,22 @@ export function findAndReplace(tree, find, replace, options) {
79
147
nodes . push ( { type : 'text' , value : node . value . slice ( start ) } )
80
148
}
81
149
82
- nodes . unshift ( index , 1 )
83
- splice . apply ( parent . children , nodes )
150
+ // @ts -expect-error This is a bug!
151
+ nodes = [ index , 1 , ...nodes ]
152
+ ; [ ] . splice . call ( parent . children , ...nodes )
84
153
}
85
154
86
155
if ( pairs . length > 1 ) {
87
156
subhandler = handlerFactory ( pairs . slice ( 1 ) )
88
157
position = - 1
89
158
90
159
while ( ++ position < nodes . length ) {
91
- node = nodes [ position ]
160
+ child = nodes [ position ]
92
161
93
- if ( node . type === 'text' ) {
94
- subhandler ( node , parent )
162
+ if ( child . type === 'text' ) {
163
+ subhandler ( child , parent )
95
164
} else {
96
- search ( node , settings , subhandler )
165
+ search ( child , settings , subhandler )
97
166
}
98
167
}
99
168
}
@@ -103,25 +172,33 @@ export function findAndReplace(tree, find, replace, options) {
103
172
}
104
173
}
105
174
175
+ /**
176
+ * @param {Node } tree
177
+ * @param {Options } options
178
+ * @param {Handler } handler
179
+ * @returns {void }
180
+ */
106
181
function search ( tree , options , handler ) {
107
182
var ignored = convertElement ( options . ignore || defaultIgnore )
108
- var result = [ ]
109
183
110
184
visitParents ( tree , 'text' , visitor )
111
185
112
- return result
113
-
186
+ /** @type {import('unist-util-visit-parents').Visitor<Text> } */
114
187
function visitor ( node , parents ) {
115
188
var index = - 1
189
+ /** @type {Parent } */
116
190
var parent
191
+ /** @type {Parent } */
117
192
var grandparent
118
193
119
194
while ( ++ index < parents . length ) {
195
+ // @ts -expect-error hast vs. unist parent.
120
196
parent = parents [ index ]
121
197
122
198
if (
123
199
ignored (
124
200
parent ,
201
+ // @ts -expect-error hast vs. unist parent.
125
202
grandparent ? grandparent . children . indexOf ( parent ) : undefined ,
126
203
grandparent
127
204
)
@@ -136,18 +213,22 @@ function search(tree, options, handler) {
136
213
}
137
214
}
138
215
216
+ /**
217
+ * @param {FindAndReplaceSchema|FindAndReplaceList } schema
218
+ * @returns {Pairs }
219
+ */
139
220
function toPairs ( schema ) {
221
+ var index = - 1
222
+ /** @type {Pairs } */
140
223
var result = [ ]
224
+ /** @type {string } */
141
225
var key
142
- var index
143
226
144
227
if ( typeof schema !== 'object' ) {
145
228
throw new TypeError ( 'Expected array or object as schema' )
146
229
}
147
230
148
- if ( 'length' in schema ) {
149
- index = - 1
150
-
231
+ if ( Array . isArray ( schema ) ) {
151
232
while ( ++ index < schema . length ) {
152
233
result . push ( [
153
234
toExpression ( schema [ index ] [ 0 ] ) ,
@@ -165,14 +246,24 @@ function toPairs(schema) {
165
246
return result
166
247
}
167
248
249
+ /**
250
+ * @param {Find } find
251
+ * @returns {RegExp }
252
+ */
168
253
function toExpression ( find ) {
169
254
return typeof find === 'string' ? new RegExp ( escape ( find ) , 'g' ) : find
170
255
}
171
256
257
+ /**
258
+ * @param {Replace } replace
259
+ * @returns {ReplaceFunction }
260
+ */
172
261
function toFunction ( replace ) {
173
262
return typeof replace === 'function' ? replace : returner
174
263
264
+ /** @type {ReplaceFunction } */
175
265
function returner ( ) {
266
+ // @ts -expect-error it’s a string.
176
267
return replace
177
268
}
178
269
}
0 commit comments