Skip to content

Add JSDoc based types #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
*.d.ts
*.log
coverage/
node_modules/
Expand Down
69 changes: 0 additions & 69 deletions index.d.ts

This file was deleted.

287 changes: 212 additions & 75 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,105 +1,242 @@
// Assert if `test` passes for `node`.
// When a `parent` node is known the `index` of node should also be given.
// eslint-disable-next-line max-params
export function is(node, test, index, parent, context) {
var check = convert(test)

if (
index !== undefined &&
index !== null &&
(typeof index !== 'number' ||
index < 0 ||
index === Number.POSITIVE_INFINITY)
) {
throw new Error('Expected positive finite index')
}

if (
parent !== undefined &&
parent !== null &&
(!is(parent) || !parent.children)
) {
throw new Error('Expected parent node')
}

if (
(parent === undefined || parent === null) !==
(index === undefined || index === null)
) {
throw new Error('Expected both parent and index')
}

return node && node.type && typeof node.type === 'string'
? Boolean(check.call(context, node, index, parent))
: false
}

export function convert(test) {
if (test === undefined || test === null) {
return ok
}
/**
* @typedef {import('unist').Node} Node
* @typedef {import('unist').Parent} Parent
*
* @typedef {string} Type
* @typedef {Object<string, unknown>} Props
*/

/**
* Check if a node passes a test
*
* @callback TestFunctionAnything
* @param {Node} node
* @param {number} [index]
* @param {Parent} [parent]
* @returns {boolean|void}
*/

/**
* Check if a node passes a certain node test
*
* @template {Node} X
* @callback TestFunctionPredicate
* @param {Node} node
* @param {number} [index]
* @param {Parent} [parent]
* @returns {node is X}
*/

/**
* @callback AssertAnything
* @param {unknown} [node]
* @param {number} [index]
* @param {Parent} [parent]
* @returns {boolean}
*/

/**
* Check if a node passes a certain node test
*
* @template {Node} Y
* @callback AssertPredicate
* @param {unknown} [node]
* @param {number} [index]
* @param {Parent} [parent]
* @returns {node is Y}
*/

export var is =
/**
* Check if a node passes a test.
* When a `parent` node is known the `index` of node should also be given.
*
* @type {(
* (<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) &
* ((node?: unknown, test?: null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>, index?: number, parent?: Parent, context?: unknown) => boolean)
Comment on lines +55 to +56
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Impressive that it works, 👍
I'm looking foward to when JSDoc gets clearer syntax for overloading 😅

* )}
*/
(
/**
* Check if a node passes a test.
* When a `parent` node is known the `index` of node should also be given.
*
* @param {unknown} [node] Node to check
* @param {null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>} [test]
* When nullish, checks if `node` is a `Node`.
* When `string`, works like passing `function (node) {return node.type === test}`.
* When `function` checks if function passed the node is true.
* When `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
* When `array`, checks any one of the subtests pass.
* @param {number} [index] Position of `node` in `parent`
* @param {Parent} [parent] Parent of `node`
* @param {unknown} [context] Context object to invoke `test` with
* @returns {boolean} Whether test passed and `node` is a `Node` (object with `type` set to non-empty `string`).
*/
// eslint-disable-next-line max-params
function is(node, test, index, parent, context) {
var check = convert(test)

if (
index !== undefined &&
index !== null &&
(typeof index !== 'number' ||
index < 0 ||
index === Number.POSITIVE_INFINITY)
) {
throw new Error('Expected positive finite index')
}

if (typeof test === 'string') {
return typeFactory(test)
}
if (
parent !== undefined &&
parent !== null &&
(!is(parent) || !parent.children)
) {
throw new Error('Expected parent node')
}

if (typeof test === 'object') {
return 'length' in test ? anyFactory(test) : allFactory(test)
}
if (
(parent === undefined || parent === null) !==
(index === undefined || index === null)
) {
throw new Error('Expected both parent and index')
}

if (typeof test === 'function') {
return test
}
// @ts-ignore Looks like a node.
return node && node.type && typeof node.type === 'string'
? Boolean(check.call(context, node, index, parent))
: false
}
)

export var convert =
/**
* @type {(
* (<T extends Node>(test: T['type']|Partial<T>|TestFunctionPredicate<T>) => AssertPredicate<T>) &
* ((test?: null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>) => AssertAnything)
* )}
*/
(
/**
* Generate an assertion from a check.
* @param {null|undefined|Type|Props|TestFunctionAnything|Array.<Type|Props|TestFunctionAnything>} [test]
* When nullish, checks if `node` is a `Node`.
* When `string`, works like passing `function (node) {return node.type === test}`.
* When `function` checks if function passed the node is true.
* When `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
* When `array`, checks any one of the subtests pass.
* @returns {AssertAnything}
*/
function (test) {
if (test === undefined || test === null) {
return ok
}

throw new Error('Expected function, string, or object as test')
}
if (typeof test === 'string') {
return typeFactory(test)
}

// Utility to assert each property in `test` is represented in `node`, and each
// values are strictly equal.
function allFactory(test) {
return all
if (typeof test === 'object') {
// @ts-ignore looks like a list of tests / partial test object.
return 'length' in test ? anyFactory(test) : propsFactory(test)
}

function all(node) {
var key
if (typeof test === 'function') {
return castFactory(test)
}

for (key in test) {
if (node[key] !== test[key]) return false
throw new Error('Expected function, string, or object as test')
}

return true
}
}

)
/**
* @param {Array.<Type|Props|TestFunctionAnything>} tests
* @returns {AssertAnything}
*/
function anyFactory(tests) {
/** @type {Array.<AssertAnything>} */
var checks = []
var index = -1

while (++index < tests.length) {
checks[index] = convert(tests[index])
}

return any
return castFactory(any)

/**
* @this {unknown}
* @param {unknown[]} parameters
* @returns {boolean}
*/
function any(...parameters) {
var index = -1

while (++index < checks.length) {
if (checks[index].call(this, ...parameters)) {
return true
}
if (checks[index].call(this, ...parameters)) return true
}

return false
}
}

// Utility to convert a string into a function which checks a given node’s type
// for said string.
function typeFactory(test) {
return type
/**
* Utility to assert each property in `test` is represented in `node`, and each
* values are strictly equal.
*
* @param {Props} check
* @returns {AssertAnything}
*/
function propsFactory(check) {
return castFactory(all)

/**
* @param {Node} node
* @returns {boolean}
*/
function all(node) {
/** @type {string} */
var key

for (key in check) {
if (node[key] !== check[key]) return
}

return true
}
}

/**
* Utility to convert a string into a function which checks a given node’s type
* for said string.
*
* @param {Type} check
* @returns {AssertAnything}
*/
function typeFactory(check) {
return castFactory(type)

/**
* @param {Node} node
*/
function type(node) {
return Boolean(node && node.type === test)
return node && node.type === check
}
}

/**
* Utility to convert a string into a function which checks a given node’s type
* for said string.
* @param {TestFunctionAnything} check
* @returns {AssertAnything}
*/
function castFactory(check) {
return assertion

/**
* @this {unknown}
* @param {Array.<unknown>} parameters
* @returns {boolean}
*/
function assertion(...parameters) {
return Boolean(check.call(this, ...parameters))
}
}

Expand Down
Loading