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+
142import { visitParents } from 'unist-util-visit-parents'
243import { convertElement } from 'hast-util-is-element'
344import escape from 'escape-string-regexp'
445
5- var splice = [ ] . splice
646var own = { } . hasOwnProperty
747
848export const defaultIgnore = [ 'title' , 'script' , 'style' , 'svg' , 'math' ]
949
50+ /**
51+ * @param {Node } tree
52+ * @param {Find|FindAndReplaceSchema|FindAndReplaceList } find
53+ * @param {Replace|Options } [replace]
54+ * @param {Options } [options]
55+ */
1056export function findAndReplace ( tree , find , replace , options ) {
57+ /** @type {Options } */
1158 var settings
59+ /** @type {FindAndReplaceSchema|FindAndReplaceList } */
1260 var schema
1361
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.
1564 schema = [ [ find , replace ] ]
65+ settings = options
1666 } else {
1767 schema = find
18- options = replace
68+ // @ts -expect-error don’t expect replace twice.
69+ settings = replace
1970 }
2071
21- settings = options || { }
72+ if ( ! settings ) {
73+ settings = { }
74+ }
2275
2376 search ( tree , settings , handlerFactory ( toPairs ( schema ) ) )
2477
2578 return tree
2679
80+ /**
81+ * @param {Pairs } pairs
82+ * @returns {Handler }
83+ */
2784 function handlerFactory ( pairs ) {
2885 var pair = pairs [ 0 ]
2986
3087 return handler
3188
89+ /**
90+ * @type {Handler }
91+ */
3292 function handler ( node , parent ) {
3393 var find = pair [ 0 ]
3494 var replace = pair [ 1 ]
95+ /** @type {Array.<Content> } */
3596 var nodes = [ ]
3697 var start = 0
3798 var index = parent . children . indexOf ( node )
99+ /** @type {number } */
38100 var position
101+ /** @type {RegExpMatchArray } */
39102 var match
103+ /** @type {Handler } */
40104 var subhandler
105+ /** @type {Content } */
106+ var child
107+ /** @type {Array.<Content>|Content|string|false|undefined|null } */
41108 var value
42109
43110 find . lastIndex = 0
@@ -46,19 +113,20 @@ export function findAndReplace(tree, find, replace, options) {
46113
47114 while ( match ) {
48115 position = match . index
116+ // @ts -expect-error this is perfectly fine, typescript.
49117 value = replace ( ...match , { index : match . index , input : match . input } )
50118
119+ if ( typeof value === 'string' && value . length > 0 ) {
120+ value = { type : 'text' , value}
121+ }
122+
51123 if ( value !== false ) {
52124 if ( start !== position ) {
53125 nodes . push ( { type : 'text' , value : node . value . slice ( start , position ) } )
54126 }
55127
56- if ( typeof value === 'string' && value . length > 0 ) {
57- value = { type : 'text' , value}
58- }
59-
60128 if ( value ) {
61- nodes . push ( value )
129+ nodes = [ ] . concat ( nodes , value )
62130 }
63131
64132 start = position + match [ 0 ] . length
@@ -79,21 +147,22 @@ export function findAndReplace(tree, find, replace, options) {
79147 nodes . push ( { type : 'text' , value : node . value . slice ( start ) } )
80148 }
81149
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 )
84153 }
85154
86155 if ( pairs . length > 1 ) {
87156 subhandler = handlerFactory ( pairs . slice ( 1 ) )
88157 position = - 1
89158
90159 while ( ++ position < nodes . length ) {
91- node = nodes [ position ]
160+ child = nodes [ position ]
92161
93- if ( node . type === 'text' ) {
94- subhandler ( node , parent )
162+ if ( child . type === 'text' ) {
163+ subhandler ( child , parent )
95164 } else {
96- search ( node , settings , subhandler )
165+ search ( child , settings , subhandler )
97166 }
98167 }
99168 }
@@ -103,25 +172,33 @@ export function findAndReplace(tree, find, replace, options) {
103172 }
104173}
105174
175+ /**
176+ * @param {Node } tree
177+ * @param {Options } options
178+ * @param {Handler } handler
179+ * @returns {void }
180+ */
106181function search ( tree , options , handler ) {
107182 var ignored = convertElement ( options . ignore || defaultIgnore )
108- var result = [ ]
109183
110184 visitParents ( tree , 'text' , visitor )
111185
112- return result
113-
186+ /** @type {import('unist-util-visit-parents').Visitor<Text> } */
114187 function visitor ( node , parents ) {
115188 var index = - 1
189+ /** @type {Parent } */
116190 var parent
191+ /** @type {Parent } */
117192 var grandparent
118193
119194 while ( ++ index < parents . length ) {
195+ // @ts -expect-error hast vs. unist parent.
120196 parent = parents [ index ]
121197
122198 if (
123199 ignored (
124200 parent ,
201+ // @ts -expect-error hast vs. unist parent.
125202 grandparent ? grandparent . children . indexOf ( parent ) : undefined ,
126203 grandparent
127204 )
@@ -136,18 +213,22 @@ function search(tree, options, handler) {
136213 }
137214}
138215
216+ /**
217+ * @param {FindAndReplaceSchema|FindAndReplaceList } schema
218+ * @returns {Pairs }
219+ */
139220function toPairs ( schema ) {
221+ var index = - 1
222+ /** @type {Pairs } */
140223 var result = [ ]
224+ /** @type {string } */
141225 var key
142- var index
143226
144227 if ( typeof schema !== 'object' ) {
145228 throw new TypeError ( 'Expected array or object as schema' )
146229 }
147230
148- if ( 'length' in schema ) {
149- index = - 1
150-
231+ if ( Array . isArray ( schema ) ) {
151232 while ( ++ index < schema . length ) {
152233 result . push ( [
153234 toExpression ( schema [ index ] [ 0 ] ) ,
@@ -165,14 +246,24 @@ function toPairs(schema) {
165246 return result
166247}
167248
249+ /**
250+ * @param {Find } find
251+ * @returns {RegExp }
252+ */
168253function toExpression ( find ) {
169254 return typeof find === 'string' ? new RegExp ( escape ( find ) , 'g' ) : find
170255}
171256
257+ /**
258+ * @param {Replace } replace
259+ * @returns {ReplaceFunction }
260+ */
172261function toFunction ( replace ) {
173262 return typeof replace === 'function' ? replace : returner
174263
264+ /** @type {ReplaceFunction } */
175265 function returner ( ) {
266+ // @ts -expect-error it’s a string.
176267 return replace
177268 }
178269}
0 commit comments