Skip to content

Commit f78c229

Browse files
authored
Add JSDoc based types
Closes GH-18. Reviewed-by: Christian Murphy <christian.murphy.42@gmail.com>
1 parent 131ee3d commit f78c229

File tree

8 files changed

+461
-170
lines changed

8 files changed

+461
-170
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
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.d.ts

Lines changed: 0 additions & 69 deletions
This file was deleted.

index.js

Lines changed: 212 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,242 @@
1-
// Assert if `test` passes for `node`.
2-
// When a `parent` node is known the `index` of node should also be given.
3-
// eslint-disable-next-line max-params
4-
export function is(node, test, index, parent, context) {
5-
var check = convert(test)
6-
7-
if (
8-
index !== undefined &&
9-
index !== null &&
10-
(typeof index !== 'number' ||
11-
index < 0 ||
12-
index === Number.POSITIVE_INFINITY)
13-
) {
14-
throw new Error('Expected positive finite index')
15-
}
16-
17-
if (
18-
parent !== undefined &&
19-
parent !== null &&
20-
(!is(parent) || !parent.children)
21-
) {
22-
throw new Error('Expected parent node')
23-
}
24-
25-
if (
26-
(parent === undefined || parent === null) !==
27-
(index === undefined || index === null)
28-
) {
29-
throw new Error('Expected both parent and index')
30-
}
31-
32-
return node && node.type && typeof node.type === 'string'
33-
? Boolean(check.call(context, node, index, parent))
34-
: false
35-
}
36-
37-
export function convert(test) {
38-
if (test === undefined || test === null) {
39-
return ok
40-
}
1+
/**
2+
* @typedef {import('unist').Node} Node
3+
* @typedef {import('unist').Parent} Parent
4+
*
5+
* @typedef {string} Type
6+
* @typedef {Object<string, unknown>} Props
7+
*/
8+
9+
/**
10+
* Check if a node passes a test
11+
*
12+
* @callback TestFunctionAnything
13+
* @param {Node} node
14+
* @param {number} [index]
15+
* @param {Parent} [parent]
16+
* @returns {boolean|void}
17+
*/
18+
19+
/**
20+
* Check if a node passes a certain node test
21+
*
22+
* @template {Node} X
23+
* @callback TestFunctionPredicate
24+
* @param {Node} node
25+
* @param {number} [index]
26+
* @param {Parent} [parent]
27+
* @returns {node is X}
28+
*/
29+
30+
/**
31+
* @callback AssertAnything
32+
* @param {unknown} [node]
33+
* @param {number} [index]
34+
* @param {Parent} [parent]
35+
* @returns {boolean}
36+
*/
37+
38+
/**
39+
* Check if a node passes a certain node test
40+
*
41+
* @template {Node} Y
42+
* @callback AssertPredicate
43+
* @param {unknown} [node]
44+
* @param {number} [index]
45+
* @param {Parent} [parent]
46+
* @returns {node is Y}
47+
*/
48+
49+
export var is =
50+
/**
51+
* Check if a node passes a test.
52+
* When a `parent` node is known the `index` of node should also be given.
53+
*
54+
* @type {(
55+
* (<T extends Node>(node: unknown, test: T['type']|Partial<T>|TestFunctionPredicate<T>|Array.<T['type']|Partial<T>|TestFunctionPredicate<T>>, index?: number, parent?: Parent, context?: unknown) => node is T) &
56+
* ((node?: unknown, test?: null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>, index?: number, parent?: Parent, context?: unknown) => boolean)
57+
* )}
58+
*/
59+
(
60+
/**
61+
* Check if a node passes a test.
62+
* When a `parent` node is known the `index` of node should also be given.
63+
*
64+
* @param {unknown} [node] Node to check
65+
* @param {null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>} [test]
66+
* When nullish, checks if `node` is a `Node`.
67+
* When `string`, works like passing `function (node) {return node.type === test}`.
68+
* When `function` checks if function passed the node is true.
69+
* When `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
70+
* When `array`, checks any one of the subtests pass.
71+
* @param {number} [index] Position of `node` in `parent`
72+
* @param {Parent} [parent] Parent of `node`
73+
* @param {unknown} [context] Context object to invoke `test` with
74+
* @returns {boolean} Whether test passed and `node` is a `Node` (object with `type` set to non-empty `string`).
75+
*/
76+
// eslint-disable-next-line max-params
77+
function is(node, test, index, parent, context) {
78+
var check = convert(test)
79+
80+
if (
81+
index !== undefined &&
82+
index !== null &&
83+
(typeof index !== 'number' ||
84+
index < 0 ||
85+
index === Number.POSITIVE_INFINITY)
86+
) {
87+
throw new Error('Expected positive finite index')
88+
}
4189

42-
if (typeof test === 'string') {
43-
return typeFactory(test)
44-
}
90+
if (
91+
parent !== undefined &&
92+
parent !== null &&
93+
(!is(parent) || !parent.children)
94+
) {
95+
throw new Error('Expected parent node')
96+
}
4597

46-
if (typeof test === 'object') {
47-
return 'length' in test ? anyFactory(test) : allFactory(test)
48-
}
98+
if (
99+
(parent === undefined || parent === null) !==
100+
(index === undefined || index === null)
101+
) {
102+
throw new Error('Expected both parent and index')
103+
}
49104

50-
if (typeof test === 'function') {
51-
return test
52-
}
105+
// @ts-ignore Looks like a node.
106+
return node && node.type && typeof node.type === 'string'
107+
? Boolean(check.call(context, node, index, parent))
108+
: false
109+
}
110+
)
111+
112+
export var convert =
113+
/**
114+
* @type {(
115+
* (<T extends Node>(test: T['type']|Partial<T>|TestFunctionPredicate<T>) => AssertPredicate<T>) &
116+
* ((test?: null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>) => AssertAnything)
117+
* )}
118+
*/
119+
(
120+
/**
121+
* Generate an assertion from a check.
122+
* @param {null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>} [test]
123+
* When nullish, checks if `node` is a `Node`.
124+
* When `string`, works like passing `function (node) {return node.type === test}`.
125+
* When `function` checks if function passed the node is true.
126+
* When `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
127+
* When `array`, checks any one of the subtests pass.
128+
* @returns {AssertAnything}
129+
*/
130+
function (test) {
131+
if (test === undefined || test === null) {
132+
return ok
133+
}
53134

54-
throw new Error('Expected function, string, or object as test')
55-
}
135+
if (typeof test === 'string') {
136+
return typeFactory(test)
137+
}
56138

57-
// Utility to assert each property in `test` is represented in `node`, and each
58-
// values are strictly equal.
59-
function allFactory(test) {
60-
return all
139+
if (typeof test === 'object') {
140+
// @ts-ignore looks like a list of tests / partial test object.
141+
return 'length' in test ? anyFactory(test) : propsFactory(test)
142+
}
61143

62-
function all(node) {
63-
var key
144+
if (typeof test === 'function') {
145+
return castFactory(test)
146+
}
64147

65-
for (key in test) {
66-
if (node[key] !== test[key]) return false
148+
throw new Error('Expected function, string, or object as test')
67149
}
68-
69-
return true
70-
}
71-
}
72-
150+
)
151+
/**
152+
* @param {Array.<Type|Props|TestFunctionAnything>} tests
153+
* @returns {AssertAnything}
154+
*/
73155
function anyFactory(tests) {
156+
/** @type {Array.<AssertAnything>} */
74157
var checks = []
75158
var index = -1
76159

77160
while (++index < tests.length) {
78161
checks[index] = convert(tests[index])
79162
}
80163

81-
return any
164+
return castFactory(any)
82165

166+
/**
167+
* @this {unknown}
168+
* @param {unknown[]} parameters
169+
* @returns {boolean}
170+
*/
83171
function any(...parameters) {
84172
var index = -1
85173

86174
while (++index < checks.length) {
87-
if (checks[index].call(this, ...parameters)) {
88-
return true
89-
}
175+
if (checks[index].call(this, ...parameters)) return true
90176
}
91-
92-
return false
93177
}
94178
}
95179

96-
// Utility to convert a string into a function which checks a given node’s type
97-
// for said string.
98-
function typeFactory(test) {
99-
return type
180+
/**
181+
* Utility to assert each property in `test` is represented in `node`, and each
182+
* values are strictly equal.
183+
*
184+
* @param {Props} check
185+
* @returns {AssertAnything}
186+
*/
187+
function propsFactory(check) {
188+
return castFactory(all)
189+
190+
/**
191+
* @param {Node} node
192+
* @returns {boolean}
193+
*/
194+
function all(node) {
195+
/** @type {string} */
196+
var key
197+
198+
for (key in check) {
199+
if (node[key] !== check[key]) return
200+
}
201+
202+
return true
203+
}
204+
}
100205

206+
/**
207+
* Utility to convert a string into a function which checks a given node’s type
208+
* for said string.
209+
*
210+
* @param {Type} check
211+
* @returns {AssertAnything}
212+
*/
213+
function typeFactory(check) {
214+
return castFactory(type)
215+
216+
/**
217+
* @param {Node} node
218+
*/
101219
function type(node) {
102-
return Boolean(node && node.type === test)
220+
return node && node.type === check
221+
}
222+
}
223+
224+
/**
225+
* Utility to convert a string into a function which checks a given node’s type
226+
* for said string.
227+
* @param {TestFunctionAnything} check
228+
* @returns {AssertAnything}
229+
*/
230+
function castFactory(check) {
231+
return assertion
232+
233+
/**
234+
* @this {unknown}
235+
* @param {Array.<unknown>} parameters
236+
* @returns {boolean}
237+
*/
238+
function assertion(...parameters) {
239+
return Boolean(check.call(this, ...parameters))
103240
}
104241
}
105242

0 commit comments

Comments
 (0)