Skip to content

Commit 098866d

Browse files
authored
Add JSDoc based types
Closes GH-2.
1 parent c37d0e5 commit 098866d

17 files changed

+277
-182
lines changed

.gitignore

+1
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.js

+176-53
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,142 @@
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+
111
import nodeAssert from 'assert'
212
import {inspect} from './inspect.js'
313

4-
export var assert = wrap(assertNode)
5-
assert.parent = wrap(parent)
6-
assert.text = wrap(text)
7-
assert.void = wrap(empty)
14+
var own = {}.hasOwnProperty
15+
16+
/**
17+
* Assert that `node` is a valid unist node.
18+
* If `node` is a parent, all children will be asserted too.
19+
*
20+
* @param {unknown} [node]
21+
* @param {Parent} [parent]
22+
* @returns {asserts node is Node}
23+
*/
24+
export function assert(node, parent) {
25+
return wrap(assertNode)(node, parent)
26+
}
27+
28+
/**
29+
* Assert that `node` is a valid unist parent.
30+
* All children will be asserted too.
31+
*
32+
* @param {unknown} [node]
33+
* @param {Parent} [parent]
34+
* @returns {asserts node is Parent}
35+
*/
36+
export function parent(node, parent) {
37+
return wrap(assertParent)(node, parent)
38+
}
39+
40+
/**
41+
* Assert that `node` is a valid unist literal.
42+
*
43+
* @param {unknown} [node]
44+
* @param {Parent} [parent]
45+
* @returns {asserts node is Literal}
46+
*/
47+
export function literal(node, parent) {
48+
return wrap(assertLiteral)(node, parent)
49+
}
50+
51+
/**
52+
* Assert that `node` is a valid unist node, but neither parent nor literal.
53+
*
54+
* @param {unknown} [node]
55+
* @param {Parent} [parent]
56+
* @returns {asserts node is Void}
57+
*/
58+
export function _void(node, parent) {
59+
return wrap(assertVoid)(node, parent)
60+
}
861

962
// Identifier to check if a value is seen.
1063
var ID = '__unist__'
1164

1265
// List of specced properties.
1366
var defined = new Set(['type', 'value', 'children', 'position'])
1467

