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