|
1 | 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} Element |
9 |
| - * @typedef {import('hast').Content} Content |
10 |
| - * @typedef {Root|Content} Node |
11 |
| - * |
12 |
| - * @typedef {import('hast-util-is-element').Test} Test |
13 |
| - * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult |
14 |
| - * |
15 |
| - * @typedef RegExpMatchObject |
16 |
| - * @property {number} index |
17 |
| - * @property {string} input |
18 |
| - * |
19 |
| - * @typedef {string|RegExp} Find |
20 |
| - * @typedef {string|ReplaceFunction} Replace |
21 |
| - * |
22 |
| - * @typedef {[Find, Replace]} FindAndReplaceTuple |
23 |
| - * @typedef {Record<string, Replace>} FindAndReplaceSchema |
24 |
| - * @typedef {Array<FindAndReplaceTuple>} FindAndReplaceList |
25 |
| - * |
26 |
| - * @typedef {[RegExp, ReplaceFunction]} Pair |
27 |
| - * @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 |
28 | 10 | */
|
29 | 11 |
|
30 |
| -/** |
31 |
| - * @callback ReplaceFunction |
32 |
| - * @param {...any} parameters |
33 |
| - * @returns {Array<Content>|Content|string|false|undefined|null} |
34 |
| - */ |
35 |
| - |
36 |
| -import {visitParents} from 'unist-util-visit-parents' |
37 |
| -import {convertElement} from 'hast-util-is-element' |
38 |
| -import escape from 'escape-string-regexp' |
39 |
| - |
40 |
| -const own = {}.hasOwnProperty |
41 |
| - |
42 |
| -export const defaultIgnore = ['title', 'script', 'style', 'svg', 'math'] |
43 |
| - |
44 |
| -/** |
45 |
| - * @param {Node} tree |
46 |
| - * @param {Find|FindAndReplaceSchema|FindAndReplaceList} find |
47 |
| - * @param {Replace|Options} [replace] |
48 |
| - * @param {Options} [options] |
49 |
| - */ |
50 |
| -export function findAndReplace(tree, find, replace, options) { |
51 |
| - /** @type {Options|undefined} */ |
52 |
| - let settings |
53 |
| - /** @type {FindAndReplaceSchema|FindAndReplaceList} */ |
54 |
| - let schema |
55 |
| - |
56 |
| - if (typeof find === 'string' || find instanceof RegExp) { |
57 |
| - // @ts-expect-error don’t expect options twice. |
58 |
| - schema = [[find, replace]] |
59 |
| - settings = options |
60 |
| - } else { |
61 |
| - schema = find |
62 |
| - // @ts-expect-error don’t expect replace twice. |
63 |
| - settings = replace |
64 |
| - } |
65 |
| - |
66 |
| - if (!settings) { |
67 |
| - settings = {} |
68 |
| - } |
69 |
| - |
70 |
| - const ignored = convertElement(settings.ignore || defaultIgnore) |
71 |
| - const pairs = toPairs(schema) |
72 |
| - let pairIndex = -1 |
73 |
| - |
74 |
| - while (++pairIndex < pairs.length) { |
75 |
| - visitParents(tree, 'text', visitor) |
76 |
| - } |
77 |
| - |
78 |
| - return tree |
79 |
| - |
80 |
| - /** @type {import('unist-util-visit-parents/complex-types').BuildVisitor<Node, 'text'>} */ |
81 |
| - function visitor(node, parents) { |
82 |
| - let index = -1 |
83 |
| - /** @type {Root|Element|undefined} */ |
84 |
| - let grandparent |
85 |
| - |
86 |
| - while (++index < parents.length) { |
87 |
| - const parent = parents[index] |
88 |
| - |
89 |
| - if ( |
90 |
| - ignored( |
91 |
| - parent, |
92 |
| - // @ts-expect-error: TS doesn’t understand but it’s perfect. |
93 |
| - grandparent ? grandparent.children.indexOf(parent) : undefined, |
94 |
| - grandparent |
95 |
| - ) |
96 |
| - ) { |
97 |
| - return |
98 |
| - } |
99 |
| - |
100 |
| - grandparent = parent |
101 |
| - } |
102 |
| - |
103 |
| - if (grandparent) { |
104 |
| - return handler(node, grandparent) |
105 |
| - } |
106 |
| - } |
107 |
| - |
108 |
| - /** |
109 |
| - * @param {Text} node |
110 |
| - * @param {Parent} parent |
111 |
| - * @returns {VisitorResult} |
112 |
| - */ |
113 |
| - function handler(node, parent) { |
114 |
| - const find = pairs[pairIndex][0] |
115 |
| - const replace = pairs[pairIndex][1] |
116 |
| - let start = 0 |
117 |
| - let index = parent.children.indexOf(node) |
118 |
| - /** @type {Array<Content>} */ |
119 |
| - let nodes = [] |
120 |
| - /** @type {number|undefined} */ |
121 |
| - let position |
122 |
| - |
123 |
| - find.lastIndex = 0 |
124 |
| - |
125 |
| - let match = find.exec(node.value) |
126 |
| - |
127 |
| - while (match) { |
128 |
| - position = match.index |
129 |
| - let value = replace(...match, {index: match.index, input: match.input}) |
130 |
| - |
131 |
| - if (typeof value === 'string') { |
132 |
| - value = value.length > 0 ? {type: 'text', value} : undefined |
133 |
| - } |
134 |
| - |
135 |
| - if (value !== false) { |
136 |
| - if (start !== position) { |
137 |
| - nodes.push({type: 'text', value: node.value.slice(start, position)}) |
138 |
| - } |
139 |
| - |
140 |
| - if (Array.isArray(value)) { |
141 |
| - nodes.push(...value) |
142 |
| - } else if (value) { |
143 |
| - nodes.push(value) |
144 |
| - } |
145 |
| - |
146 |
| - start = position + match[0].length |
147 |
| - } |
148 |
| - |
149 |
| - if (!find.global) { |
150 |
| - break |
151 |
| - } |
152 |
| - |
153 |
| - match = find.exec(node.value) |
154 |
| - } |
155 |
| - |
156 |
| - if (position === undefined) { |
157 |
| - nodes = [node] |
158 |
| - index-- |
159 |
| - } else { |
160 |
| - if (start < node.value.length) { |
161 |
| - nodes.push({type: 'text', value: node.value.slice(start)}) |
162 |
| - } |
163 |
| - |
164 |
| - parent.children.splice(index, 1, ...nodes) |
165 |
| - } |
166 |
| - |
167 |
| - return index + nodes.length + 1 |
168 |
| - } |
169 |
| -} |
170 |
| - |
171 |
| -/** |
172 |
| - * @param {FindAndReplaceSchema|FindAndReplaceList} schema |
173 |
| - * @returns {Pairs} |
174 |
| - */ |
175 |
| -function toPairs(schema) { |
176 |
| - /** @type {Pairs} */ |
177 |
| - const result = [] |
178 |
| - |
179 |
| - if (typeof schema !== 'object') { |
180 |
| - throw new TypeError('Expected array or object as schema') |
181 |
| - } |
182 |
| - |
183 |
| - if (Array.isArray(schema)) { |
184 |
| - let index = -1 |
185 |
| - |
186 |
| - while (++index < schema.length) { |
187 |
| - result.push([ |
188 |
| - toExpression(schema[index][0]), |
189 |
| - toFunction(schema[index][1]) |
190 |
| - ]) |
191 |
| - } |
192 |
| - } else { |
193 |
| - /** @type {string} */ |
194 |
| - let key |
195 |
| - |
196 |
| - for (key in schema) { |
197 |
| - if (own.call(schema, key)) { |
198 |
| - result.push([toExpression(key), toFunction(schema[key])]) |
199 |
| - } |
200 |
| - } |
201 |
| - } |
202 |
| - |
203 |
| - return result |
204 |
| -} |
205 |
| - |
206 |
| -/** |
207 |
| - * @param {Find} find |
208 |
| - * @returns {RegExp} |
209 |
| - */ |
210 |
| -function toExpression(find) { |
211 |
| - return typeof find === 'string' ? new RegExp(escape(find), 'g') : find |
212 |
| -} |
213 |
| - |
214 |
| -/** |
215 |
| - * @param {Replace} replace |
216 |
| - * @returns {ReplaceFunction} |
217 |
| - */ |
218 |
| -function toFunction(replace) { |
219 |
| - return typeof replace === 'function' ? replace : () => replace |
220 |
| -} |
| 12 | +export {defaultIgnore, findAndReplace} from './lib/index.js' |
0 commit comments