Spaces:
Build error
Build error
| /** | |
| * @typedef {import('unist').Parent} Parent | |
| * @typedef {import('hast').Element} Element | |
| */ | |
| /** | |
| * @typedef {null | undefined | string | TestFunctionAnything | Array<string | TestFunctionAnything>} Test | |
| * Check for an arbitrary element, unaware of TypeScript inferral. | |
| * | |
| * @callback TestFunctionAnything | |
| * Check if an element passes a test, unaware of TypeScript inferral. | |
| * @param {Element} element | |
| * An element. | |
| * @param {number | null | undefined} [index] | |
| * The element’s position in its parent. | |
| * @param {Parent | null | undefined} [parent] | |
| * The element’s parent. | |
| * @returns {boolean | void} | |
| * Whether this element passes the test. | |
| */ | |
| /** | |
| * @template {Element} T | |
| * Element type. | |
| * @typedef {T['tagName'] | TestFunctionPredicate<T> | Array<T['tagName'] | TestFunctionPredicate<T>>} PredicateTest | |
| * Check for an element that can be inferred by TypeScript. | |
| */ | |
| /** | |
| * Check if an element passes a certain node test. | |
| * | |
| * @template {Element} T | |
| * Element type. | |
| * @callback TestFunctionPredicate | |
| * Complex test function for an element that can be inferred by TypeScript. | |
| * @param {Element} element | |
| * An element. | |
| * @param {number | null | undefined} [index] | |
| * The element’s position in its parent. | |
| * @param {Parent | null | undefined} [parent] | |
| * The element’s parent. | |
| * @returns {element is T} | |
| * Whether this element passes the test. | |
| */ | |
| /** | |
| * @callback AssertAnything | |
| * Check that an arbitrary value is an element, unaware of TypeScript inferral. | |
| * @param {unknown} [node] | |
| * Anything (typically a node). | |
| * @param {number | null | undefined} [index] | |
| * The node’s position in its parent. | |
| * @param {Parent | null | undefined} [parent] | |
| * The node’s parent. | |
| * @returns {boolean} | |
| * Whether this is an element and passes a test. | |
| */ | |
| /** | |
| * Check if a node is an element and passes a certain node test | |
| * | |
| * @template {Element} T | |
| * Element type. | |
| * @callback AssertPredicate | |
| * Check that an arbitrary value is a specific element, aware of TypeScript. | |
| * @param {unknown} [node] | |
| * Anything (typically a node). | |
| * @param {number | null | undefined} [index] | |
| * The node’s position in its parent. | |
| * @param {Parent | null | undefined} [parent] | |
| * The node’s parent. | |
| * @returns {node is T} | |
| * Whether this is an element and passes a test. | |
| */ | |
| /** | |
| * Check if `node` is an `Element` and whether it passes the given test. | |
| * | |
| * @param node | |
| * Thing to check, typically `Node`. | |
| * @param test | |
| * A check for a specific element. | |
| * @param index | |
| * The node’s position in its parent. | |
| * @param parent | |
| * The node’s parent. | |
| * @returns | |
| * Whether `node` is an element and passes a test. | |
| */ | |
| export const isElement = | |
| /** | |
| * @type {( | |
| * (() => false) & | |
| * (<T extends Element = Element>(node: unknown, test?: PredicateTest<T>, index?: number, parent?: Parent, context?: unknown) => node is T) & | |
| * ((node: unknown, test: Test, index?: number, parent?: Parent, context?: unknown) => boolean) | |
| * )} | |
| */ | |
| ( | |
| /** | |
| * @param {unknown} [node] | |
| * @param {Test | undefined} [test] | |
| * @param {number | null | undefined} [index] | |
| * @param {Parent | null | undefined} [parent] | |
| * @param {unknown} [context] | |
| * @returns {boolean} | |
| */ | |
| // eslint-disable-next-line max-params | |
| function (node, test, index, parent, context) { | |
| const check = convertElement(test) | |
| if ( | |
| index !== undefined && | |
| index !== null && | |
| (typeof index !== 'number' || | |
| index < 0 || | |
| index === Number.POSITIVE_INFINITY) | |
| ) { | |
| throw new Error('Expected positive finite index for child node') | |
| } | |
| if ( | |
| parent !== undefined && | |
| parent !== null && | |
| (!parent.type || !parent.children) | |
| ) { | |
| throw new Error('Expected parent node') | |
| } | |
| // @ts-expect-error Looks like a node. | |
| if (!node || !node.type || typeof node.type !== 'string') { | |
| return false | |
| } | |
| if ( | |
| (parent === undefined || parent === null) !== | |
| (index === undefined || index === null) | |
| ) { | |
| throw new Error('Expected both parent and index') | |
| } | |
| return check.call(context, node, index, parent) | |
| } | |
| ) | |
| /** | |
| * Generate an assertion from a test. | |
| * | |
| * Useful if you’re 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 an `Element`. | |
| * * When `string`, works like passing `(element) => element.tagName === test`. | |
| * * When `function` checks if function passed the element is true. | |
| * * When `array`, checks any one of the subtests pass. | |
| * @returns | |
| * An assertion. | |
| */ | |
| export const convertElement = | |
| /** | |
| * @type {( | |
| * (<T extends Element>(test: T['tagName'] | TestFunctionPredicate<T>) => AssertPredicate<T>) & | |
| * ((test?: Test) => AssertAnything) | |
| * )} | |
| */ | |
| ( | |
| /** | |
| * @param {Test | null | undefined} [test] | |
| * @returns {AssertAnything} | |
| */ | |
| function (test) { | |
| if (test === undefined || test === null) { | |
| return element | |
| } | |
| if (typeof test === 'string') { | |
| return tagNameFactory(test) | |
| } | |
| if (typeof test === 'object') { | |
| return anyFactory(test) | |
| } | |
| if (typeof test === 'function') { | |
| return castFactory(test) | |
| } | |
| throw new Error('Expected function, string, or array as test') | |
| } | |
| ) | |
| /** | |
| * Handle multiple tests. | |
| * | |
| * @param {Array<string | TestFunctionAnything>} tests | |
| * @returns {AssertAnything} | |
| */ | |
| function anyFactory(tests) { | |
| /** @type {Array<AssertAnything>} */ | |
| const checks = [] | |
| let index = -1 | |
| while (++index < tests.length) { | |
| checks[index] = convertElement(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 a string into a test for an element with a certain tag name. | |
| * | |
| * @param {string} check | |
| * @returns {AssertAnything} | |
| */ | |
| function tagNameFactory(check) { | |
| return tagName | |
| /** | |
| * @param {unknown} node | |
| * @returns {boolean} | |
| */ | |
| function tagName(node) { | |
| return element(node) && node.tagName === check | |
| } | |
| } | |
| /** | |
| * Turn a custom test into a test for an element 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) { | |
| // @ts-expect-error: fine. | |
| return element(node) && Boolean(check.call(this, node, ...parameters)) | |
| } | |
| } | |
| /** | |
| * Make sure something is an element. | |
| * | |
| * @param {unknown} node | |
| * @returns {node is Element} | |
| */ | |
| function element(node) { | |
| return Boolean( | |
| node && | |
| typeof node === 'object' && | |
| // @ts-expect-error Looks like a node. | |
| node.type === 'element' && | |
| // @ts-expect-error Looks like an element. | |
| typeof node.tagName === 'string' | |
| ) | |
| } | |