| 
1 | 1 | /**  | 
2 |  | - * @typedef Options  | 
3 |  | - *   Configuration (optional).  | 
4 |  | - * @property {Test} [ignore]  | 
5 |  | - *   `unist-util-is` test used to assert parents  | 
6 |  | - *  | 
7 |  | - * @typedef {import('mdast').Root} Root  | 
8 |  | - * @typedef {import('mdast').Content} Content  | 
9 |  | - * @typedef {import('mdast').PhrasingContent} PhrasingContent  | 
10 |  | - * @typedef {import('mdast').Text} Text  | 
11 |  | - * @typedef {Content|Root} Node  | 
12 |  | - * @typedef {Extract<Node, import('mdast').Parent>} Parent  | 
13 |  | - *  | 
14 |  | - * @typedef {import('unist-util-visit-parents').Test} Test  | 
15 |  | - * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult  | 
16 |  | - *  | 
17 |  | - * @typedef RegExpMatchObject  | 
18 |  | - * @property {number} index  | 
19 |  | - * @property {string} input  | 
20 |  | - *  | 
21 |  | - * @typedef {string|RegExp} Find  | 
22 |  | - * @typedef {string|ReplaceFunction} Replace  | 
23 |  | - *  | 
24 |  | - * @typedef {[Find, Replace]} FindAndReplaceTuple  | 
25 |  | - * @typedef {Record<string, Replace>} FindAndReplaceSchema  | 
26 |  | - * @typedef {Array<FindAndReplaceTuple>} FindAndReplaceList  | 
27 |  | - *  | 
28 |  | - * @typedef {[RegExp, ReplaceFunction]} Pair  | 
29 |  | - * @typedef {Array<Pair>} Pairs  | 
 | 2 | + * @typedef {import('./lib/index.js').Options} Options  | 
 | 3 | + * @typedef {import('./lib/index.js').RegExpMatchObject} RegExpMatchObject  | 
 | 4 | + * @typedef {import('./lib/index.js').Find} Find  | 
 | 5 | +  * @typedef {import('./lib/index.js').Replace} Replace  | 
 | 6 | +  * @typedef {import('./lib/index.js').ReplaceFunction} ReplaceFunction  | 
 | 7 | +  * @typedef {import('./lib/index.js').FindAndReplaceTuple} FindAndReplaceTuple  | 
 | 8 | +  * @typedef {import('./lib/index.js').FindAndReplaceSchema} FindAndReplaceSchema  | 
 | 9 | +  * @typedef {import('./lib/index.js').FindAndReplaceList} FindAndReplaceList  | 
30 | 10 |  */  | 
31 | 11 | 
 
  | 
