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+ }
1581
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+ }
2389
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+ }
2794
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+ }
34101
35- return check . call ( context , node , index , parent )
36- }
102+ return check . call ( context , node , index , parent )
103+ }
104+ )
37105
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+ }
42128
43- if ( typeof test === 'string' ) {
44- return tagNameFactory ( test )
45- }
129+ if ( typeof test === 'string' ) {
130+ return tagNameFactory ( test )
131+ }
46132
47- if ( typeof test === 'object' ) {
48- return anyFactory ( test )
49- }
133+ if ( typeof test === 'object' ) {
134+ return anyFactory ( test )
135+ }
50136
51- if ( typeof test === 'function' ) {
52- return callFactory ( test )
53- }
137+ if ( typeof test === 'function' ) {
138+ return castFactory ( test )
139+ }
54140
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+ )
57144
145+ /**
146+ * @param {Array.<TagName|TestFunctionAnything> } tests
147+ * @returns {AssertPredicate<Element> }
148+ */
58149function anyFactory ( tests ) {
59- var index = - 1
150+ /** @type { Array.<AssertPredicate<Element>> } */
60151 var checks = [ ]
152+ var index = - 1
61153
62154 while ( ++ index < tests . length ) {
63155 checks [ index ] = convertElement ( tests [ index ] )
64156 }
65157
66- return any
158+ return castFactory ( any )
67159
160+ /**
161+ * @this {unknown}
162+ * @param {unknown[] } parameters
163+ * @returns {node is Element }
164+ */
68165 function any ( ...parameters ) {
69166 var index = - 1
70167
@@ -78,30 +175,55 @@ function anyFactory(tests) {
78175 }
79176}
80177
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 ) {
83186 return tagName
84187
188+ /**
189+ * @param {Node } node
190+ * @returns {node is Element }
191+ */
85192 function tagName ( node ) {
86- return element ( node ) && node . tagName === test
193+ return element ( node ) && node . tagName === check
87194 }
88195}
89196
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 ) )
96212 }
97213}
98214
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+ */
100220function element ( node ) {
101- return (
221+ return Boolean (
102222 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'
106228 )
107229}
0 commit comments