|
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 | + } |
41 | 89 |
|
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 | + } |
45 | 97 |
|
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 | + } |
49 | 104 |
|
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 | + } |
53 | 134 |
|
54 |
| - throw new Error('Expected function, string, or object as test') |
55 |
| -} |
| 135 | + if (typeof test === 'string') { |
| 136 | + return typeFactory(test) |
| 137 | + } |
56 | 138 |
|
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 | + } |
61 | 143 |
|
62 |
| - function all(node) { |
63 |
| - var key |
| 144 | + if (typeof test === 'function') { |
| 145 | + return castFactory(test) |
| 146 | + } |
64 | 147 |
|
65 |
| - for (key in test) { |
66 |
| - if (node[key] !== test[key]) return false |
| 148 | + throw new Error('Expected function, string, or object as test') |
67 | 149 | }
|
68 |
| - |
69 |
| - return true |
70 |
| - } |
71 |
| -} |
72 |
| - |
| 150 | + ) |
| 151 | +/** |
| 152 | + * @param {Array.<Type|Props|TestFunctionAnything>} tests |
| 153 | + * @returns {AssertAnything} |
| 154 | + */ |
73 | 155 | function anyFactory(tests) {
|
| 156 | + /** @type {Array.<AssertAnything>} */ |
74 | 157 | var checks = []
|
75 | 158 | var index = -1
|
76 | 159 |
|
77 | 160 | while (++index < tests.length) {
|
78 | 161 | checks[index] = convert(tests[index])
|
79 | 162 | }
|
80 | 163 |
|
81 |
| - return any |
| 164 | + return castFactory(any) |
82 | 165 |
|
| 166 | + /** |
| 167 | + * @this {unknown} |
| 168 | + * @param {unknown[]} parameters |
| 169 | + * @returns {boolean} |
| 170 | + */ |
83 | 171 | function any(...parameters) {
|
84 | 172 | var index = -1
|
85 | 173 |
|
86 | 174 | while (++index < checks.length) {
|
87 |
| - if (checks[index].call(this, ...parameters)) { |
88 |
| - return true |
89 |
| - } |
| 175 | + if (checks[index].call(this, ...parameters)) return true |
90 | 176 | }
|
91 |
| - |
92 |
| - return false |
93 | 177 | }
|
94 | 178 | }
|
95 | 179 |
|
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 | +} |
100 | 205 |
|
| 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 | + */ |
101 | 219 | 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)) |
103 | 240 | }
|
104 | 241 | }
|
105 | 242 |
|
|
0 commit comments