unist-util-find-all-before
Version:
unist utility to find nodes before another node
139 lines (125 loc) • 3.77 kB
JavaScript
/**
* @typedef {import('unist').Node} UnistNode
* @typedef {import('unist').Parent} UnistParent
*/
/**
* @typedef {Exclude<import('unist-util-is').Test, undefined> | undefined} Test
* Test from `unist-util-is`.
*
* Note: we have remove and add `undefined`, because otherwise when generating
* automatic `.d.ts` files, TS tries to flatten paths from a local perspective,
* which doesn’t work when publishing on npm.
*/
/**
* @typedef {(
* Fn extends (value: any) => value is infer Thing
* ? Thing
* : Fallback
* )} Predicate
* Get the value of a type guard `Fn`.
* @template Fn
* Value; typically function that is a type guard (such as `(x): x is Y`).
* @template Fallback
* Value to yield if `Fn` is not a type guard.
*/
/**
* @typedef {(
* Check extends null | undefined // No test.
* ? Value
* : Value extends {type: Check} // String (type) test.
* ? Value
* : Value extends Check // Partial test.
* ? Value
* : Check extends Function // Function test.
* ? Predicate<Check, Value> extends Value
* ? Predicate<Check, Value>
* : never
* : never // Some other test?
* )} MatchesOne
* Check whether a node matches a primitive check in the type system.
* @template Value
* Value; typically unist `Node`.
* @template Check
* Value; typically `unist-util-is`-compatible test, but not arrays.
*/
/**
* @typedef {(
* Check extends Array<any>
* ? MatchesOne<Value, Check[keyof Check]>
* : MatchesOne<Value, Check>
* )} Matches
* Check whether a node matches a check in the type system.
* @template Value
* Value; typically unist `Node`.
* @template Check
* Value; typically `unist-util-is`-compatible test.
*/
/**
* @typedef {(
* Kind extends {children: Array<infer Child>}
* ? Child
* : never
* )} Child
* Collect nodes that can be parents of `Child`.
* @template {UnistNode} Kind
* All node types.
*/
import {convert} from 'unist-util-is'
/**
* Find the nodes in `parent` before another `node` or before an index, that
* pass `test`.
*
* @param parent
* Parent node.
* @param index
* Child node or index.
* @param [test=undefined]
* Test for child to look for (optional).
* @returns
* Children (matching `test`, if given).
*/
export const findAllBefore =
// Note: overloads like this are needed to support optional generics.
/**
* @type {(
* (<Kind extends UnistParent, Check extends Test>(parent: Kind, index: Child<Kind> | number, test: Check) => Array<Matches<Child<Kind>, Check>>) &
* (<Kind extends UnistParent>(parent: Kind, index: Child<Kind> | number, test?: null | undefined) => Array<Child<Kind>>)
* )}
*/
(
/**
* @param {UnistParent} parent
* @param {UnistNode | number} index
* @param {Test} [test]
* @returns {Array<UnistNode>}
*/
function (parent, index, test) {
const is = convert(test)
/** @type {Array<UnistNode>} */
const results = []
let offset = -1
if (!parent || !parent.type || !parent.children) {
throw new Error('Expected parent node')
}
if (typeof index === 'number') {
if (index < 0 || index === Number.POSITIVE_INFINITY) {
throw new Error('Expected positive finite number as index')
}
} else {
index = parent.children.indexOf(index)
if (index < 0) {
throw new Error('Expected child node or index')
}
}
// Performance.
if (index > parent.children.length) {
index = parent.children.length
}
while (++offset < index) {
if (is(parent.children[offset], offset, parent)) {
results.push(parent.children[offset])
}
}
return results
}
)