UNPKG

2.8 kBJavaScriptView Raw
1/**
2 * @typedef {import('unist').Node} Node
3 * @typedef {import('unist').Parent} Parent
4 * @typedef {import('unist-util-is').Test} Test
5 *
6 * @typedef Options
7 * Configuration.
8 * @property {boolean | null | undefined} [cascade=true]
9 * Whether to drop parent nodes if they had children, but all their children
10 * were filtered out.
11 */
12
13import {convert} from 'unist-util-is'
14
15/** @type {Array<unknown>} */
16const empty = []
17
18/**
19 * Change the given `tree` by removing all nodes that pass `test`.
20 *
21 * The tree is walked in preorder (NLR), visiting the node itself, then its
22 * head, etc.
23 *
24 * @param tree
25 * Tree to change.
26 * @param options
27 * Configuration (optional).
28 * @param test
29 * `unist-util-is` compatible test.
30 * @returns
31 * The given `tree` without nodes that pass `test`.
32 *
33 * `null` is returned if `tree` itself didn’t pass the test or is cascaded
34 * away.
35 */
36// To do: next major: don’t return `tree`.
37export const remove =
38 /**
39 * @type {(
40 * (<Tree extends Node>(node: Tree, options: Options, test: Test) => Tree | null) &
41 * (<Tree extends Node>(node: Tree, test: Test) => Tree | null)
42 * )}
43 */
44 (
45 /**
46 * @param {Node} tree
47 * @param {Options | null | undefined} [options]
48 * @param {Test | null | undefined} [test]
49 * @returns {Node | null}
50 */
51 function (tree, options, test) {
52 const is = convert(test || options)
53 const cascade =
54 !options || options.cascade === undefined || options.cascade === null
55 ? true
56 : options.cascade
57
58 return preorder(tree)
59
60 /**
61 * Check and remove nodes recursively in preorder.
62 * For each composite node, modify its children array in-place.
63 *
64 * @param {Node} node
65 * @param {number | null | undefined} [index]
66 * @param {Parent | null | undefined} [parent]
67 * @returns {Node | null}
68 */
69 function preorder(node, index, parent) {
70 /** @type {Array<Node>} */
71 // @ts-expect-error looks like a parent.
72 const children = node.children || empty
73 let childIndex = -1
74 let position = 0
75
76 if (is(node, index, parent)) {
77 return null
78 }
79
80 if (children.length > 0) {
81 // Move all living children to the beginning of the children array.
82 while (++childIndex < children.length) {
83 // @ts-expect-error looks like a parent.
84 if (preorder(children[childIndex], childIndex, node)) {
85 children[position++] = children[childIndex]
86 }
87 }
88
89 // Cascade delete.
90 if (cascade && !position) {
91 return null
92 }
93
94 // Drop other nodes.
95 children.length = position
96 }
97
98 return node
99 }
100 }
101 )