Skip to content

Commit c85728a

Browse files
committed
Fix bug causing missed finds, multiple replaces
1 parent 306095d commit c85728a

File tree

2 files changed

+160
-165
lines changed

2 files changed

+160
-165
lines changed

index.js

+130-149
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
2-
* @typedef Options
3-
* @property {Test} [ignore]
2+
* @typedef Options Configuration.
3+
* @property {Test} [ignore] `unist-util-is` test used to assert parents
44
*
55
* @typedef {import('mdast').Text} Text
66
* @typedef {import('mdast').Parent} Parent
@@ -26,17 +26,9 @@
2626
* @typedef {Array.<Pair>} Pairs
2727
*/
2828

29-
/**
30-
* @callback Handler
31-
* @param {Text} node
32-
* @param {Parent} parent
33-
* @returns {VisitorResult}
34-
*/
35-
3629
/**
3730
* @callback ReplaceFunction
38-
* @param {...string} parameters
39-
* @param {RegExpMatchObject} matchObject
31+
* @param {...unknown} parameters
4032
* @returns {Array.<PhrasingContent>|PhrasingContent|string|false|undefined|null}
4133
*/
4234

@@ -47,168 +39,157 @@ import {convert} from 'unist-util-is'
4739
var own = {}.hasOwnProperty
4840

