11/**
2- * @typedef Options
3- * Configuration (optional).
4- * @property {Test } [ignore]
5- * `unist-util-is` test used to assert parents
6- *
2+ * @typedef {import('mdast').Parent } MdastParent
73 * @typedef {import('mdast').Root } Root
84 * @typedef {import('mdast').Content } Content
95 * @typedef {import('mdast').PhrasingContent } PhrasingContent
106 * @typedef {import('mdast').Text } Text
11- * @typedef {Content|Root } Node
12- * @typedef {Exclude<Extract<Node, import('mdast').Parent>, Root> } Parent
13- *
147 * @typedef {import('unist-util-visit-parents').Test } Test
158 * @typedef {import('unist-util-visit-parents').VisitorResult } VisitorResult
9+ */
10+
11+ /**
12+ * @typedef {Content | Root } Node
13+ * @typedef {Extract<Node, MdastParent> } Parent
14+ * @typedef {Exclude<Parent, Root> } ContentParent
1615 *
1716 * @typedef RegExpMatchObject
17+ * Info on the match.
1818 * @property {number } index
19+ * The index of the search at which the result was found.
1920 * @property {string } input
20- * @property {[Root, ...Array<Parent>, Text] } stack
21+ * A copy of the search string in the text node.
22+ * @property {[Root, ...Array<ContentParent>, Text] } stack
23+ * All ancestors of the text node, where the last node is the text itself.
2124 *
22- * @typedef {string|RegExp } Find
23- * @typedef {string|ReplaceFunction } Replace
25+ * @callback ReplaceFunction
26+ * Callback called when a search matches.
27+ * @param {...any } parameters
28+ * The parameters are the result of corresponding search expression:
2429 *
25- * @typedef {[Find, Replace] } FindAndReplaceTuple
26- * @typedef {Record<string, Replace> } FindAndReplaceSchema
27- * @typedef {Array<FindAndReplaceTuple> } FindAndReplaceList
30+ * * `value` (`string`) — whole match
31+ * * `...capture` (`Array<string>`) — matches from regex capture groups
32+ * * `match` (`RegExpMatchObject`) — info on the match
33+ * @returns {Array<PhrasingContent> | PhrasingContent | string | false | undefined | null }
34+ * Thing to replace with.
35+ *
36+ * * when `null`, `undefined`, `''`, remove the match
37+ * * …or when `false`, do not replace at all
38+ * * …or when `string`, replace with a text node of that value
39+ * * …or when `Node` or `Array<Node>`, replace with those nodes
2840 *
41+ * @typedef {string | RegExp } Find
42+ * Pattern to find.
43+ *
44+ * Strings are escaped and then turned into global expressions.
45+ *
46+ * @typedef {Array<FindAndReplaceTuple> } FindAndReplaceList
47+ * Several find and replaces, in array form.
48+ * @typedef {Record<string, Replace> } FindAndReplaceSchema
49+ * Several find and replaces, in object form.
50+ * @typedef {[Find, Replace] } FindAndReplaceTuple
51+ * Find and replace in tuple form.
52+ * @typedef {string | ReplaceFunction } Replace
53+ * Thing to replace with.
2954 * @typedef {[RegExp, ReplaceFunction] } Pair
55+ * Normalized find and replace.
3056 * @typedef {Array<Pair> } Pairs
31- */
32-
33- /**
34- * @callback ReplaceFunction
35- * @param { ...any } parameters
36- * @returns { Array<PhrasingContent>|PhrasingContent|string|false|undefined|null }
57+ * All find and replaced.
58+ *
59+ * @typedef Options
60+ * Configuration.
61+ * @property { Test | null | undefined } [ignore]
62+ * Test for which nodes to ignore.
3763 */
3864
3965import escape from 'escape-string-regexp'
@@ -43,31 +69,42 @@ import {convert} from 'unist-util-is'
4369const own = { } . hasOwnProperty
4470
4571/**
46- * @param tree mdast tree
47- * @param find Value to find and remove. When `string`, escaped and made into a global `RegExp`
48- * @param [replace] Value to insert.
49- * * When `string`, turned into a Text node.
50- * * When `Function`, called with the results of calling `RegExp.exec` as
51- * arguments, in which case it can return a single or a list of `Node`,
52- * a `string` (which is wrapped in a `Text` node), or `false` to not replace
53- * @param [options] Configuration.
72+ * Find patterns in a tree and replace them.
73+ *
74+ * The algorithm searches the tree in *preorder* for complete values in `Text`
75+ * nodes.
76+ * Partial matches are not supported.
77+ *
78+ * @param tree
79+ * Tree to change.
80+ * @param find
81+ * Patterns to find.
82+ * @param replace
83+ * Things to replace with (when `find` is `Find`) or configuration.
84+ * @param options
85+ * Configuration (when `find` is not `Find`).
86+ * @returns
87+ * Given, modified, tree.
5488 */
89+ // To do: next major: remove `find` & `replace` combo, remove schema.
5590export const findAndReplace =
5691 /**
5792 * @type {(
58- * ((tree: Node , find: Find, replace?: Replace, options?: Options) => Node ) &
59- * ((tree: Node , schema: FindAndReplaceSchema| FindAndReplaceList, options?: Options) => Node )
93+ * (<Tree extends Node> (tree: Tree , find: Find, replace?: Replace | null | undefined , options?: Options | null | undefined ) => Tree ) &
94+ * (<Tree extends Node> (tree: Tree , schema: FindAndReplaceSchema | FindAndReplaceList, options?: Options | null | undefined ) => Tree )
6095 * )}
6196 **/
6297 (
6398 /**
64- * @param {Node } tree
65- * @param {Find|FindAndReplaceSchema|FindAndReplaceList } find
66- * @param {Replace|Options } [replace]
67- * @param {Options } [options]
99+ * @template {Node} Tree
100+ * @param {Tree } tree
101+ * @param {Find | FindAndReplaceSchema | FindAndReplaceList } find
102+ * @param {Replace | Options | null | undefined } [replace]
103+ * @param {Options | null | undefined } [options]
104+ * @returns {Tree }
68105 */
69106 function ( tree , find , replace , options ) {
70- /** @type {Options| undefined } */
107+ /** @type {Options | null | undefined } */
71108 let settings
72109 /** @type {FindAndReplaceSchema|FindAndReplaceList } */
73110 let schema
@@ -94,21 +131,22 @@ export const findAndReplace =
94131 visitParents ( tree , 'text' , visitor )
95132 }
96133
134+ // To do next major: don’t return the given tree.
97135 return tree
98136
99137 /** @type {import('unist-util-visit-parents/complex-types.js').BuildVisitor<Root, 'text'> } */
100138 function visitor ( node , parents ) {
101139 let index = - 1
102- /** @type {Parent| undefined } */
140+ /** @type {Parent | undefined } */
103141 let grandparent
104142
105143 while ( ++ index < parents . length ) {
106- const parent = /** @type { Parent } */ ( parents [ index ] )
144+ const parent = parents [ index ]
107145
108146 if (
109147 ignored (
110148 parent ,
111- // @ts -expect-error mdast vs. unist parent .
149+ // @ts -expect-error: TS doesn’t understand but it’s perfect .
112150 grandparent ? grandparent . children . indexOf ( parent ) : undefined ,
113151 grandparent
114152 )
@@ -120,15 +158,19 @@ export const findAndReplace =
120158 }
121159
122160 if ( grandparent ) {
123- // @ts -expect-error: stack is fine.
124161 return handler ( node , parents )
125162 }
126163 }
127164
128165 /**
166+ * Handle a text node which is not in an ignored parent.
167+ *
129168 * @param {Text } node
130- * @param {[Root, ...Array<Parent>] } parents
169+ * Text node.
170+ * @param {Array<Parent> } parents
171+ * Parents.
131172 * @returns {VisitorResult }
173+ * Result.
132174 */
133175 function handler ( node , parents ) {
134176 const parent = parents [ parents . length - 1 ]
@@ -140,19 +182,18 @@ export const findAndReplace =
140182 let change = false
141183 /** @type {Array<PhrasingContent> } */
142184 let nodes = [ ]
143- /** @type {number|undefined } */
144- let position
145185
146186 find . lastIndex = 0
147187
148188 let match = find . exec ( node . value )
149189
150190 while ( match ) {
151- position = match . index
191+ const position = match . index
152192 /** @type {RegExpMatchObject } */
153193 const matchObject = {
154194 index : match . index ,
155195 input : match . input ,
196+ // @ts -expect-error: stack is fine.
156197 stack : [ ...parents , node ]
157198 }
158199 let value = replace ( ...match , matchObject )
@@ -161,6 +202,7 @@ export const findAndReplace =
161202 value = value . length > 0 ? { type : 'text' , value} : undefined
162203 }
163204
205+ // It wasn’t a match after all.
164206 if ( value !== false ) {
165207 if ( start !== position ) {
166208 nodes . push ( {
@@ -202,8 +244,12 @@ export const findAndReplace =
202244 )
203245
204246/**
205- * @param {FindAndReplaceSchema|FindAndReplaceList } schema
247+ * Turn a schema into pairs.
248+ *
249+ * @param {FindAndReplaceSchema | FindAndReplaceList } schema
250+ * Schema.
206251 * @returns {Pairs }
252+ * Clean pairs.
207253 */
208254function toPairs ( schema ) {
209255 /** @type {Pairs } */
@@ -237,16 +283,24 @@ function toPairs(schema) {
237283}
238284
239285/**
286+ * Turn a find into an expression.
287+ *
240288 * @param {Find } find
289+ * Find.
241290 * @returns {RegExp }
291+ * Expression.
242292 */
243293function toExpression ( find ) {
244294 return typeof find === 'string' ? new RegExp ( escape ( find ) , 'g' ) : find
245295}
246296
247297/**
298+ * Turn a replace into a function.
299+ *
248300 * @param {Replace } replace
301+ * Replace.
249302 * @returns {ReplaceFunction }
303+ * Function.
250304 */
251305function toFunction ( replace ) {
252306 return typeof replace === 'function' ? replace : ( ) => replace
0 commit comments