18
18
* @property {[...Array<Parents>, Text] } stack
19
19
* All ancestors of the text node, where the last node is the text itself.
20
20
*
21
+ * @typedef {RegExp | string } Find
22
+ * Pattern to find.
23
+ *
24
+ * Strings are escaped and then turned into global expressions.
25
+ *
26
+ * @typedef {Array<FindAndReplaceTuple> } FindAndReplaceList
27
+ * Several find and replaces, in array form.
28
+ *
29
+ * @typedef {Record<string, Replace> } FindAndReplaceSchema
30
+ * Several find and replaces, in object form.
31
+ *
32
+ * @typedef {[Find, Replace] } FindAndReplaceTuple
33
+ * Find and replace in tuple form.
34
+ *
35
+ * @typedef {ReplaceFunction | string } Replace
36
+ * Thing to replace with.
37
+ *
21
38
* @callback ReplaceFunction
22
39
* Callback called when a search matches.
23
40
* @param {...any } parameters
26
43
* * `value` (`string`) — whole match
27
44
* * `...capture` (`Array<string>`) — matches from regex capture groups
28
45
* * `match` (`RegExpMatchObject`) — info on the match
29
- * @returns {Array<PhrasingContent> | PhrasingContent | string | false | undefined | null }
46
+ * @returns {Array<PhrasingContent> | PhrasingContent | string | false | null | undefined }
30
47
* Thing to replace with.
31
48
*
32
49
* * when `null`, `undefined`, `''`, remove the match
33
50
* * …or when `false`, do not replace at all
34
51
* * …or when `string`, replace with a text node of that value
35
52
* * …or when `Node` or `Array<Node>`, replace with those nodes
36
53
*
37
- * @typedef {string | RegExp } Find
38
- * Pattern to find.
39
- *
40
- * Strings are escaped and then turned into global expressions.
41
- *
42
- * @typedef {Array<FindAndReplaceTuple> } FindAndReplaceList
43
- * Several find and replaces, in array form.
44
- * @typedef {Record<string, Replace> } FindAndReplaceSchema
45
- * Several find and replaces, in object form.
46
- * @typedef {[Find, Replace] } FindAndReplaceTuple
47
- * Find and replace in tuple form.
48
- * @typedef {string | ReplaceFunction } Replace
49
- * Thing to replace with.
50
54
* @typedef {[RegExp, ReplaceFunction] } Pair
51
55
* Normalized find and replace.
56
+ *
52
57
* @typedef {Array<Pair> } Pairs
53
58
* All find and replaced.
54
59
*
55
60
* @typedef Options
56
61
* Configuration.
57
62
* @property {Test | null | undefined } [ignore]
58
- * Test for which nodes to ignore.
63
+ * Test for which nodes to ignore (optional) .
59
64
*/
60
65
61
66
import escape from 'escape-string-regexp'
@@ -71,176 +76,175 @@ const own = {}.hasOwnProperty
71
76
* nodes.
72
77
* Partial matches are not supported.
73
78
*
74
- * @param tree
79
+ * @overload
80
+ * @param {Nodes } tree
81
+ * @param {Find } find
82
+ * @param {Replace | null | undefined } [replace]
83
+ * @param {Options | null | undefined } [options]
84
+ * @returns {undefined }
85
+ *
86
+ * @overload
87
+ * @param {Nodes } tree
88
+ * @param {FindAndReplaceSchema | FindAndReplaceList } schema
89
+ * @param {Options | null | undefined } [options]
90
+ * @returns {undefined }
91
+ *
92
+ * @param {Nodes } tree
75
93
* Tree to change.
76
- * @param find
94
+ * @param { Find | FindAndReplaceList | FindAndReplaceSchema } find
77
95
* Patterns to find.
78
- * @param replace
96
+ * @param { Options | Replace | null | undefined } [ replace]
79
97
* Things to replace with (when `find` is `Find`) or configuration.
80
- * @param options
98
+ * @param { Options | null | undefined } [ options]
81
99
* Configuration (when `find` is not `Find`).
82
- * @returns
100
+ * @returns { undefined }
83
101
* Given, modified, tree.
84
102
*/
85
103
// To do: next major: remove `find` & `replace` combo, remove schema.
86
- export const findAndReplace =
87
- /**
88
- * @type {(
89
- * ((tree: Nodes, find: Find, replace?: Replace | null | undefined, options?: Options | null | undefined) => undefined) &
90
- * ((tree: Nodes, schema: FindAndReplaceSchema | FindAndReplaceList, options?: Options | null | undefined) => undefined)
91
- * )}
92
- **/
93
- (
94
- /**
95
- * @param {Nodes } tree
96
- * @param {Find | FindAndReplaceSchema | FindAndReplaceList } find
97
- * @param {Replace | Options | null | undefined } [replace]
98
- * @param {Options | null | undefined } [options]
99
- * @returns {undefined }
100
- */
101
- function ( tree , find , replace , options ) {
102
- /** @type {Options | null | undefined } */
103
- let settings
104
- /** @type {FindAndReplaceSchema|FindAndReplaceList } */
105
- let schema
106
-
107
- if ( typeof find === 'string' || find instanceof RegExp ) {
108
- // @ts -expect-error don’t expect options twice.
109
- schema = [ [ find , replace ] ]
110
- settings = options
111
- } else {
112
- schema = find
113
- // @ts -expect-error don’t expect replace twice.
114
- settings = replace
115
- }
104
+ export function findAndReplace ( tree , find , replace , options ) {
105
+ /** @type {Options | null | undefined } */
106
+ let settings
107
+ /** @type {FindAndReplaceList | FindAndReplaceSchema } */
108
+ let schema
109
+
110
+ if ( typeof find === 'string' || find instanceof RegExp ) {
111
+ // @ts -expect-error don’t expect options twice.
112
+ schema = [ [ find , replace ] ]
113
+ settings = options
114
+ } else {
115
+ schema = find
116
+ // @ts -expect-error don’t expect replace twice.
117
+ settings = replace
118
+ }
116
119
117
- if ( ! settings ) {
118
- settings = { }
120
+ if ( ! settings ) {
121
+ settings = { }
122
+ }
123
+
124
+ const ignored = convert ( settings . ignore || [ ] )
125
+ const pairs = toPairs ( schema )
126
+ let pairIndex = - 1
127
+
128
+ while ( ++ pairIndex < pairs . length ) {
129
+ visitParents ( tree , 'text' , visitor )
130
+ }
131
+
132
+ // @ts -expect-error: To do: remove.
133
+ return tree
134
+
135
+ /** @type {import('unist-util-visit-parents').BuildVisitor<Root, 'text'> } */
136
+ function visitor ( node , parents ) {
137
+ let index = - 1
138
+ /** @type {Parents | undefined } */
139
+ let grandparent
140
+
141
+ while ( ++ index < parents . length ) {
142
+ const parent = parents [ index ]
143
+ /** @type {Array<Nodes> | undefined } */
144
+ const siblings = grandparent ? grandparent . children : undefined
145
+
146
+ if (
147
+ ignored (
148
+ parent ,
149
+ siblings ? siblings . indexOf ( parent ) : undefined ,
150
+ grandparent
151
+ )
152
+ ) {
153
+ return
119
154
}
120
155
121
- const ignored = convert ( settings . ignore || [ ] )
122
- const pairs = toPairs ( schema )
123
- let pairIndex = - 1
156
+ grandparent = parent
157
+ }
158
+
159
+ if ( grandparent ) {
160
+ return handler ( node , parents )
161
+ }
162
+ }
163
+
164
+ /**
165
+ * Handle a text node which is not in an ignored parent.
166
+ *
167
+ * @param {Text } node
168
+ * Text node.
169
+ * @param {Array<Parents> } parents
170
+ * Parents.
171
+ * @returns {VisitorResult }
172
+ * Result.
173
+ */
174
+ function handler ( node , parents ) {
175
+ const parent = parents [ parents . length - 1 ]
176
+ const find = pairs [ pairIndex ] [ 0 ]
177
+ const replace = pairs [ pairIndex ] [ 1 ]
178
+ let start = 0
179
+ /** @type {Array<Nodes> } */
180
+ const siblings = parent . children
181
+ const index = siblings . indexOf ( node )
182
+ let change = false
183
+ /** @type {Array<PhrasingContent> } */
184
+ let nodes = [ ]
185
+
186
+ find . lastIndex = 0
187
+
188
+ let match = find . exec ( node . value )
189
+
190
+ while ( match ) {
191
+ const position = match . index
192
+ /** @type {RegExpMatchObject } */
193
+ const matchObject = {
194
+ index : match . index ,
195
+ input : match . input ,
196
+ stack : [ ...parents , node ]
197
+ }
198
+ let value = replace ( ...match , matchObject )
124
199
125
- while ( ++ pairIndex < pairs . length ) {
126
- visitParents ( tree , 'text' , visitor )
200
+ if ( typeof value === 'string' ) {
201
+ value = value . length > 0 ? { type : 'text' , value } : undefined
127
202
}
128
203
129
- // @ts -expect-error: To do: remove.
130
- return tree
131
-
132
- /** @type {import('unist-util-visit-parents').BuildVisitor<Root, 'text'> } */
133
- function visitor ( node , parents ) {
134
- let index = - 1
135
- /** @type {Parents | undefined } */
136
- let grandparent
137
-
138
- while ( ++ index < parents . length ) {
139
- const parent = parents [ index ]
140
-
141
- if (
142
- ignored (
143
- parent ,
144
- // @ts -expect-error: TS doesn’t understand but it’s perfect.
145
- grandparent ? grandparent . children . indexOf ( parent ) : undefined ,
146
- grandparent
147
- )
148
- ) {
149
- return
150
- }
151
-
152
- grandparent = parent
204
+ // It wasn’t a match after all.
205
+ if ( value !== false ) {
206
+ if ( start !== position ) {
207
+ nodes . push ( {
208
+ type : 'text' ,
209
+ value : node . value . slice ( start , position )
210
+ } )
153
211
}
154
212
155
- if ( grandparent ) {
156
- return handler ( node , parents )
213
+ if ( Array . isArray ( value ) ) {
214
+ nodes . push ( ...value )
215
+ } else if ( value ) {
216
+ nodes . push ( value )
157
217
}
158
- }
159
218
160
- /**
161
- * Handle a text node which is not in an ignored parent.
162
- *
163
- * @param {Text } node
164
- * Text node.
165
- * @param {Array<Parents> } parents
166
- * Parents.
167
- * @returns {VisitorResult }
168
- * Result.
169
- */
170
- function handler ( node , parents ) {
171
- const parent = parents [ parents . length - 1 ]
172
- const find = pairs [ pairIndex ] [ 0 ]
173
- const replace = pairs [ pairIndex ] [ 1 ]
174
- let start = 0
175
- // @ts -expect-error: TS is wrong, some of these children can be text.
176
- const index = parent . children . indexOf ( node )
177
- let change = false
178
- /** @type {Array<PhrasingContent> } */
179
- let nodes = [ ]
180
-
181
- find . lastIndex = 0
182
-
183
- let match = find . exec ( node . value )
184
-
185
- while ( match ) {
186
- const position = match . index
187
- /** @type {RegExpMatchObject } */
188
- const matchObject = {
189
- index : match . index ,
190
- input : match . input ,
191
- stack : [ ...parents , node ]
192
- }
193
- let value = replace ( ...match , matchObject )
194
-
195
- if ( typeof value === 'string' ) {
196
- value = value . length > 0 ? { type : 'text' , value} : undefined
197
- }
198
-
199
- // It wasn’t a match after all.
200
- if ( value !== false ) {
201
- if ( start !== position ) {
202
- nodes . push ( {
203
- type : 'text' ,
204
- value : node . value . slice ( start , position )
205
- } )
206
- }
207
-
208
- if ( Array . isArray ( value ) ) {
209
- nodes . push ( ...value )
210
- } else if ( value ) {
211
- nodes . push ( value )
212
- }
213
-
214
- start = position + match [ 0 ] . length
215
- change = true
216
- }
217
-
218
- if ( ! find . global ) {
219
- break
220
- }
221
-
222
- match = find . exec ( node . value )
223
- }
219
+ start = position + match [ 0 ] . length
220
+ change = true
221
+ }
224
222
225
- if ( change ) {
226
- if ( start < node . value . length ) {
227
- nodes . push ( { type : 'text' , value : node . value . slice ( start ) } )
228
- }
223
+ if ( ! find . global ) {
224
+ break
225
+ }
229
226
230
- parent . children . splice ( index , 1 , ...nodes )
231
- } else {
232
- nodes = [ node ]
233
- }
227
+ match = find . exec ( node . value )
228
+ }
234
229
235
- return index + nodes . length
230
+ if ( change ) {
231
+ if ( start < node . value . length ) {
232
+ nodes . push ( { type : 'text' , value : node . value . slice ( start ) } )
236
233
}
234
+
235
+ parent . children . splice ( index , 1 , ...nodes )
236
+ } else {
237
+ nodes = [ node ]
237
238
}
238
- )
239
+
240
+ return index + nodes . length
241
+ }
242
+ }
239
243
240
244
/**
241
245
* Turn a schema into pairs.
242
246
*
243
- * @param {FindAndReplaceSchema | FindAndReplaceList } schema
247
+ * @param {FindAndReplaceList | FindAndReplaceSchema } schema
244
248
* Schema.
245
249
* @returns {Pairs }
246
250
* Clean pairs.
@@ -297,5 +301,9 @@ function toExpression(find) {
297
301
* Function.
298
302
*/
299
303
function toFunction ( replace ) {
300
- return typeof replace === 'function' ? replace : ( ) => replace
304
+ return typeof replace === 'function'
305
+ ? replace
306
+ : function ( ) {
307
+ return replace
308
+ }
301
309
}
0 commit comments