astro-ghostcms/.pnpm-store/v3/files/73/d165e9245cfb6cd76ec99c7face...

302 lines
7.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @typedef {import('unist').Node} Node
* @typedef {import('unist').Parent} Parent
*/
/**
* @typedef {Record<string, unknown>} Props
* @typedef {null | undefined | string | Props | TestFunctionAnything | Array<string | Props | TestFunctionAnything>} Test
* Check for an arbitrary node, unaware of TypeScript inferral.
*
* @callback TestFunctionAnything
* Check if a node passes a test, unaware of TypeScript inferral.
* @param {unknown} this
* The given context.
* @param {Node} node
* A node.
* @param {number | null | undefined} [index]
* The nodes position in its parent.
* @param {Parent | null | undefined} [parent]
* The nodes parent.
* @returns {boolean | void}
* Whether this node passes the test.
*/
/**
* @template {Node} Kind
* Node type.
* @typedef {Kind['type'] | Partial<Kind> | TestFunctionPredicate<Kind> | Array<Kind['type'] | Partial<Kind> | TestFunctionPredicate<Kind>>} PredicateTest
* Check for a node that can be inferred by TypeScript.
*/
/**
* Check if a node passes a certain test.
*
* @template {Node} Kind
* Node type.
* @callback TestFunctionPredicate
* Complex test function for a node that can be inferred by TypeScript.
* @param {Node} node
* A node.
* @param {number | null | undefined} [index]
* The nodes position in its parent.
* @param {Parent | null | undefined} [parent]
* The nodes parent.
* @returns {node is Kind}
* Whether this node passes the test.
*/
/**
* @callback AssertAnything
* Check that an arbitrary value is a node, unaware of TypeScript inferral.
* @param {unknown} [node]
* Anything (typically a node).
* @param {number | null | undefined} [index]
* The nodes position in its parent.
* @param {Parent | null | undefined} [parent]
* The nodes parent.
* @returns {boolean}
* Whether this is a node and passes a test.
*/
/**
* Check if a node is a node and passes a certain node test.
*
* @template {Node} Kind
* Node type.
* @callback AssertPredicate
* Check that an arbitrary value is a specific node, aware of TypeScript.
* @param {unknown} [node]
* Anything (typically a node).
* @param {number | null | undefined} [index]
* The nodes position in its parent.
* @param {Parent | null | undefined} [parent]
* The nodes parent.
* @returns {node is Kind}
* Whether this is a node and passes a test.
*/
/**
* Check if `node` is a `Node` and whether it passes the given test.
*
* @param node
* Thing to check, typically `Node`.
* @param test
* A check for a specific node.
* @param index
* The nodes position in its parent.
* @param parent
* The nodes parent.
* @returns
* Whether `node` is a node and passes a test.
*/
export const is =
/**
* @type {(
* (() => false) &
* (<Kind extends Node = Node>(node: unknown, test: PredicateTest<Kind>, index: number, parent: Parent, context?: unknown) => node is Kind) &
* (<Kind extends Node = Node>(node: unknown, test: PredicateTest<Kind>, index?: null | undefined, parent?: null | undefined, context?: unknown) => node is Kind) &
* ((node: unknown, test: Test, index: number, parent: Parent, context?: unknown) => boolean) &
* ((node: unknown, test?: Test, index?: null | undefined, parent?: null | undefined, context?: unknown) => boolean)
* )}
*/
(
/**
* @param {unknown} [node]
* @param {Test} [test]
* @param {number | null | undefined} [index]
* @param {Parent | null | undefined} [parent]
* @param {unknown} [context]
* @returns {boolean}
*/
// eslint-disable-next-line max-params
function is(node, test, index, parent, context) {
const 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')
}
// @ts-expect-error Looks like a node.
return node && node.type && typeof node.type === 'string'
? Boolean(check.call(context, node, index, parent))
: false
}
)
/**
* Generate an assertion from a test.
*
* Useful if youre going to test many nodes, for example when creating a
* utility where something else passes a compatible test.
*
* The created function is a bit faster because it expects valid input only:
* a `node`, `index`, and `parent`.
*
* @param test
* * when nullish, checks if `node` is a `Node`.
* * when `string`, works like passing `(node) => 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 if any one of the subtests pass.
* @returns
* An assertion.
*/
export const convert =
/**
* @type {(
* (<Kind extends Node>(test: PredicateTest<Kind>) => AssertPredicate<Kind>) &
* ((test?: Test) => AssertAnything)
* )}
*/
(
/**
* @param {Test} [test]
* @returns {AssertAnything}
*/
function (test) {
if (test === undefined || test === null) {
return ok
}
if (typeof test === 'string') {
return typeFactory(test)
}
if (typeof test === 'object') {
return Array.isArray(test) ? anyFactory(test) : propsFactory(test)
}
if (typeof test === 'function') {
return castFactory(test)
}
throw new Error('Expected function, string, or object as test')
}
)
/**
* @param {Array<string | Props | TestFunctionAnything>} tests
* @returns {AssertAnything}
*/
function anyFactory(tests) {
/** @type {Array<AssertAnything>} */
const checks = []
let index = -1
while (++index < tests.length) {
checks[index] = convert(tests[index])
}
return castFactory(any)
/**
* @this {unknown}
* @param {Array<unknown>} parameters
* @returns {boolean}
*/
function any(...parameters) {
let index = -1
while (++index < checks.length) {
if (checks[index].call(this, ...parameters)) return true
}
return false
}
}
/**
* Turn an object into a test for a node with a certain fields.
*
* @param {Props} check
* @returns {AssertAnything}
*/
function propsFactory(check) {
return castFactory(all)
/**
* @param {Node} node
* @returns {boolean}
*/
function all(node) {
/** @type {string} */
let key
for (key in check) {
// @ts-expect-error: hush, it sure works as an index.
if (node[key] !== check[key]) return false
}
return true
}
}
/**
* Turn a string into a test for a node with a certain type.
*
* @param {string} check
* @returns {AssertAnything}
*/
function typeFactory(check) {
return castFactory(type)
/**
* @param {Node} node
*/
function type(node) {
return node && node.type === check
}
}
/**
* Turn a custom test into a test for a node that passes that test.
*
* @param {TestFunctionAnything} check
* @returns {AssertAnything}
*/
function castFactory(check) {
return assertion
/**
* @this {unknown}
* @param {unknown} node
* @param {Array<unknown>} parameters
* @returns {boolean}
*/
function assertion(node, ...parameters) {
return Boolean(
node &&
typeof node === 'object' &&
'type' in node &&
// @ts-expect-error: fine.
Boolean(check.call(this, node, ...parameters))
)
}
}
function ok() {
return true
}