1
- // Check if if `node` is an `element` and whether it passes the given test.
2
- // eslint-disable-next-line max-params
3
- export function isElement ( node , test , index , parent , context ) {
4
- var check = convertElement ( test )
5
-
6
- if (
7
- index !== undefined &&
8
- index !== null &&
9
- ( typeof index !== 'number' ||
10
- index < 0 ||
11
- index === Number . POSITIVE_INFINITY )
12
- ) {
13
- throw new Error ( 'Expected positive finite index for child node' )
14
- }
1
+ /**
2
+ * @typedef {import('unist').Node } Node
3
+ * @typedef {import('unist').Parent } Parent
4
+ * @typedef {import('hast').Element } Element
5
+ *
6
+ * @typedef {string } TagName
7
+ */
8
+
9
+ /**
10
+ * Check if an element passes a test
11
+ *
12
+ * @callback TestFunctionAnything
13
+ * @param {Element } element
14
+ * @param {number } [index]
15
+ * @param {Parent } [parent]
16
+ * @returns {boolean|void }
17
+ */
18
+
19
+ /**
20
+ * Check if an element passes a certain node test
21
+ *
22
+ * @template {Element} X
23
+ * @callback TestFunctionPredicate
24
+ * @param {X } element
25
+ * @param {number } [index]
26
+ * @param {Parent } [parent]
27
+ * @returns {element is X }
28
+ */
29
+
30
+ /**
31
+ * Check if a node is an element and passes a certain node test
32
+ *
33
+ * @template {Element} Y
34
+ * @callback AssertPredicate
35
+ * @param {unknown } [node]
36
+ * @param {number } [index]
37
+ * @param {Parent } [parent]
38
+ * @returns {node is Y }
39
+ */
40
+
41
+ // Check if `node` is an `element` and whether it passes the given test.
42
+ export const isElement =
43
+ /**
44
+ * Check if a node is an element and passes a test.
45
+ * When a `parent` node is known the `index` of node should also be given.
46
+ *
47
+ * @type {(
48
+ * (<T extends Element>(node: unknown, test: T['tagName']|TestFunctionPredicate<T>|Array.<T['tagName']|TestFunctionPredicate<T>>, index?: number, parent?: Parent, context?: unknown) => node is T) &
49
+ * ((node?: unknown, test?: null|undefined|TagName|TestFunctionAnything|Array.<TagName|TestFunctionAnything>, index?: number, parent?: Parent, context?: unknown) => node is Element)
50
+ * )}
51
+ */
52
+ (
53
+ /**
54
+ * Check if a node passes a test.
55
+ * When a `parent` node is known the `index` of node should also be given.
56
+ *
57
+ * @param {unknown } [node] Node to check
58
+ * @param {null|undefined|TagName|TestFunctionAnything|Array.<TagName|TestFunctionAnything> } [test]
59
+ * When nullish, checks if `node` is a `Node`.
60
+ * When `string`, works like passing `function (node) {return node.type === test}`.
61
+ * When `function` checks if function passed the node is true.
62
+ * When `array`, checks any one of the subtests pass.
63
+ * @param {number } [index] Position of `node` in `parent`
64
+ * @param {Parent } [parent] Parent of `node`
65
+ * @param {unknown } [context] Context object to invoke `test` with
66
+ * @returns {boolean } Whether test passed and `node` is an `Element` (object with `type` set to `element` and `tagName` set to a non-empty string).
67
+ */
68
+ // eslint-disable-next-line max-params
69
+ function ( node , test , index , parent , context ) {
70
+ var check = convertElement ( test )
71
+
72
+ if (
73
+ index !== undefined &&
74
+ index !== null &&
75
+ ( typeof index !== 'number' ||
76
+ index < 0 ||
77
+ index === Number . POSITIVE_INFINITY )
78
+ ) {
79
+ throw new Error ( 'Expected positive finite index for child node' )
80
+ }
15
81
16
- if (
17
- parent !== undefined &&
18
- parent !== null &&
19
- ( ! parent . type || ! parent . children )
20
- ) {
21
- throw new Error ( 'Expected parent node' )
22
- }
82
+ if (
83
+ parent !== undefined &&
84
+ parent !== null &&
85
+ ( ! parent . type || ! parent . children )
86
+ ) {
87
+ throw new Error ( 'Expected parent node' )
88
+ }
23
89
24
- if ( ! node || ! node . type || typeof node . type !== 'string' ) {
25
- return false
26
- }
90
+ // @ts -ignore Looks like a node.
91
+ if ( ! node || ! node . type || typeof node . type !== 'string' ) {
92
+ return false
93
+ }
27
94
28
- if (
29
- ( parent === undefined || parent === null ) !==
30
- ( index === undefined || index === null )
31
- ) {
32
- throw new Error ( 'Expected both parent and index' )
33
- }
95
+ if (
96
+ ( parent === undefined || parent === null ) !==
97
+ ( index === undefined || index === null )
98
+ ) {
99
+ throw new Error ( 'Expected both parent and index' )
100
+ }
34
101
35
- return check . call ( context , node , index , parent )
36
- }
102
+ return check . call ( context , node , index , parent )
103
+ }
104
+ )
37
105
38
- export function convertElement ( test ) {
39
- if ( test === undefined || test === null ) {
40
- return element
41
- }
106
+ export const convertElement =
107
+ /**
108
+ * @type {(
109
+ * (<T extends Element>(test: T['tagName']|TestFunctionPredicate<T>) => AssertPredicate<T>) &
110
+ * ((test?: null|undefined|TagName|TestFunctionAnything|Array.<TagName|TestFunctionAnything>) => AssertPredicate<Element>)
111
+ * )}
112
+ */
113
+ (
114
+ /**
115
+ * Generate an assertion from a check.
116
+ * @param {null|undefined|TagName|TestFunctionAnything|Array.<TagName|TestFunctionAnything> } [test]
117
+ * When nullish, checks if `node` is a `Node`.
118
+ * When `string`, works like passing `function (node) {return node.type === test}`.
119
+ * When `function` checks if function passed the node is true.
120
+ * When `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
121
+ * When `array`, checks any one of the subtests pass.
122
+ * @returns {AssertPredicate<Element> }
123
+ */
124
+ function ( test ) {
125
+ if ( test === undefined || test === null ) {
126
+ return element
127
+ }
42
128
43
- if ( typeof test === 'string' ) {
44
- return tagNameFactory ( test )
45
- }
129
+ if ( typeof test === 'string' ) {
130
+ return tagNameFactory ( test )
131
+ }
46
132
47
- if ( typeof test === 'object' ) {
48
- return anyFactory ( test )
49
- }
133
+ if ( typeof test === 'object' ) {
134
+ return anyFactory ( test )
135
+ }
50
136
51
- if ( typeof test === 'function' ) {
52
- return callFactory ( test )
53
- }
137
+ if ( typeof test === 'function' ) {
138
+ return castFactory ( test )
139
+ }
54
140
55
- throw new Error ( 'Expected function, string, or array as test' )
56
- }
141
+ throw new Error ( 'Expected function, string, or array as test' )
142
+ }
143
+ )
57
144
145
+ /**
146
+ * @param {Array.<TagName|TestFunctionAnything> } tests
147
+ * @returns {AssertPredicate<Element> }
148
+ */
58
149
function anyFactory ( tests ) {
59
- var index = - 1
150
+ /** @type { Array.<AssertPredicate<Element>> } */
60
151
var checks = [ ]
152
+ var index = - 1
61
153
62
154
while ( ++ index < tests . length ) {
63
155
checks [ index ] = convertElement ( tests [ index ] )
64
156
}
65
157
66
- return any
158
+ return castFactory ( any )
67
159
160
+ /**
161
+ * @this {unknown}
162
+ * @param {unknown[] } parameters
163
+ * @returns {node is Element }
164
+ */
68
165
function any ( ...parameters ) {
69
166
var index = - 1
70
167
@@ -78,30 +175,55 @@ function anyFactory(tests) {
78
175
}
79
176
}
80
177
81
- // Utility to convert a string a tag name check.
82
- function tagNameFactory ( test ) {
178
+ /**
179
+ * Utility to convert a string into a function which checks a given node’s tag
180
+ * name for said string.
181
+ *
182
+ * @param {TagName } check
183
+ * @returns {AssertPredicate<Element> }
184
+ */
185
+ function tagNameFactory ( check ) {
83
186
return tagName
84
187
188
+ /**
189
+ * @param {Node } node
190
+ * @returns {node is Element }
191
+ */
85
192
function tagName ( node ) {
86
- return element ( node ) && node . tagName === test
193
+ return element ( node ) && node . tagName === check
87
194
}
88
195
}
89
196
90
- // Utility to convert a function check.
91
- function callFactory ( test ) {
92
- return call
93
-
94
- function call ( node , ...parameters ) {
95
- return element ( node ) && Boolean ( test . call ( this , node , ...parameters ) )
197
+ /**
198
+ * @param {TestFunctionAnything } check
199
+ * @returns {AssertPredicate<Element> }
200
+ */
201
+ function castFactory ( check ) {
202
+ return assertion
203
+
204
+ /**
205
+ * @this {unknown}
206
+ * @param {Node } node
207
+ * @param {Array.<unknown> } parameters
208
+ * @returns {node is Element }
209
+ */
210
+ function assertion ( node , ...parameters ) {
211
+ return element ( node ) && Boolean ( check . call ( this , node , ...parameters ) )
96
212
}
97
213
}
98
214
99
- // Utility to return true if this is an element.
215
+ /**
216
+ * Utility to return true if this is an element.
217
+ * @param {unknown } node
218
+ * @returns {node is Element }
219
+ */
100
220
function element ( node ) {
101
- return (
221
+ return Boolean (
102
222
node &&
103
- typeof node === 'object' &&
104
- node . type === 'element' &&
105
- typeof node . tagName === 'string'
223
+ typeof node === 'object' &&
224
+ // @ts -ignore Looks like a node.
225
+ node . type === 'element' &&
226
+ // @ts -ignore Looks like an element.
227
+ typeof node . tagName === 'string'
106
228
)
107
229
}
0 commit comments