syntax-tree / mdast-util-find-and-replace
Showing 1 of 5 files from the diff.
Other files ignored by Codecov
package.json has changed.
test.js has changed.
.gitignore has changed.

@@ -1,41 +1,109 @@
Loading
1 +
/**
2 +
 * @typedef Options
3 +
 * @property {Test} [ignore]
4 +
 *
5 +
 * @typedef {import('mdast').Text} Text
6 +
 * @typedef {import('mdast').Parent} Parent
7 +
 * @typedef {import('mdast').Root} Root
8 +
 * @typedef {import('mdast').PhrasingContent} PhrasingContent
9 +
 * @typedef {Parent['children'][number]|Root} Node
10 +
 *
11 +
 * @typedef {import('unist-util-visit-parents').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 {...string} parameters
39 +
 * @param {RegExpMatchObject} matchObject
40 +
 * @returns {Array.<PhrasingContent>|PhrasingContent|string|false|undefined|null}
41 +
 */
42 +
1 43
import escape from 'escape-string-regexp'
2 44
import {visitParents} from 'unist-util-visit-parents'
3 45
import {convert} from 'unist-util-is'
4 46
5 47
var own = {}.hasOwnProperty
6 -
var splice = [].splice
7 48
49 +
/**
50 +
 * @param {Node} tree
51 +
 * @param {Find|FindAndReplaceSchema|FindAndReplaceList} find
52 +
 * @param {Replace|Options} [replace]
53 +
 * @param {Options} [options]
54 +
 */
