Skip to content

Commit 83279aa

Browse files
committed
Add JSDoc based types
1 parent 5f2ca6e commit 83279aa

File tree

5 files changed

+157
-32
lines changed

5 files changed

+157
-32
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
.DS_Store
2+
*.d.ts
23
*.log
34
coverage/
45
node_modules/

index.js

+113-22
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,110 @@
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['children'][number]} Content
9+
* @typedef {Parent['children'][number]|Root} Node
10+
*
11+
* @typedef {import('hast-util-is-element').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 {...unknown} parameters
39+
* @returns {Array.<Content>|Content|string|false|undefined|null}
40+
*/
41+
142
import {visitParents} from 'unist-util-visit-parents'
243
import {convertElement} from 'hast-util-is-element'
344
import escape from 'escape-string-regexp'
445

5-
var splice = [].splice
646
var own = {}.hasOwnProperty
747

848
export const defaultIgnore = ['title', 'script', 'style', 'svg', 'math']
949

50+
/**
51+
* @param {Node} tree
52+
* @param {Find|FindAndReplaceSchema|FindAndReplaceList} find
53+
* @param {Replace|Options} [replace]
54+
* @param {Options} [options]
55+
*/
1056
export function findAndReplace(tree, find, replace, options) {
57+
/** @type {Options} */
1158
var settings
59+
/** @type {FindAndReplaceSchema|FindAndReplaceList} */
1260
var schema
1361

14-
if (typeof find === 'string' || (find && typeof find.exec === 'function')) {
62+
if (typeof find === 'string' || find instanceof RegExp) {
63+
// @ts-expect-error don’t expect options twice.
1564
schema = [[find, replace]]
65+
settings = options
1666
} else {
1767
schema = find
18-
options = replace
68+
// @ts-expect-error don’t expect replace twice.
69+
settings = replace
1970
}
2071

21-
settings = options || {}
72+
if (!settings) {
73+
settings = {}
74+
}
2275

2376
search(tree, settings, handlerFactory(toPairs(schema)))
2477

2578
return tree
2679

80+
/**
81+
* @param {Pairs} pairs
82+
* @returns {Handler}
83+
*/
2784
function handlerFactory(pairs) {
2885
var pair = pairs[0]
2986

3087
return handler
3188

89+
/**
90+
* @type {Handler}
91+
*/
3292
function handler(node, parent) {
3393
var find = pair[0]
3494
var replace = pair[1]
95+
/** @type {Array.<Content>} */
3596
var nodes = []
3697
var start = 0
3798
var index = parent.children.indexOf(node)
99+
/** @type {number} */
38100
var position
101+
/** @type {RegExpMatchArray} */
39102
var match
103+
/** @type {Handler} */
40104
var subhandler
105+
/** @type {Content} */
106+
var child
107+
/** @type {Array.<Content>|Content|string|false|undefined|null} */
41108
var value
42109

43110
find.lastIndex = 0
@@ -46,19 +113,20 @@ export function findAndReplace(tree, find, replace, options) {
46113

47114
while (match) {
48115
position = match.index
116+
// @ts-expect-error this is perfectly fine, typescript.
49117
value = replace(...match, {index: match.index, input: match.input})
50118

119+
if (typeof value === 'string' && value.length > 0) {
120+
value = {type: 'text', value}
121+
}
122+
51123
if (value !== false) {
52124
if (start !== position) {
53125
nodes.push({type: 'text', value: node.value.slice(start, position)})
54126
}
55127

56-
if (typeof value === 'string' && value.length > 0) {
57-
value = {type: 'text', value}
58-
}
59-
60128
if (value) {
61-
nodes.push(value)
129+
nodes = [].concat(nodes, value)
62130
}
63131

64132
start = position + match[0].length
@@ -79,21 +147,22 @@ export function findAndReplace(tree, find, replace, options) {
79147
nodes.push({type: 'text', value: node.value.slice(start)})
80148
}
81149

82-
nodes.unshift(index, 1)
83-
splice.apply(parent.children, nodes)
150+
// @ts-expect-error This is a bug!
151+
nodes = [index, 1, ...nodes]
152+
;[].splice.call(parent.children, ...nodes)
84153
}
85154

86155
if (pairs.length > 1) {
87156
subhandler = handlerFactory(pairs.slice(1))
88157
position = -1
89158

90159
while (++position < nodes.length) {
91-
node = nodes[position]
160+
child = nodes[position]
92161

93-
if (node.type === 'text') {
94-
subhandler(node, parent)
162+
if (child.type === 'text') {
163+
subhandler(child, parent)
95164
} else {
96-
search(node, settings, subhandler)
165+
search(child, settings, subhandler)
97166
}
98167
}
99168
}
@@ -103,25 +172,33 @@ export function findAndReplace(tree, find, replace, options) {
103172
}
104173
}
105174

175+
/**
176+
* @param {Node} tree
177+
* @param {Options} options
178+
* @param {Handler} handler
179+
* @returns {void}
180+
*/
106181
function search(tree, options, handler) {
107182
var ignored = convertElement(options.ignore || defaultIgnore)
108-
var result = []
109183

110184
visitParents(tree, 'text', visitor)
111185

112-
return result
113-
186+
/** @type {import('unist-util-visit-parents').Visitor<Text>} */
114187
function visitor(node, parents) {
115188
var index = -1
189+
/** @type {Parent} */
116190
var parent
191+
/** @type {Parent} */
117192
var grandparent
118193

119194
while (++index < parents.length) {
195+
// @ts-expect-error hast vs. unist parent.
120196
parent = parents[index]
121197

122198
if (
123199
ignored(
124200
parent,
201+
// @ts-expect-error hast vs. unist parent.
125202
grandparent ? grandparent.children.indexOf(parent) : undefined,
126203
grandparent
127204
)
@@ -136,18 +213,22 @@ function search(tree, options, handler) {
136213
}
137214
}
138215

216+
/**
217+
* @param {FindAndReplaceSchema|FindAndReplaceList} schema
218+
* @returns {Pairs}
219+
*/
139220
function toPairs(schema) {
221+
var index = -1
222+
/** @type {Pairs} */
140223
var result = []
224+
/** @type {string} */
141225
var key
142-
var index
143226

144227
if (typeof schema !== 'object') {
145228
throw new TypeError('Expected array or object as schema')
146229
}
147230

148-
if ('length' in schema) {
149-
index = -1
150-
231+
if (Array.isArray(schema)) {
151232
while (++index < schema.length) {
152233
result.push([
153234
toExpression(schema[index][0]),
@@ -165,14 +246,24 @@ function toPairs(schema) {
165246
return result
166247
}
167248

249+
/**
250+
* @param {Find} find
251+
* @returns {RegExp}
252+
*/
168253
function toExpression(find) {
169254
return typeof find === 'string' ? new RegExp(escape(find), 'g') : find
170255
}
171256

257+
/**
258+
* @param {Replace} replace
259+
* @returns {ReplaceFunction}
260+
*/
172261
function toFunction(replace) {
173262
return typeof replace === 'function' ? replace : returner
174263

264+
/** @type {ReplaceFunction} */
175265
function returner() {
266+
// @ts-expect-error it’s a string.
176267
return replace
177268
}
178269
}

package.json

+14-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
"sideEffects": false,
2727
"type": "module",
2828
"main": "index.js",
29+
"types": "index.d.ts",
2930
"files": [
31+
"index.d.ts",
3032
"index.js"
3133
],
3234
"dependencies": {
@@ -35,19 +37,25 @@
3537
"unist-util-visit-parents": "^4.0.0"
3638
},
3739
"devDependencies": {
40+
"@types/tape": "^4.0.0",
3841
"c8": "^7.0.0",
3942
"hastscript": "^7.0.0",
4043
"prettier": "^2.0.0",
4144
"remark-cli": "^9.0.0",
4245
"remark-preset-wooorm": "^8.0.0",
46+
"rimraf": "^3.0.0",
4347
"tape": "^5.0.0",
48+
"type-coverage": "^2.0.0",
49+
"typescript": "^4.0.0",
4450
"xo": "^0.39.0"
4551
},
4652
"scripts": {
53+
"prepack": "npm run build && npm run format",
54+
"build": "rimraf \"*.d.ts\" && tsc && type-coverage",
4755
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
4856
"test-api": "node test.js",
4957
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js",
50-
"test": "npm run format && npm run test-coverage"
58+
"test": "npm run build && npm run format && npm run test-coverage"
5159
},
5260
"prettier": {
5361
"tabWidth": 2,
@@ -68,5 +76,10 @@
6876
"plugins": [
6977
"preset-wooorm"
7078
]
79+
},
80+
"typeCoverage": {
81+
"atLeast": 100,
82+
"detail": true,
83+
"strict": true
7184
}
7285
}

test.js

+14-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {findAndReplace} from './index.js'
55
test('findAndReplace', function (t) {
66
t.throws(
77
function () {
8+
// @ts-expect-error runtime.
89
findAndReplace(create(), true)
910
},
1011
/^TypeError: Expected array or object as schema$/,
@@ -40,9 +41,13 @@ test('findAndReplace', function (t) {
4041
)
4142

4243
t.deepEqual(
43-
findAndReplace(create(), /em(\w+)is/, function ($0, $1) {
44-
return '[' + $1 + ']'
45-
}),
44+
findAndReplace(
45+
create(),
46+
/em(\w+)is/,
47+
function (/** @type {string} */ _, /** @type {string} */ $1) {
48+
return '[' + $1 + ']'
49+
}
50+
),
4651
h('p', [
4752
'Some ',
4853
h('em', '[phas]'),
@@ -172,13 +177,13 @@ test('findAndReplace', function (t) {
172177

173178
t.deepEqual(
174179
findAndReplace(h('p', 'Some emphasis, importance, and code.'), {
175-
importance(match) {
180+
importance(/** @type {string} */ match) {
176181
return h('strong', match)
177182
},
178-
code(match) {
183+
code(/** @type {string} */ match) {
179184
return h('code', match)
180185
},
181-
emphasis(match) {
186+
emphasis(/** @type {string} */ match) {
182187
return h('em', match)
183188
}
184189
}),
@@ -190,19 +195,19 @@ test('findAndReplace', function (t) {
190195
findAndReplace(h('p', 'Some emphasis, importance, and code.'), [
191196
[
192197
/importance/g,
193-
function (match) {
198+
function (/** @type {string} */ match) {
194199
return h('strong', match)
195200
}
196201
],
197202
[
198203
/code/g,
199-
function (match) {
204+
function (/** @type {string} */ match) {
200205
return h('code', match)
201206
}
202207
],
203208
[
204209
/emphasis/g,
205-
function (match) {
210+
function (/** @type {string} */ match) {
206211
return h('em', match)
207212
}
208213
]

tsconfig.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"include": ["*.js"],
3+
"compilerOptions": {
4+
"target": "ES2020",
5+
"lib": ["ES2020"],
6+
"module": "ES2020",
7+
"moduleResolution": "node",
8+
"allowJs": true,
9+
"checkJs": true,
10+
"declaration": true,
11+
"emitDeclarationOnly": true,
12+
"allowSyntheticDefaultImports": true,
13+
"skipLibCheck": true
14+
}
15+
}

0 commit comments

Comments
 (0)