From 3cb2732d7e0697247f98b1780f9c1e1eb759aabb Mon Sep 17 00:00:00 2001 From: Titus Wormer Date: Thu, 6 Jul 2023 12:32:36 +0200 Subject: [PATCH] Fix performance of `InclusiveDescendant` type --- index.test-d.ts | 5 +- lib/index.js | 144 ++++++++++++++++++++++++++++++++++++++++++------ package.json | 4 ++ 3 files changed, 134 insertions(+), 19 deletions(-) diff --git a/index.test-d.ts b/index.test-d.ts index 6de53d0..cf964e0 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -55,7 +55,7 @@ visit(implicitTree, function (node, index, parent) { expectAssignable(node) expectNotType(node) expectType(index) - expectType(parent) + expectAssignable(parent) }) // ## String test @@ -126,8 +126,7 @@ visit(sampleTree, isHeading, function (node) { }) // Function test (explicit assertion). visit(sampleTree, isHeading2, function (node) { - // To do: improving `InclusiveDescendant` should use `Heading & {depth: 2}`. - expectType(node) + expectType(node) }) // ## Combined tests diff --git a/lib/index.js b/lib/index.js index 11ceef1..aac4691 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,22 +5,134 @@ * @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult */ +// To do: use types from `unist-util-visit-parents` when it’s released. + +/** + * @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 {( - * Ancestor extends UnistParent - * ? Child extends Ancestor['children'][number] - * ? Ancestor - * : never + * 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 extends Value + * ? Predicate * : never - * )} ParentsOf - * Check if `Child` can be a child of `Ancestor`. - * - * Returns the ancestor when `Child` can be a child of `Ancestor`, or returns - * `never`. - * @template {UnistNode} Ancestor - * Node type. + * : 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 + * ? MatchesOne + * : MatchesOne + * )} 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 {0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10} Uint + * Number; capped reasonably. + */ + +/** + * @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 + * Increment a number in the type system. + * @template {Uint} [I=0] + * Index. + */ + +/** + * @typedef {( + * Node extends UnistParent + * ? Node extends {children: Array} + * ? Child extends Children ? Node : never + * : never + * : never + * )} InternalParent + * Collect nodes that can be parents of `Child`. + * @template {UnistNode} Node + * All node types in a tree. * @template {UnistNode} Child - * Node type. + * Node to search for. + */ + +/** + * @typedef {InternalParent, Child>} Parent + * Collect nodes in `Tree` that can be parents of `Child`. + * @template {UnistNode} Tree + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + */ + +/** + * @typedef {( + * Depth extends Max + * ? never + * : + * | InternalParent + * | InternalAncestor, Max, Increment> + * )} InternalAncestor + * Collect nodes in `Tree` that can be ancestors of `Child`. + * @template {UnistNode} Node + * All node types in a tree. + * @template {UnistNode} Child + * Node to search for. + * @template {Uint} [Max=10] + * Max; searches up to this depth. + * @template {Uint} [Depth=0] + * Current depth. + */ + +/** + * @typedef {( + * Tree extends UnistParent + * ? Depth extends Max + * ? Tree + * : Tree | InclusiveDescendant> + * : Tree + * )} InclusiveDescendant + * Collect all (inclusive) descendants of `Tree`. + * + * > 👉 **Note**: for performance reasons, this seems to be the fastest way to + * > recurse without actually running into an infinite loop, which the + * > previous version did. + * > + * > Practically, a max of `2` is typically enough assuming a `Root` is + * > passed, but it doesn’t improve performance. + * > It gets higher with `List > ListItem > Table > TableRow > TableCell`. + * > Using up to `10` doesn’t hurt or help either. + * @template {UnistNode} Tree + * Tree type. + * @template {Uint} [Max=10] + * Max; searches up to this depth. + * @template {Uint} [Depth=0] + * Current depth. */ /** @@ -45,7 +157,7 @@ * Found node. * @param {Visited extends UnistNode ? number | undefined : never} index * Index of `node` in `parent`. - * @param {Ancestor extends UnistNode ? Ancestor | undefined : never} parent + * @param {Ancestor extends UnistParent ? Ancestor | undefined : never} parent * Parent of `node`. * @returns {VisitorResult} * What to do next. @@ -63,7 +175,7 @@ */ /** - * @typedef {Visitor>} BuildVisitorFromMatch + * @typedef {Visitor>} BuildVisitorFromMatch * Build a typed `Visitor` function from a node and all possible parents. * * It will infer which values are passed as `node` and which as `parent`. @@ -76,7 +188,7 @@ /** * @typedef {( * BuildVisitorFromMatch< - * import('unist-util-visit-parents/complex-types.js').Matches, + * Matches, * Extract * > * )} BuildVisitorFromDescendants @@ -92,7 +204,7 @@ /** * @typedef {( * BuildVisitorFromDescendants< - * import('unist-util-visit-parents/complex-types.js').InclusiveDescendant, + * InclusiveDescendant, * Check * > * )} BuildVisitor diff --git a/package.json b/package.json index 5cb6d0e..e09ef83 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,10 @@ "typeCoverage": { "atLeast": 100, "detail": true, + "#": "needed `any`s", + "ignoreFiles": [ + "lib/index.d.ts" + ], "ignoreCatch": true, "strict": true },