15-
// Wrapper around `Node` which adds the current node (and parent, if available),
16-
// to the message.
68+
/**
69+
* Wrapper that adds the current node (and parent, if available) to error
70+
* messages.
71+
*
72+
* @param {(node?: unknown, parent?: Parent|null) => asserts node is Node} fn
73+
* @returns {(node?: unknown, parent?: Parent|null) => asserts node is Node}
74+
*/
1775
export function wrap(fn) {
1876
return wrapped
1977

78+
/**
79+
* @param {unknown} node
80+
* @param {Parent} [parent]
81+
* @throws {AssertionError}
82+
* @returns {asserts node is T}
83+
*/
2084
function wrapped(node, parent) {
2185
try {
2286
fn(node, parent)
2387
} catch (error) {
24-
if (!error[ID]) {
88+
if (!own.call(error, ID)) {
2589
error[ID] = true
26-
2790
error.message += ': `' + view(node) + '`'
28-
29-
if (parent) {
30-
error.message += ' in `' + view(parent) + '`'
31-
}
91+
if (parent) error.message += ' in `' + view(parent) + '`'
3292
}
3393

3494
throw error
3595
}
3696
}
3797
}
3898

39-
// Assert.
99+
/**
100+
* Assert.
101+
*
102+
* @param {unknown} node
103+
* @returns {asserts node is Node}
104+
*/
40105
function assertNode(node) {
41-
var type
42-
var children
43-
var value
106+
var index = -1
107+
/** @type {Parent} */
108+
var parent
109+
/** @type {unknown} */
110+
var child
111+
/** @type {string} */
44112
var key
45-
var index
46-
var length
47-
48-
nodeAssert.ok(node === Object(node), 'node should be an object')
49113

50-
type = node.type
51-
children = node.children
52-
value = node.value
114+
nodeAssert.ok(
115+
node && typeof node === 'object' && !Array.isArray(node),
116+
'node should be an object'
117+
)
53118

54-
nodeAssert.ok('type' in node, 'node should have a type')
55-
nodeAssert.strictEqual(typeof type, 'string', '`type` should be a string')
56-
nodeAssert.notStrictEqual(type, '', '`type` should not be empty')
119+
nodeAssert.ok(own.call(node, 'type'), 'node should have a type')
120+
nodeAssert.strictEqual(
121+
// @ts-expect-error Looks like an indexed object.
122+
typeof node.type,
123+
'string',
124+
'`type` should be a string'
125+
)
126+
// @ts-expect-error Looks like an indexed object.
127+
nodeAssert.notStrictEqual(node.type, '', '`type` should not be empty')
57128

58-
if (value !== null && value !== undefined) {
59-
nodeAssert.strictEqual(typeof value, 'string', '`value` should be a string')
129+
// @ts-expect-error Looks like an indexed object.
130+
if (node.value !== null && node.value !== undefined) {
131+
nodeAssert.strictEqual(
132+
// @ts-expect-error Looks like an indexed object.
133+
typeof node.value,
134+
'string',
135+
'`value` should be a string'
136+
)
60137
}
61138

139+
// @ts-expect-error Looks like an indexed object.
62140
position(node.position)
63141

64142
for (key in node) {
@@ -67,19 +145,30 @@ function assertNode(node) {
67145
}
68146
}
69147

70-
if (children !== null && children !== undefined) {
71-
nodeAssert.ok(Array.isArray(children), '`children` should be an array')
148+
// @ts-expect-error Looks like an indexed object.
149+
if (node.children !== null && node.children !== undefined) {
150+
// @ts-expect-error Looks like parent.
151+
parent = node
152+
nodeAssert.ok(
153+
Array.isArray(parent.children),
154+
'`children` should be an array'
155+
)
72156
index = -1
73-
length = children.length
74157

75-
while (++index < length) {
76-
assert(children[index], node)
158+
while (++index < parent.children.length) {
159+
child = parent.children[index]
160+
assert(child, parent)
77161
}
78162
}
79163
}
80164

81-
// Assert `value` (which lives at `key`) can be stringified and re-parsed to the
82-
// same (deep) value.
165+
/**
166+
* Assert `value` (which lives at `key`) can be stringified and re-parsed to the
167+
* same (deep) value.
168+
*
169+
* @param {string} key
170+
* @param {unknown} value
171+
*/
83172
function vanilla(key, value) {
84173
try {
85174
nodeAssert.deepStrictEqual(value, JSON.parse(JSON.stringify(value)))
@@ -88,8 +177,13 @@ function vanilla(key, value) {
88177
}
89178
}
90179

91-
// Stringify a value to inspect it.
92-
// Tries `JSON.stringify()`, and if that fails uses `String()` instead.
180+
/**
181+
* Stringify a value to inspect it.
182+
* Tries `JSON.stringify()`, and if that fails uses `String()` instead.
183+
*
184+
* @param {unknown} value
185+
* @returns {string}
186+
*/
93187
function view(value) {
94188
try {
95189
return inspect(value)
@@ -99,8 +193,13 @@ function view(value) {
99193
}
100194
}
101195

102-
// Assert `node` is a parent node.
103-
function parent(node) {
196+
/**
197+
* Assert `node` is a parent node.
198+
*
199+
* @param {Node} node
200+
* @returns {asserts node is Parent}
201+
*/
202+
function assertParent(node) {
104203
assertNode(node)
105204

106205
nodeAssert.strictEqual(
@@ -111,20 +210,30 @@ function parent(node) {
111210
nodeAssert.ok('children' in node, 'parent should have `children`')
112211
}
113212

114-
// Assert `node` is a text node.
115-
function text(node) {
213+
/**
214+
* Assert `node` is a literal node.
215+
*
216+
* @param {Node} node
217+
* @returns {asserts node is Literal}
218+
*/
219+
function assertLiteral(node) {
116220
assertNode(node)
117221

118222
nodeAssert.strictEqual(
119223
'children' in node,
120224
false,
121-
'text should not have `children`'
225+
'literal should not have `children`'
122226
)
123-
nodeAssert.ok('value' in node, 'text should have `value`')
227+
nodeAssert.ok('value' in node, 'literal should have `value`')
124228
}
125229

126-
// Assert `node` is a unist node, but neither parent nor text.
127-
function empty(node) {
230+
/**
231+
* Assert `node` is a unist node, but neither parent nor literal.
232+
*
233+
* @param {Node} node
234+
* @returns {asserts node is Void}
235+
*/
236+
function assertVoid(node) {
128237
assertNode(node)
129238

130239
nodeAssert.strictEqual('value' in node, false, 'void should not have `value`')
@@ -135,7 +244,12 @@ function empty(node) {
135244
)
136245
}
137246

138-
// Assert `position` is a unist position.
247+
/**
248+
* Assert `position` is a unist position.
249+
*
250+
* @param {Position} position
251+
* @returns {asserts position is Position}
252+
*/
139253
function position(position) {
140254
if (position !== null && position !== undefined) {
141255
nodeAssert.ok(
@@ -148,27 +262,36 @@ function position(position) {
148262
}
149263
}
150264

151-
// Assert `point` is a unist point.
152-
function point(point, name) {
265+
/**
266+
* Assert `point` is a unist point.
267+
*
268+
* @param {Point} point
269+
* @param {string} label
270+
* @returns {asserts point is Point}
271+
*/
272+
function point(point, label) {
153273
if (point !== null && point !== undefined) {
154-
nodeAssert.ok(point === Object(point), '`' + name + '` should be an object')
274+
nodeAssert.ok(
275+
point === Object(point),
276+
'`' + label + '` should be an object'
277+
)
155278

156279
if (point.line !== null && point.line !== undefined) {
157280
nodeAssert.ok(
158281
'line' in point,
159-
'`' + name + '` should have numeric `line`'
282+
'`' + label + '` should have numeric `line`'
160283
)
161-
nodeAssert.ok(point.line >= 1, '`' + name + '.line` should be gte `1`')
284+
nodeAssert.ok(point.line >= 1, '`' + label + '.line` should be gte `1`')
162285
}
163286

164287
if (point.column !== null && point.column !== undefined) {
165288
nodeAssert.ok(
166289
'column' in point,
167-
'`' + name + '` should have numeric `column`'
290+
'`' + label + '` should have numeric `column`'
168291
)
169292
nodeAssert.ok(
170293
point.column >= 1,
171-
'`' + name + '.column` should be gte `1`'
294+
'`' + label + '.column` should be gte `1`'
172295
)
173296
}
174297
}

index.test-d.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import {expectType, expectNotType} from 'tsd'
2+
import {Node, Parent} from 'unist'
3+
import {assert, parent} from './index.js'
4+
5+
var emptyNode = {type: 'a'}
6+
var literalNode = {type: 'b', value: 'c'}
7+
var parentNode = {type: 'd', children: [emptyNode, literalNode]}
8+
9+
expectNotType<Node>(emptyNode)
10+
expectNotType<Node>(literalNode)
11+
expectNotType<Node>(parentNode)
12+
13+
assert(emptyNode)
14+
expectType<Node>(emptyNode)
15+
16+
expectNotType<Parent>(parentNode)
17+
parent(parentNode)
18+
expectType<Parent>(parentNode)

inspect.browser.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
/**
2+
* @param {unknown} value
3+
* @returns {string}
4+
*/
15
export function inspect(value) {
26
return JSON.stringify(value)
37
}

inspect.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import {inspect as utilInspect} from 'util'
22

3+
/**
4+
* @param {unknown} value
5+
* @returns {string}
6+
*/
37
export function inspect(value) {
48
return utilInspect(value, {colors: false})
59
}

0 commit comments

Comments
 (0)