8 55
export function findAndReplace(tree, find, replace, options) {
56 +
  /** @type {Options} */
9 57
  var settings
58 +
  /** @type {FindAndReplaceSchema|FindAndReplaceList} */
10 59
  var schema
11 60
12 -
  if (typeof find === 'string' || (find && typeof find.exec === 'function')) {
61 +
  if (typeof find === 'string' || find instanceof RegExp) {
62 +
    // @ts-expect-error don’t expect options twice.
13 63
    schema = [[find, replace]]
64 +
    settings = options
14 65
  } else {
15 66
    schema = find
16 -
    options = replace
67 +
    // @ts-expect-error don’t expect replace twice.
68 +
    settings = replace
17 69
  }
18 70
19 -
  settings = options || {}
71 +
  if (!settings) {
72 +
    settings = {}
73 +
  }
20 74
21 75
  search(tree, settings, handlerFactory(toPairs(schema)))
22 76
23 77
  return tree
24 78
79 +
  /**
80 +
   * @param {Pairs} pairs
81 +
   * @returns {Handler}
82 +
   */
25 83
  function handlerFactory(pairs) {
26 84
    var pair = pairs[0]
27 85
28 86
    return handler
29 87
88 +
    /**
89 +
     * @type {Handler}
90 +
     */
30 91
    function handler(node, parent) {
31 92
      var find = pair[0]
32 93
      var replace = pair[1]
94 +
      /** @type {Array.<PhrasingContent>} */
33 95
      var nodes = []
34 96
      var start = 0
35 97
      var index = parent.children.indexOf(node)
98 +
      /** @type {number} */
36 99
      var position
100 +
      /** @type {RegExpMatchArray} */
37 101
      var match
102 +
      /** @type {Handler} */
38 103
      var subhandler
104 +
      /** @type {PhrasingContent} */
105 +
      var child
106 +
      /** @type {Array.<PhrasingContent>|PhrasingContent|string|false|undefined|null} */
39 107
      var value
40 108
41 109
      find.lastIndex = 0
@@ -44,17 +112,18 @@
Loading
44 112
45 113
      while (match) {
46 114
        position = match.index
47 -
        value = replace(...match, {index: match.index, input: match.input})
115 +
        // @ts-expect-error this is perfectly fine, typescript.
116 +
        value = replace(...match, {index: position, input: match.input})
117 +
118 +
        if (typeof value === 'string' && value.length > 0) {
119 +
          value = {type: 'text', value}
120 +
        }
48 121
49 122
        if (value !== false) {
50 123
          if (start !== position) {
51 124
            nodes.push({type: 'text', value: node.value.slice(start, position)})
52 125
          }
53 126
54 -
          if (typeof value === 'string' && value.length > 0) {
55 -
            value = {type: 'text', value}
56 -
          }
57 -
58 127
          if (value) {
59 128
            nodes = [].concat(nodes, value)
60 129
          }
@@ -77,8 +146,7 @@
Loading
77 146
          nodes.push({type: 'text', value: node.value.slice(start)})
78 147
        }
79 148
80 -
        nodes.unshift(index, 1)
81 -
        splice.apply(parent.children, nodes)
149 +
        parent.children.splice(index, 1, ...nodes)
82 150
      }
83 151
84 152
      if (pairs.length > 1) {
@@ -86,12 +154,12 @@
Loading
86 154
        position = -1
87 155
88 156
        while (++position < nodes.length) {
89 -
          node = nodes[position]
157 +
          child = nodes[position]
90 158
91 -
          if (node.type === 'text') {
92 -
            subhandler(node, parent)
159 +
          if (child.type === 'text') {
160 +
            subhandler(child, parent)
93 161
          } else {
94 -
            search(node, settings, subhandler)
162 +
            search(child, settings, subhandler)
95 163
          }
96 164
        }
97 165
      }
@@ -101,25 +169,33 @@
Loading
101 169
  }
102 170
}
103 171
104 -
function search(tree, settings, handler) {
105 -
  var ignored = convert(settings.ignore || [])
106 -
  var result = []
172 +
/**
173 +
 * @param {Node} tree
174 +
 * @param {Options} options
175 +
 * @param {Handler} handler
176 +
 * @returns {void}
177 +
 */
178 +
function search(tree, options, handler) {
179 +
  var ignored = convert(options.ignore || [])
107 180
108 181
  visitParents(tree, 'text', visitor)
109 182
110 -
  return result
111 -
183 +
  /** @type {import('unist-util-visit-parents').Visitor<Text>} */
112 184
  function visitor(node, parents) {
113 185
    var index = -1
186 +
    /** @type {Parent} */
114 187
    var parent
188 +
    /** @type {Parent} */
115 189
    var grandparent
116 190
117 191
    while (++index < parents.length) {
192 +
      // @ts-expect-error mdast vs. unist parent.
118 193
      parent = parents[index]
119 194
120 195
      if (
121 196
        ignored(
122 197
          parent,
198 +
          // @ts-expect-error mdast vs. unist parent.
123 199
          grandparent ? grandparent.children.indexOf(parent) : undefined,
124 200
          grandparent
125 201
        )
@@ -134,18 +210,22 @@
Loading
134 210
  }
135 211
}
136 212
213 +
/**
214 +
 * @param {FindAndReplaceSchema|FindAndReplaceList} schema
215 +
 * @returns {Pairs}
216 +
 */
137 217
function toPairs(schema) {
218 +
  var index = -1
219 +
  /** @type {Pairs} */
138 220
  var result = []
221 +
  /** @type {string} */
139 222
  var key
140 -
  var index
141 223
142 224
  if (typeof schema !== 'object') {
143 225
    throw new TypeError('Expected array or object as schema')
144 226
  }
145 227
146 -
  if ('length' in schema) {
147 -
    index = -1
148 -
228 +
  if (Array.isArray(schema)) {
149 229
    while (++index < schema.length) {
150 230
      result.push([
151 231
        toExpression(schema[index][0]),
@@ -163,14 +243,24 @@
Loading
163 243
  return result
164 244
}
165 245
246 +
/**
247 +
 * @param {Find} find
248 +
 * @returns {RegExp}
249 +
 */
166 250
function toExpression(find) {
167 251
  return typeof find === 'string' ? new RegExp(escape(find), 'g') : find
168 252
}
169 253
254 +
/**
255 +
 * @param {Replace} replace
256 +
 * @returns {ReplaceFunction}
257 +
 */
170 258
function toFunction(replace) {
171 259
  return typeof replace === 'function' ? replace : returner
172 260
261 +
  /** @type {ReplaceFunction} */
173 262
  function returner() {
263 +
    // @ts-expect-error it’s a string.
174 264
    return replace
175 265
  }
176 266
}
Files Coverage
index.js 100.00%
Project Totals (1 files) 100.00%
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading