|
1 | 1 | /**
|
2 |
| - * @typedef {import('assert').AssertionError} AssertionError |
3 |
| - * @typedef {import('unist').Node} Node |
4 |
| - * @typedef {import('unist').Parent} Parent |
5 |
| - * @typedef {import('unist').Literal} Literal |
6 |
| - * @typedef {import('unist').Position} Position |
7 |
| - * @typedef {import('unist').Point} Point |
8 |
| - * @typedef {Node & {children: never, value: never}} _Void |
9 |
| - * |
10 |
| - * @typedef SeenErrorFields |
11 |
| - * @property {true} [__unist__] |
12 |
| - * |
13 |
| - * @typedef {Error & SeenErrorFields} SeenError |
| 2 | + * @typedef {import('./lib/index.js').AssertionError} AssertionError |
14 | 3 | */
|
15 | 4 |
|
16 |
| -import nodeAssert from 'node:assert' |
17 |
| -import {inspect} from './inspect.js' |
18 |
| - |
19 |
| -const own = {}.hasOwnProperty |
20 |
| - |
21 |
| -/** |
22 |
| - * Assert that `tree` is a valid unist node. |
23 |
| - * |
24 |
| - * If `node` is a parent, all children will be asserted too. |
25 |
| - * |
26 |
| - * @param {unknown} [tree] |
27 |
| - * Thing to assert. |
28 |
| - * @param {Parent | null | undefined} [parent] |
29 |
| - * Optional, valid parent. |
30 |
| - * @returns {asserts tree is Node} |
31 |
| - * Whether `tree` (and its descendants) are valid nodes. |
32 |
| - * @throws {AssertionError} |
33 |
| - * When `tree` (or its descendants) is not a node. |
34 |
| - */ |
35 |
| -export function assert(tree, parent) { |
36 |
| - return wrap(assertNode)(tree, parent) |
37 |
| -} |
38 |
| - |
39 |
| -/** |
40 |
| - * Assert that `tree` is a valid unist parent. |
41 |
| - * |
42 |
| - * All children will be asserted too. |
43 |
| - * |
44 |
| - * @param {unknown} [tree] |
45 |
| - * Thing to assert. |
46 |
| - * @param {Parent | null | undefined} [parent] |
47 |
| - * Optional, valid parent. |
48 |
| - * @returns {asserts tree is Parent} |
49 |
| - * Whether `tree` is a parent and its descendants are valid nodes. |
50 |
| - * @throws {AssertionError} |
51 |
| - * When `tree` is not a parent or its descendants are not nodes. |
52 |
| - */ |
53 |
| -export function parent(tree, parent) { |
54 |
| - return wrap(assertParent)(tree, parent) |
55 |
| -} |
56 |
| - |
57 |
| -/** |
58 |
| - * Assert that `node` is a valid unist literal. |
59 |
| - * |
60 |
| - * @param {unknown} [node] |
61 |
| - * Thing to assert. |
62 |
| - * @param {Parent | null | undefined} [parent] |
63 |
| - * Optional, valid parent. |
64 |
| - * @returns {asserts node is Literal} |
65 |
| - * Whether `node` is a literal. |
66 |
| - * @throws {AssertionError} |
67 |
| - * When `node` is not a literal. |
68 |
| - */ |
69 |
| -export function literal(node, parent) { |
70 |
| - return wrap(assertLiteral)(node, parent) |
71 |
| -} |
72 |
| - |
73 |
| -/** |
74 |
| - * Assert that `node` is a valid void node. |
75 |
| - * |
76 |
| - * @param {unknown} [node] |
77 |
| - * Thing to assert. |
78 |
| - * @param {Parent | null | undefined} [parent] |
79 |
| - * Optional, valid parent. |
80 |
| - * @returns {asserts node is _Void} |
81 |
| - * Whether `node` is a node but neither parent nor literal. |
82 |
| - * @throws {AssertionError} |
83 |
| - * When `node` is not a node, a parent, or a literal. |
84 |
| - */ |
85 |
| -export function _void(node, parent) { |
86 |
| - return wrap(assertVoid)(node, parent) |
87 |
| -} |
88 |
| - |
89 |
| -// Identifier to check if a value is seen. |
90 |
| -const ID = '__unist__' |
91 |
| - |
92 |
| -// List of specced properties. |
93 |
| -const defined = new Set(['type', 'value', 'children', 'position']) |
94 |
| - |
95 |
| -/** |
96 |
| - * Wrapper that adds the current node (and parent, if available) to error |
97 |
| - * messages. |
98 |
| - * |
99 |
| - * @template {Node} T |
100 |
| - * Node type. |
101 |
| - * @param {(node?: any, parent?: Parent | null | undefined) => asserts node is T} fn |
102 |
| - * Custom assertion. |
103 |
| - * @returns {(node?: any, parent?: Parent | null | undefined) => asserts node is T} |
104 |
| - * Assertion. |
105 |
| - */ |
106 |
| -export function wrap(fn) { |
107 |
| - return wrapped |
108 |
| - |
109 |
| - /** |
110 |
| - * @param {unknown} node |
111 |
| - * Thing to check. |
112 |
| - * @param {Parent | null | undefined} [parent] |
113 |
| - * Optional, valid parent. |
114 |
| - * @throws {AssertionError} |
115 |
| - * Whether `node` is a node but neither parent nor literal. |
116 |
| - * @returns {void} |
117 |
| - * Nothing. |
118 |
| - */ |
119 |
| - function wrapped(node, parent) { |
120 |
| - try { |
121 |
| - fn(node, parent) |
122 |
| - } catch (error) { |
123 |
| - const exception = /** @type {SeenError} */ (error) |
124 |
| - if (!own.call(exception, ID)) { |
125 |
| - exception[ID] = true |
126 |
| - exception.message += ': `' + view(node) + '`' |
127 |
| - if (parent) exception.message += ' in `' + view(parent) + '`' |
128 |
| - } |
129 |
| - |
130 |
| - throw error |
131 |
| - } |
132 |
| - } |
133 |
| -} |
134 |
| - |
135 |
| -/** |
136 |
| - * Assert that `node` is a valid unist parent. |
137 |
| - * |
138 |
| - * All children will be asserted too. |
139 |
| - * |
140 |
| - * @param {unknown} node |
141 |
| - * Thing to assert. |
142 |
| - * @returns {asserts node is Node} |
143 |
| - * Whether `node` (and its descendants) are valid nodes. |
144 |
| - * @throws {AssertionError} |
145 |
| - * When `node` (or its descendants) is not a node. |
146 |
| - */ |
147 |
| -function assertNode(node) { |
148 |
| - let index = -1 |
149 |
| - |
150 |
| - nodeAssert.ok( |
151 |
| - node && typeof node === 'object' && !Array.isArray(node), |
152 |
| - 'node should be an object' |
153 |
| - ) |
154 |
| - |
155 |
| - nodeAssert.ok(own.call(node, 'type'), 'node should have a type') |
156 |
| - nodeAssert.strictEqual( |
157 |
| - // @ts-expect-error Looks like an indexed object. |
158 |
| - typeof node.type, |
159 |
| - 'string', |
160 |
| - '`type` should be a string' |
161 |
| - ) |
162 |
| - // @ts-expect-error Looks like an indexed object. |
163 |
| - nodeAssert.notStrictEqual(node.type, '', '`type` should not be empty') |
164 |
| - |
165 |
| - // @ts-expect-error Looks like an indexed object. |
166 |
| - if (node.value !== null && node.value !== undefined) { |
167 |
| - nodeAssert.strictEqual( |
168 |
| - // @ts-expect-error Looks like an indexed object. |
169 |
| - typeof node.value, |
170 |
| - 'string', |
171 |
| - '`value` should be a string' |
172 |
| - ) |
173 |
| - } |
174 |
| - |
175 |
| - // @ts-expect-error Looks like an indexed object. |
176 |
| - position(node.position) |
177 |
| - |
178 |
| - /** @type {string} */ |
179 |
| - let key |
180 |
| - |
181 |
| - for (key in node) { |
182 |
| - if (!defined.has(key)) { |
183 |
| - /** @type {unknown} */ |
184 |
| - // @ts-expect-error: hush. |
185 |
| - const value = node[key] |
186 |
| - vanilla(key, value) |
187 |
| - } |
188 |
| - } |
189 |
| - |
190 |
| - // @ts-expect-error Looks like an indexed object. |
191 |
| - if (node.children !== null && node.children !== undefined) { |
192 |
| - /** @type {Parent} */ |
193 |
| - // @ts-expect-error Looks like parent. |
194 |
| - const parent = node |
195 |
| - nodeAssert.ok( |
196 |
| - Array.isArray(parent.children), |
197 |
| - '`children` should be an array' |
198 |
| - ) |
199 |
| - index = -1 |
200 |
| - |
201 |
| - while (++index < parent.children.length) { |
202 |
| - assert(parent.children[index], parent) |
203 |
| - } |
204 |
| - } |
205 |
| -} |
206 |
| - |
207 |
| -/** |
208 |
| - * Assert `value` (which lives at `key`) can be stringified and re-parsed to the |
209 |
| - * same (deep) value. |
210 |
| - * |
211 |
| - * @param {string} key |
212 |
| - * Name of field. |
213 |
| - * @param {unknown} value |
214 |
| - * Value of field. |
215 |
| - */ |
216 |
| -function vanilla(key, value) { |
217 |
| - try { |
218 |
| - nodeAssert.deepStrictEqual(value, JSON.parse(JSON.stringify(value))) |
219 |
| - } catch { |
220 |
| - nodeAssert.fail('non-specced property `' + key + '` should be JSON') |
221 |
| - } |
222 |
| -} |
223 |
| - |
224 |
| -/** |
225 |
| - * Stringify a value to inspect it. |
226 |
| - * |
227 |
| - * Tries `JSON.stringify()`, and if that fails uses `String()` instead. |
228 |
| - * |
229 |
| - * @param {unknown} value |
230 |
| - * Anything (should be JSON). |
231 |
| - * @returns {string} |
232 |
| - * User-visible preresentation. |
233 |
| - */ |
234 |
| -function view(value) { |
235 |
| - try { |
236 |
| - return inspect(value) |
237 |
| - /* c8 ignore next 3 */ |
238 |
| - } catch { |
239 |
| - return String(value) |
240 |
| - } |
241 |
| -} |
242 |
| - |
243 |
| -/** |
244 |
| - * Assert that `node` is a valid unist parent. |
245 |
| - * |
246 |
| - * All children will be asserted too. |
247 |
| - * |
248 |
| - * @param {Node} node |
249 |
| - * Thing to assert. |
250 |
| - * @returns {asserts node is Parent} |
251 |
| - * Whether `node` is a parent and its descendants are valid nodes. |
252 |
| - * @throws {AssertionError} |
253 |
| - * When `node` is not a parent or its descendants are not nodes. |
254 |
| - */ |
255 |
| -function assertParent(node) { |
256 |
| - assertNode(node) |
257 |
| - |
258 |
| - nodeAssert.strictEqual( |
259 |
| - 'value' in node, |
260 |
| - false, |
261 |
| - 'parent should not have `value`' |
262 |
| - ) |
263 |
| - nodeAssert.ok('children' in node, 'parent should have `children`') |
264 |
| -} |
265 |
| - |
266 |
| -/** |
267 |
| - * Assert that `node` is a valid unist literal. |
268 |
| - * |
269 |
| - * @param {unknown} [node] |
270 |
| - * Thing to assert. |
271 |
| - * @returns {asserts node is Literal} |
272 |
| - * Whether `node` is a literal. |
273 |
| - * @throws {AssertionError} |
274 |
| - * When `node` is not a literal. |
275 |
| - */ |
276 |
| -function assertLiteral(node) { |
277 |
| - assertNode(node) |
278 |
| - |
279 |
| - nodeAssert.strictEqual( |
280 |
| - 'children' in node, |
281 |
| - false, |
282 |
| - 'literal should not have `children`' |
283 |
| - ) |
284 |
| - nodeAssert.ok('value' in node, 'literal should have `value`') |
285 |
| -} |
286 |
| - |
287 |
| -/** |
288 |
| - * Assert that `node` is a valid void node. |
289 |
| - * |
290 |
| - * @param {unknown} [node] |
291 |
| - * Thing to assert. |
292 |
| - * @returns {asserts node is _Void} |
293 |
| - * Whether `node` is a node but neither parent nor literal. |
294 |
| - * @throws {AssertionError} |
295 |
| - * When `node` is not a node, a parent, or a literal. |
296 |
| - */ |
297 |
| -function assertVoid(node) { |
298 |
| - assertNode(node) |
299 |
| - |
300 |
| - nodeAssert.strictEqual('value' in node, false, 'void should not have `value`') |
301 |
| - nodeAssert.strictEqual( |
302 |
| - 'children' in node, |
303 |
| - false, |
304 |
| - 'void should not have `children`' |
305 |
| - ) |
306 |
| -} |
307 |
| - |
308 |
| -/** |
309 |
| - * Assert that `position` is a unist position. |
310 |
| - * |
311 |
| - * @param {unknown} position |
312 |
| - * Thing to assert. |
313 |
| - * @returns {asserts position is Position} |
314 |
| - * Whether `position` is a unist position. |
315 |
| - * @throws {AssertionError} |
316 |
| - * When `position` is not a position. |
317 |
| - */ |
318 |
| -function position(position) { |
319 |
| - if (position !== null && position !== undefined) { |
320 |
| - nodeAssert.ok( |
321 |
| - typeof position === 'object' && position === Object(position), |
322 |
| - '`position` should be an object' |
323 |
| - ) |
324 |
| - |
325 |
| - // @ts-expect-error: indexable. |
326 |
| - point(position.start, 'position.start') |
327 |
| - // @ts-expect-error: indexable. |
328 |
| - point(position.end, 'position.end') |
329 |
| - } |
330 |
| -} |
331 |
| - |
332 |
| -/** |
333 |
| - * Assert `point` is a unist point. |
334 |
| - * |
335 |
| - * @param {unknown} point |
336 |
| - * Thing to assert. |
337 |
| - * @param {string} label |
338 |
| - * Whether `point` is a unist point. |
339 |
| - * @returns {asserts point is Point} |
340 |
| - * When `point` is not a point. |
341 |
| - */ |
342 |
| -function point(point, label) { |
343 |
| - if (point !== null && point !== undefined) { |
344 |
| - nodeAssert.ok( |
345 |
| - typeof point === 'object' && point === Object(point), |
346 |
| - '`' + label + '` should be an object' |
347 |
| - ) |
348 |
| - |
349 |
| - if ('line' in point && point.line !== null && point.line !== undefined) { |
350 |
| - nodeAssert.ok( |
351 |
| - typeof point.line === 'number', |
352 |
| - '`' + label + '` should have numeric `line`' |
353 |
| - ) |
354 |
| - nodeAssert.ok(point.line >= 1, '`' + label + '.line` should be gte `1`') |
355 |
| - } |
356 |
| - |
357 |
| - if ( |
358 |
| - 'column' in point && |
359 |
| - point.column !== null && |
360 |
| - point.column !== undefined |
361 |
| - ) { |
362 |
| - nodeAssert.ok( |
363 |
| - typeof point.column === 'number', |
364 |
| - '`' + label + '` should have numeric `column`' |
365 |
| - ) |
366 |
| - nodeAssert.ok( |
367 |
| - point.column >= 1, |
368 |
| - '`' + label + '.column` should be gte `1`' |
369 |
| - ) |
370 |
| - } |
371 |
| - } |
372 |
| -} |
| 5 | +export {_void, assert, literal, parent, wrap} from './lib/index.js' |
0 commit comments