32 |  | -/**  | 
33 |  | - * @callback ReplaceFunction  | 
34 |  | - * @param {...any} parameters  | 
35 |  | - * @returns {Array<PhrasingContent>|PhrasingContent|string|false|undefined|null}  | 
36 |  | - */  | 
37 |  | - | 
38 |  | -import escape from 'escape-string-regexp'  | 
39 |  | -import {visitParents} from 'unist-util-visit-parents'  | 
40 |  | -import {convert} from 'unist-util-is'  | 
41 |  | - | 
42 |  | -const own = {}.hasOwnProperty  | 
43 |  | - | 
44 |  | -/**  | 
45 |  | - * @param tree mdast tree  | 
46 |  | - * @param find Value to find and remove. When `string`, escaped and made into a global `RegExp`  | 
47 |  | - * @param [replace] Value to insert.  | 
48 |  | - *   * When `string`, turned into a Text node.  | 
49 |  | - *   * When `Function`, called with the results of calling `RegExp.exec` as  | 
50 |  | - *     arguments, in which case it can return a single or a list of `Node`,  | 
51 |  | - *     a `string` (which is wrapped in a `Text` node), or `false` to not replace  | 
52 |  | - * @param [options] Configuration.  | 
53 |  | - */  | 
54 |  | -export const findAndReplace =  | 
55 |  | -  /**  | 
56 |  | -   * @type {(  | 
57 |  | -   *   ((tree: Node, find: Find, replace?: Replace, options?: Options) => Node) &  | 
58 |  | -   *   ((tree: Node, schema: FindAndReplaceSchema|FindAndReplaceList, options?: Options) => Node)  | 
59 |  | -   * )}  | 
60 |  | -   **/  | 
61 |  | -  (  | 
62 |  | -    /**  | 
63 |  | -     * @param {Node} tree  | 
64 |  | -     * @param {Find|FindAndReplaceSchema|FindAndReplaceList} find  | 
65 |  | -     * @param {Replace|Options} [replace]  | 
66 |  | -     * @param {Options} [options]  | 
67 |  | -     */  | 
68 |  | -    function (tree, find, replace, options) {  | 
69 |  | -      /** @type {Options|undefined} */  | 
70 |  | -      let settings  | 
71 |  | -      /** @type {FindAndReplaceSchema|FindAndReplaceList} */  | 
72 |  | -      let schema  | 
73 |  | - | 
74 |  | -      if (typeof find === 'string' || find instanceof RegExp) {  | 
75 |  | -        // @ts-expect-error don’t expect options twice.  | 
76 |  | -        schema = [[find, replace]]  | 
77 |  | -        settings = options  | 
78 |  | -      } else {  | 
79 |  | -        schema = find  | 
80 |  | -        // @ts-expect-error don’t expect replace twice.  | 
81 |  | -        settings = replace  | 
82 |  | -      }  | 
83 |  | - | 
84 |  | -      if (!settings) {  | 
85 |  | -        settings = {}  | 
86 |  | -      }  | 
87 |  | - | 
88 |  | -      const ignored = convert(settings.ignore || [])  | 
89 |  | -      const pairs = toPairs(schema)  | 
90 |  | -      let pairIndex = -1  | 
91 |  | - | 
92 |  | -      while (++pairIndex < pairs.length) {  | 
93 |  | -        visitParents(tree, 'text', visitor)  | 
94 |  | -      }  | 
95 |  | - | 
96 |  | -      return tree  | 
97 |  | - | 
98 |  | -      /** @type {import('unist-util-visit-parents/complex-types').BuildVisitor<Root, 'text'>} */  | 
99 |  | -      function visitor(node, parents) {  | 
100 |  | -        let index = -1  | 
101 |  | -        /** @type {Parent|undefined} */  | 
102 |  | -        let grandparent  | 
103 |  | - | 
104 |  | -        while (++index < parents.length) {  | 
105 |  | -          const parent = /** @type {Parent} */ (parents[index])  | 
106 |  | - | 
107 |  | -          if (  | 
108 |  | -            ignored(  | 
109 |  | -              parent,  | 
110 |  | -              // @ts-expect-error mdast vs. unist parent.  | 
111 |  | -              grandparent ? grandparent.children.indexOf(parent) : undefined,  | 
112 |  | -              grandparent  | 
113 |  | -            )  | 
114 |  | -          ) {  | 
115 |  | -            return  | 
116 |  | -          }  | 
117 |  | - | 
118 |  | -          grandparent = parent  | 
119 |  | -        }  | 
120 |  | - | 
121 |  | -        if (grandparent) {  | 
122 |  | -          return handler(node, grandparent)  | 
123 |  | -        }  | 
124 |  | -      }  | 
125 |  | - | 
126 |  | -      /**  | 
127 |  | -       * @param {Text} node  | 
128 |  | -       * @param {Parent} parent  | 
129 |  | -       * @returns {VisitorResult}  | 
130 |  | -       */  | 
131 |  | -      function handler(node, parent) {  | 
132 |  | -        const find = pairs[pairIndex][0]  | 
133 |  | -        const replace = pairs[pairIndex][1]  | 
134 |  | -        let start = 0  | 
135 |  | -        // @ts-expect-error: TS is wrong, some of these children can be text.  | 
136 |  | -        let index = parent.children.indexOf(node)  | 
137 |  | -        /** @type {Array<PhrasingContent>} */  | 
138 |  | -        let nodes = []  | 
139 |  | -        /** @type {number|undefined} */  | 
140 |  | -        let position  | 
141 |  | - | 
142 |  | -        find.lastIndex = 0  | 
143 |  | - | 
144 |  | -        let match = find.exec(node.value)  | 
145 |  | - | 
146 |  | -        while (match) {  | 
147 |  | -          position = match.index  | 
148 |  | -          let value = replace(...match, {  | 
149 |  | -            index: match.index,  | 
150 |  | -            input: match.input  | 
151 |  | -          })  | 
152 |  | - | 
153 |  | -          if (typeof value === 'string') {  | 
154 |  | -            value = value.length > 0 ? {type: 'text', value} : undefined  | 
155 |  | -          }  | 
156 |  | - | 
157 |  | -          if (value !== false) {  | 
158 |  | -            if (start !== position) {  | 
159 |  | -              nodes.push({  | 
160 |  | -                type: 'text',  | 
161 |  | -                value: node.value.slice(start, position)  | 
162 |  | -              })  | 
163 |  | -            }  | 
164 |  | - | 
165 |  | -            if (Array.isArray(value)) {  | 
166 |  | -              nodes.push(...value)  | 
167 |  | -            } else if (value) {  | 
168 |  | -              nodes.push(value)  | 
169 |  | -            }  | 
170 |  | - | 
171 |  | -            start = position + match[0].length  | 
172 |  | -          }  | 
173 |  | - | 
174 |  | -          if (!find.global) {  | 
175 |  | -            break  | 
176 |  | -          }  | 
177 |  | - | 
178 |  | -          match = find.exec(node.value)  | 
179 |  | -        }  | 
180 |  | - | 
181 |  | -        if (position === undefined) {  | 
182 |  | -          nodes = [node]  | 
183 |  | -          index--  | 
184 |  | -        } else {  | 
185 |  | -          if (start < node.value.length) {  | 
186 |  | -            nodes.push({type: 'text', value: node.value.slice(start)})  | 
187 |  | -          }  | 
188 |  | - | 
189 |  | -          parent.children.splice(index, 1, ...nodes)  | 
190 |  | -        }  | 
191 |  | - | 
192 |  | -        return index + nodes.length + 1  | 
193 |  | -      }  | 
194 |  | -    }  | 
195 |  | -  )  | 
196 |  | - | 
197 |  | -/**  | 
198 |  | - * @param {FindAndReplaceSchema|FindAndReplaceList} schema  | 
199 |  | - * @returns {Pairs}  | 
200 |  | - */  | 
201 |  | -function toPairs(schema) {  | 
202 |  | -  /** @type {Pairs} */  | 
203 |  | -  const result = []  | 
204 |  | - | 
205 |  | -  if (typeof schema !== 'object') {  | 
206 |  | -    throw new TypeError('Expected array or object as schema')  | 
207 |  | -  }  | 
208 |  | - | 
209 |  | -  if (Array.isArray(schema)) {  | 
210 |  | -    let index = -1  | 
211 |  | - | 
212 |  | -    while (++index < schema.length) {  | 
213 |  | -      result.push([  | 
214 |  | -        toExpression(schema[index][0]),  | 
215 |  | -        toFunction(schema[index][1])  | 
216 |  | -      ])  | 
217 |  | -    }  | 
218 |  | -  } else {  | 
219 |  | -    /** @type {string} */  | 
220 |  | -    let key  | 
221 |  | - | 
222 |  | -    for (key in schema) {  | 
223 |  | -      if (own.call(schema, key)) {  | 
224 |  | -        result.push([toExpression(key), toFunction(schema[key])])  | 
225 |  | -      }  | 
226 |  | -    }  | 
227 |  | -  }  | 
228 |  | - | 
229 |  | -  return result  | 
230 |  | -}  | 
231 |  | - | 
232 |  | -/**  | 
233 |  | - * @param {Find} find  | 
234 |  | - * @returns {RegExp}  | 
235 |  | - */  | 
236 |  | -function toExpression(find) {  | 
237 |  | -  return typeof find === 'string' ? new RegExp(escape(find), 'g') : find  | 
238 |  | -}  | 
239 |  | - | 
240 |  | -/**  | 
241 |  | - * @param {Replace} replace  | 
242 |  | - * @returns {ReplaceFunction}  | 
243 |  | - */  | 
244 |  | -function toFunction(replace) {  | 
245 |  | -  return typeof replace === 'function' ? replace : () => replace  | 
246 |  | -}  | 
 | 12 | +export {findAndReplace} from './lib/index.js'  | 
0 commit comments