Skip to content

Commit 306095d

Browse files
committed
Add JSDoc based types
1 parent 1dfbda9 commit 306095d

File tree

5 files changed

+147
-27
lines changed

5 files changed

+147
-27
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

+114-24
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,109 @@
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+
143
import escape from 'escape-string-regexp'
244
import {visitParents} from 'unist-util-visit-parents'
345
import {convert} from 'unist-util-is'
446

547
var own = {}.hasOwnProperty
6-
var splice = [].splice
748

49+
/**
50+
* @param {Node} tree
51+
* @param {Find|FindAndReplaceSchema|FindAndReplaceList} find
52+
* @param {Replace|Options} [replace]
53+
* @param {Options} [options]
54+
*/
855
export function findAndReplace(tree, find, replace, options) {
56+
/** @type {Options} */
957
var settings
58+
/** @type {FindAndReplaceSchema|FindAndReplaceList} */
1059
var schema
1160

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.
1363
schema = [[find, replace]]
64+
settings = options
1465
} else {
1566
schema = find
16-
options = replace
67+
// @ts-expect-error don’t expect replace twice.
68+
settings = replace
1769
}
1870

19-
settings = options || {}
71+
if (!settings) {
72+
settings = {}
73+
}
2074

2175
search(tree, settings, handlerFactory(toPairs(schema)))
2276

2377
return tree
2478

79+
/**
80+
* @param {Pairs} pairs
81+
* @returns {Handler}
82+
*/
2583
function handlerFactory(pairs) {
2684
var pair = pairs[0]
2785

2886
return handler
2987

88+
/**
89+
* @type {Handler}
90+
*/
3091
function handler(node, parent) {
3192
var find = pair[0]
3293
var replace = pair[1]
94+
/** @type {Array.<PhrasingContent>} */
3395
var nodes = []
3496
var start = 0
3597
var index = parent.children.indexOf(node)
98+
/** @type {number} */
3699
var position
100+
/** @type {RegExpMatchArray} */
37101
var match
102+
/** @type {Handler} */
38103
var subhandler
104+
/** @type {PhrasingContent} */
105+
var child
106+
/** @type {Array.<PhrasingContent>|PhrasingContent|string|false|undefined|null} */
39107
var value
40108

41109
find.lastIndex = 0
@@ -44,17 +112,18 @@ export function findAndReplace(tree, find, replace, options) {
44112

45113
while (match) {
46114
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+
}
48121

49122
if (value !== false) {
50123
if (start !== position) {
51124
nodes.push({type: 'text', value: node.value.slice(start, position)})
52125
}
53126

54-
if (typeof value === 'string' && value.length > 0) {
55-
value = {type: 'text', value}
56-
}
57-
58127
if (value) {
59128
nodes = [].concat(nodes, value)
60129
}
@@ -77,21 +146,20 @@ export function findAndReplace(tree, find, replace, options) {
77146
nodes.push({type: 'text', value: node.value.slice(start)})
78147
}
79148

80-
nodes.unshift(index, 1)
81-
splice.apply(parent.children, nodes)
149+
parent.children.splice(index, 1, ...nodes)
82150
}
83151

84152
if (pairs.length > 1) {
85153
subhandler = handlerFactory(pairs.slice(1))
86154
position = -1
87155

88156
while (++position < nodes.length) {
89-
node = nodes[position]
157+
child = nodes[position]
90158

91-
if (node.type === 'text') {
92-
subhandler(node, parent)
159+
if (child.type === 'text') {
160+
subhandler(child, parent)
93161
} else {
94-
search(node, settings, subhandler)
162+
search(child, settings, subhandler)
95163
}
96164
}
97165
}
@@ -101,25 +169,33 @@ export function findAndReplace(tree, find, replace, options) {
101169
}
102170
}
103171

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 || [])
107180

108181
visitParents(tree, 'text', visitor)
109182

110-
return result
111-
183+
/** @type {import('unist-util-visit-parents').Visitor<Text>} */
112184
function visitor(node, parents) {
113185
var index = -1
186+
/** @type {Parent} */
114187
var parent
188+
/** @type {Parent} */
115189
var grandparent
116190

117191
while (++index < parents.length) {
192+
// @ts-expect-error mdast vs. unist parent.
118193
parent = parents[index]
119194

120195
if (
121196
ignored(
122197
parent,
198+
// @ts-expect-error mdast vs. unist parent.
123199
grandparent ? grandparent.children.indexOf(parent) : undefined,
124200
grandparent
125201
)
@@ -134,18 +210,22 @@ function search(tree, settings, handler) {
134210
}
135211
}
136212

213+
/**
214+
* @param {FindAndReplaceSchema|FindAndReplaceList} schema
215+
* @returns {Pairs}
216+
*/
137217
function toPairs(schema) {
218+
var index = -1
219+
/** @type {Pairs} */
138220
var result = []
221+
/** @type {string} */
139222
var key
140-
var index
141223

142224
if (typeof schema !== 'object') {
143225
throw new TypeError('Expected array or object as schema')
144226
}
145227

146-
if ('length' in schema) {
147-
index = -1
148-
228+
if (Array.isArray(schema)) {
149229
while (++index < schema.length) {
150230
result.push([
151231
toExpression(schema[index][0]),
@@ -163,14 +243,24 @@ function toPairs(schema) {
163243
return result
164244
}
165245

246+
/**
247+
* @param {Find} find
248+
* @returns {RegExp}
249+
*/
166250
function toExpression(find) {
167251
return typeof find === 'string' ? new RegExp(escape(find), 'g') : find
168252
}
169253

254+
/**
255+
* @param {Replace} replace
256+
* @returns {ReplaceFunction}
257+
*/
170258
function toFunction(replace) {
171259
return typeof replace === 'function' ? replace : returner
172260

261+
/** @type {ReplaceFunction} */
173262
function returner() {
263+
// @ts-expect-error it’s a string.
174264
return replace
175265
}
176266
}

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
"prettier": "^2.0.0",
4043
"remark-cli": "^9.0.0",
4144
"remark-preset-wooorm": "^8.0.0",
45+
"rimraf": "^3.0.0",
4246
"tape": "^5.0.0",
47+
"type-coverage": "^2.0.0",
48+
"typescript": "^4.0.0",
4349
"unist-builder": "^3.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

+3-2
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$/,
@@ -121,13 +122,13 @@ test('findAndReplace', function (t) {
121122
t.deepEqual(
122123
findAndReplace(create(), {
123124
emphasis() {
124-
return u('link', [u('text', 'importance')])
125+
return u('link', {url: 'x'}, [u('text', 'importance')])
125126
},
126127
importance: 'something else'
127128
}),
128129
u('paragraph', [
129130
u('text', 'Some '),
130-
u('emphasis', [u('link', [u('text', 'something else')])]),
131+
u('emphasis', [u('link', {url: 'x'}, [u('text', 'something else')])]),
131132
u('text', ', '),
132133
u('strong', [u('text', 'something else')]),
133134
u('text', ', and '),

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)