1818 * @property {[...Array<Parents>, Text] } stack
1919 * All ancestors of the text node, where the last node is the text itself.
2020 *
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+ *
2138 * @callback ReplaceFunction
2239 * Callback called when a search matches.
2340 * @param {...any } parameters
2643 * * `value` (`string`) — whole match
2744 * * `...capture` (`Array<string>`) — matches from regex capture groups
2845 * * `match` (`RegExpMatchObject`) — info on the match
29- * @returns {Array<PhrasingContent> | PhrasingContent | string | false | undefined | null }
46+ * @returns {Array<PhrasingContent> | PhrasingContent | string | false | null | undefined }
3047 * Thing to replace with.
3148 *
3249 * * when `null`, `undefined`, `''`, remove the match
3350 * * …or when `false`, do not replace at all
3451 * * …or when `string`, replace with a text node of that value
3552 * * …or when `Node` or `Array<Node>`, replace with those nodes
3653 *
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.
5054 * @typedef {[RegExp, ReplaceFunction] } Pair
5155 * Normalized find and replace.
56+ *
5257 * @typedef {Array<Pair> } Pairs
5358 * All find and replaced.
5459 *
5560 * @typedef Options
5661 * Configuration.
5762 * @property {Test | null | undefined } [ignore]
58- * Test for which nodes to ignore.
63+ * Test for which nodes to ignore (optional) .
5964 */
6065
6166import escape from 'escape-string-regexp'
@@ -71,176 +76,175 @@ const own = {}.hasOwnProperty
7176 * nodes.
7277 * Partial matches are not supported.
7378 *
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
7593 * Tree to change.
76- * @param find
94+ * @param { Find | FindAndReplaceList | FindAndReplaceSchema } find
7795 * Patterns to find.
78- * @param replace
96+ * @param { Options | Replace | null | undefined } [ replace]
7997 * Things to replace with (when `find` is `Find`) or configuration.
80- * @param options
98+ * @param { Options | null | undefined } [ options]
8199 * Configuration (when `find` is not `Find`).
82- * @returns
100+ * @returns { undefined }
83101 * Given, modified, tree.
84102 */
85103// 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+ }
116119
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
119154 }
120155
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 )
124199
125- while ( ++ pairIndex < pairs . length ) {
126- visitParents ( tree , 'text' , visitor )
200+ if ( typeof value === 'string' ) {
201+ value = value . length > 0 ? { type : 'text' , value } : undefined
127202 }
128203
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+ } )
153211 }
154212
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 )
157217 }
158- }
159218
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+ }
224222
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+ }
229226
230- parent . children . splice ( index , 1 , ...nodes )
231- } else {
232- nodes = [ node ]
233- }
227+ match = find . exec ( node . value )
228+ }
234229
235- return index + nodes . length
230+ if ( change ) {
231+ if ( start < node . value . length ) {
232+ nodes . push ( { type : 'text' , value : node . value . slice ( start ) } )
236233 }
234+
235+ parent . children . splice ( index , 1 , ...nodes )
236+ } else {
237+ nodes = [ node ]
237238 }
238- )
239+
240+ return index + nodes . length
241+ }
242+ }
239243
240244/**
241245 * Turn a schema into pairs.
242246 *
243- * @param {FindAndReplaceSchema | FindAndReplaceList } schema
247+ * @param {FindAndReplaceList | FindAndReplaceSchema } schema
244248 * Schema.
245249 * @returns {Pairs }
246250 * Clean pairs.
@@ -297,5 +301,9 @@ function toExpression(find) {
297301 * Function.
298302 */
299303function toFunction ( replace ) {
300- return typeof replace === 'function' ? replace : ( ) => replace
304+ return typeof replace === 'function'
305+ ? replace
306+ : function ( ) {
307+ return replace
308+ }
301309}
0 commit comments