Skip to content

Commit 3cb2732

Browse files
committedJul 6, 2023
Fix performance of InclusiveDescendant type
1 parent 12c9ee9 commit 3cb2732

File tree

3 files changed

+134
-19
lines changed

3 files changed

+134
-19
lines changed
 

‎index.test-d.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ visit(implicitTree, function (node, index, parent) {
5555
expectAssignable<Node>(node)
5656
expectNotType<Node>(node)
5757
expectType<number | undefined>(index)
58-
expectType<never>(parent)
58+
expectAssignable<Parent | undefined>(parent)
5959
})
6060

6161
// ## String test
@@ -126,8 +126,7 @@ visit(sampleTree, isHeading, function (node) {
126126
})
127127
// Function test (explicit assertion).
128128
visit(sampleTree, isHeading2, function (node) {
129-
// To do: improving `InclusiveDescendant` should use `Heading & {depth: 2}`.
130-
expectType<never>(node)
129+
expectType<Heading & {depth: 2}>(node)
131130
})
132131

133132
// ## Combined tests

‎lib/index.js

+128-16
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,134 @@
55
* @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult
66
*/
77

8+
// To do: use types from `unist-util-visit-parents` when it’s released.
9+
10+
/**
11+
* @typedef {(
12+
* Fn extends (value: any) => value is infer Thing
13+
* ? Thing
14+
* : Fallback
15+
* )} Predicate
16+
* Get the value of a type guard `Fn`.
17+
* @template Fn
18+
* Value; typically function that is a type guard (such as `(x): x is Y`).
19+
* @template Fallback
20+
* Value to yield if `Fn` is not a type guard.
21+
*/
22+
823
/**
924
* @typedef {(
10-
* Ancestor extends UnistParent
11-
* ? Child extends Ancestor['children'][number]
12-
* ? Ancestor
13-
* : never
25+
* Check extends null | undefined // No test.
26+
* ? Value
27+
* : Value extends {type: Check} // String (type) test.
28+
* ? Value
29+
* : Value extends Check // Partial test.
30+
* ? Value
31+
* : Check extends Function // Function test.
32+
* ? Predicate<Check, Value> extends Value
33+
* ? Predicate<Check, Value>
1434
* : never
15-
* )} ParentsOf
16-
* Check if `Child` can be a child of `Ancestor`.
17-
*
18-
* Returns the ancestor when `Child` can be a child of `Ancestor`, or returns
19-
* `never`.
20-
* @template {UnistNode} Ancestor
21-
* Node type.
35+
* : never // Some other test?
36+
* )} MatchesOne
37+
* Check whether a node matches a primitive check in the type system.
38+
* @template Value
39+
* Value; typically unist `Node`.
40+
* @template Check
41+
* Value; typically `unist-util-is`-compatible test, but not arrays.
42+
*/
43+
44+
/**
45+
* @typedef {(
46+
* Check extends Array<any>
47+
* ? MatchesOne<Value, Check[keyof Check]>
48+
* : MatchesOne<Value, Check>
49+
* )} Matches
50+
* Check whether a node matches a check in the type system.
51+
* @template Value
52+
* Value; typically unist `Node`.
53+
* @template Check
54+
* Value; typically `unist-util-is`-compatible test.
55+
*/
56+
57+
/**
58+
* @typedef {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint
59+
* Number; capped reasonably.
60+
*/
61+
62+
/**
63+
* @typedef {I extends 0 ? 1 : I extends 1 ? 2 : I extends 2 ? 3 : I extends 3 ? 4 : I extends 4 ? 5 : I extends 5 ? 6 : I extends 6 ? 7 : I extends 7 ? 8 : I extends 8 ? 9 : 10} Increment
64+
* Increment a number in the type system.
65+
* @template {Uint} [I=0]
66+
* Index.
67+
*/
68+
69+
/**
70+
* @typedef {(
71+
* Node extends UnistParent
72+
* ? Node extends {children: Array<infer Children>}
73+
* ? Child extends Children ? Node : never
74+
* : never
75+
* : never
76+
* )} InternalParent
77+
* Collect nodes that can be parents of `Child`.
78+
* @template {UnistNode} Node
79+
* All node types in a tree.
2280
* @template {UnistNode} Child
23-
* Node type.
81+
* Node to search for.
82+
*/
83+
84+
/**
85+
* @typedef {InternalParent<InclusiveDescendant<Tree>, Child>} Parent
86+
* Collect nodes in `Tree` that can be parents of `Child`.
87+
* @template {UnistNode} Tree
88+
* All node types in a tree.
89+
* @template {UnistNode} Child
90+
* Node to search for.
91+
*/
92+
93+
/**
94+
* @typedef {(
95+
* Depth extends Max
96+
* ? never
97+
* :
98+
* | InternalParent<Node, Child>
99+
* | InternalAncestor<Node, InternalParent<Node, Child>, Max, Increment<Depth>>
100+
* )} InternalAncestor
101+
* Collect nodes in `Tree` that can be ancestors of `Child`.
102+
* @template {UnistNode} Node
103+
* All node types in a tree.
104+
* @template {UnistNode} Child
105+
* Node to search for.
106+
* @template {Uint} [Max=10]
107+
* Max; searches up to this depth.
108+
* @template {Uint} [Depth=0]
109+
* Current depth.
110+
*/
111+
112+
/**
113+
* @typedef {(
114+
* Tree extends UnistParent
115+
* ? Depth extends Max
116+
* ? Tree
117+
* : Tree | InclusiveDescendant<Tree['children'][number], Max, Increment<Depth>>
118+
* : Tree
119+
* )} InclusiveDescendant
120+
* Collect all (inclusive) descendants of `Tree`.
121+
*
122+
* > 👉 **Note**: for performance reasons, this seems to be the fastest way to
123+
* > recurse without actually running into an infinite loop, which the
124+
* > previous version did.
125+
* >
126+
* > Practically, a max of `2` is typically enough assuming a `Root` is
127+
* > passed, but it doesn’t improve performance.
128+
* > It gets higher with `List > ListItem > Table > TableRow > TableCell`.
129+
* > Using up to `10` doesn’t hurt or help either.
130+
* @template {UnistNode} Tree
131+
* Tree type.
132+
* @template {Uint} [Max=10]
133+
* Max; searches up to this depth.
134+
* @template {Uint} [Depth=0]
135+
* Current depth.
24136
*/
25137