4941
/**
50-
* @param {Node} tree
51-
* @param {Find|FindAndReplaceSchema|FindAndReplaceList} find
52-
* @param {Replace|Options} [replace]
53-
* @param {Options} [options]
42+
* @param tree mdast tree
43+
* @param find Value to find and remove. When `string`, escaped and made into a global `RegExp`
44+
* @param [replace] Value to insert.
45+
* * When `string`, turned into a Text node.
46+
* * When `Function`, called with the results of calling `RegExp.exec` as
47+
* arguments, in which case it can return a single or a list of `Node`,
48+
* a `string` (which is wrapped in a `Text` node), or `false` to not replace
49+
* @param [options] Configuration.
5450
*/
55-
export function findAndReplace(tree, find, replace, options) {
56-
/** @type {Options} */
57-
var settings
58-
/** @type {FindAndReplaceSchema|FindAndReplaceList} */
59-
var schema
60-
61-
if (typeof find === 'string' || find instanceof RegExp) {
62-
// @ts-expect-error don’t expect options twice.
63-
schema = [[find, replace]]
64-
settings = options
65-
} else {
66-
schema = find
67-
// @ts-expect-error don’t expect replace twice.
68-
settings = replace
69-
}
70-
71-
if (!settings) {
72-
settings = {}
73-
}
74-
75-
search(tree, settings, handlerFactory(toPairs(schema)))
51+
export const findAndReplace =
52+
/**
53+
* @type {(
54+
* ((tree: Node, find: Find, replace?: Replace, options?: Options) => Node) &
55+
* ((tree: Node, schema: FindAndReplaceSchema|FindAndReplaceList, options?: Options) => Node)
56+
* )}
57+
**/
58+
(
59+
/**
60+
* @param {Node} tree
61+
* @param {Find|FindAndReplaceSchema|FindAndReplaceList} find
62+
* @param {Replace|Options} [replace]
63+
* @param {Options} [options]
64+
*/
65+
function (tree, find, replace, options) {
66+
/** @type {Options} */
67+
var settings
68+
/** @type {FindAndReplaceSchema|FindAndReplaceList} */
69+
var schema
70+
71+
if (typeof find === 'string' || find instanceof RegExp) {
72+
// @ts-expect-error don’t expect options twice.
73+
schema = [[find, replace]]
74+
settings = options
75+
} else {
76+
schema = find
77+
// @ts-expect-error don’t expect replace twice.
78+
settings = replace
79+
}
7680

77-
return tree
81+
if (!settings) {
82+
settings = {}
83+
}
7884

79-
/**
80-
* @param {Pairs} pairs
81-
* @returns {Handler}
82-
*/
83-
function handlerFactory(pairs) {
84-
var pair = pairs[0]
85+
var ignored = convert(settings.ignore || [])
86+
var pairs = toPairs(schema)
87+
var pairIndex = -1
8588

86-
return handler
89+
while (++pairIndex < pairs.length) {
90+
visitParents(tree, 'text', visitor)
91+
}
8792

88-
/**
89-
* @type {Handler}
90-
*/
91-
function handler(node, parent) {
92-
var find = pair[0]
93-
var replace = pair[1]
94-
/** @type {Array.<PhrasingContent>} */
95-
var nodes = []
96-
var start = 0
97-
var index = parent.children.indexOf(node)
98-
/** @type {number} */
99-
var position
100-
/** @type {RegExpMatchArray} */
101-
var match
102-
/** @type {Handler} */
103-
var subhandler
104-
/** @type {PhrasingContent} */
105-
var child
106-
/** @type {Array.<PhrasingContent>|PhrasingContent|string|false|undefined|null} */
107-
var value
108-
109-
find.lastIndex = 0
110-
111-
match = find.exec(node.value)
112-
113-
while (match) {
114-
position = match.index
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-
}
93+
return tree
12194

122-
if (value !== false) {
123-
if (start !== position) {
124-
nodes.push({type: 'text', value: node.value.slice(start, position)})
125-
}
95+
/** @type {import('unist-util-visit-parents').Visitor<Text>} */
96+
function visitor(node, parents) {
97+
var index = -1
98+
/** @type {Parent} */
99+
var parent
100+
/** @type {Parent} */
101+
var grandparent
126102

127-
if (value) {
128-
nodes = [].concat(nodes, value)
103+
while (++index < parents.length) {
104+
// @ts-expect-error mdast vs. unist parent.
105+
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
129116
}
130117

131-
start = position + match[0].length
118+
grandparent = parent
132119
}
133120

134-
if (!find.global) {
135-
break
136-
}
121+
return handler(node, grandparent)
122+
}
123+
124+
/**
125+
* @param {Text} node
126+
* @param {Parent} parent
127+
* @returns {VisitorResult}
128+
*/
129+
function handler(node, parent) {
130+
var find = pairs[pairIndex][0]
131+
var replace = pairs[pairIndex][1]
132+
/** @type {Array.<PhrasingContent>} */
133+
var nodes = []
134+
var start = 0
135+
var index = parent.children.indexOf(node)
136+
/** @type {number} */
137+
var position
138+
/** @type {RegExpMatchArray} */
139+
var match
140+
/** @type {Array.<PhrasingContent>|PhrasingContent|string|false|undefined|null} */
141+
var value
142+
143+
find.lastIndex = 0
137144

138145
match = find.exec(node.value)
139-
}
140146

141-
if (position === undefined) {
142-
nodes = [node]
143-
index--
144-
} else {
145-
if (start < node.value.length) {
146-
nodes.push({type: 'text', value: node.value.slice(start)})
147-
}
147+
while (match) {
148+
position = match.index
149+
// @ts-expect-error this is perfectly fine, typescript.
150+
value = replace(...match, {index: match.index, input: match.input})
148151

149-
parent.children.splice(index, 1, ...nodes)
150-
}
152+
if (typeof value === 'string' && value.length > 0) {
153+
value = {type: 'text', value}
154+
}
151155

152-
if (pairs.length > 1) {
153-
subhandler = handlerFactory(pairs.slice(1))
154-
position = -1
156+
if (value !== false) {
157+
if (start !== position) {
158+
nodes.push({
159+
type: 'text',
160+
value: node.value.slice(start, position)
161+
})
162+
}
155163

156-
while (++position < nodes.length) {
157-
child = nodes[position]
164+
if (value) {
165+
nodes = [].concat(nodes, value)
166+
}
158167

159-
if (child.type === 'text') {
160-
subhandler(child, parent)
161-
} else {
162-
search(child, settings, subhandler)
168+
start = position + match[0].length
163169
}
170+
171+
if (!find.global) {
172+
break
173+
}
174+
175+
match = find.exec(node.value)
164176
}
165-
}
166177

167-
return index + nodes.length + 1
168-
}
169-
}
170-
}
178+
if (position === undefined) {
179+
nodes = [node]
180+
index--
181+
} else {
182+
if (start < node.value.length) {
183+
nodes.push({type: 'text', value: node.value.slice(start)})
184+
}
171185

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 || [])
180-
181-
visitParents(tree, 'text', visitor)
182-
183-
/** @type {import('unist-util-visit-parents').Visitor<Text>} */
184-
function visitor(node, parents) {
185-
var index = -1
186-
/** @type {Parent} */
187-
var parent
188-
/** @type {Parent} */
189-
var grandparent
190-
191-
while (++index < parents.length) {
192-
// @ts-expect-error mdast vs. unist parent.
193-
parent = parents[index]
194-
195-
if (
196-
ignored(
197-
parent,
198-
// @ts-expect-error mdast vs. unist parent.
199-
grandparent ? grandparent.children.indexOf(parent) : undefined,
200-
grandparent
201-
)
202-
) {
203-
return
204-
}
186+
parent.children.splice(index, 1, ...nodes)
187+
}
205188

206-
grandparent = parent
189+
return index + nodes.length + 1
190+
}
207191
}
208-
209-
return handler(node, grandparent)
210-
}
211-
}
192+
)
212193

213194
/**
214195
* @param {FindAndReplaceSchema|FindAndReplaceList} schema

0 commit comments

Comments
 (0)