26138
/**
@@ -45,7 +157,7 @@
45157
* Found node.
46158
* @param {Visited extends UnistNode ? number | undefined : never} index
47159
* Index of `node` in `parent`.
48-
* @param {Ancestor extends UnistNode ? Ancestor | undefined : never} parent
160+
* @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent
49161
* Parent of `node`.
50162
* @returns {VisitorResult}
51163
* What to do next.
@@ -63,7 +175,7 @@
63175
*/
64176

65177
/**
66-
* @typedef {Visitor<Visited, ParentsOf<Ancestor, Visited>>} BuildVisitorFromMatch
178+
* @typedef {Visitor<Visited, Parent<Ancestor, Visited>>} BuildVisitorFromMatch
67179
* Build a typed `Visitor` function from a node and all possible parents.
68180
*
69181
* It will infer which values are passed as `node` and which as `parent`.
@@ -76,7 +188,7 @@
76188
/**
77189
* @typedef {(
78190
* BuildVisitorFromMatch<
79-
* import('unist-util-visit-parents/complex-types.js').Matches<Descendant, Check>,
191+
* Matches<Descendant, Check>,
80192
* Extract<Descendant, UnistParent>
81193
* >
82194
* )} BuildVisitorFromDescendants
@@ -92,7 +204,7 @@
92204
/**
93205
* @typedef {(
94206
* BuildVisitorFromDescendants<
95-
* import('unist-util-visit-parents/complex-types.js').InclusiveDescendant<Tree>,
207+
* InclusiveDescendant<Tree>,
96208
* Check
97209
* >
98210
* )} BuildVisitor

‎package.json

+4
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,10 @@
9292
"typeCoverage": {
9393
"atLeast": 100,
9494
"detail": true,
95+
"#": "needed `any`s",
96+
"ignoreFiles": [
97+
"lib/index.d.ts"
98+
],
9599
"ignoreCatch": true,
96100
"strict": true
97101
},

0 commit comments

Comments
 (0)
Please sign in to comment.