Skip to content

Commit

Permalink
Fix performance of InclusiveDescendant type
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jul 6, 2023
1 parent 12c9ee9 commit 3cb2732
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 19 deletions.
5 changes: 2 additions & 3 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ visit(implicitTree, function (node, index, parent) {
expectAssignable<Node>(node)
expectNotType<Node>(node)
expectType<number | undefined>(index)
expectType<never>(parent)
expectAssignable<Parent | undefined>(parent)
})

// ## String test
Expand Down Expand Up @@ -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<never>(node)
expectType<Heading & {depth: 2}>(node)
})

// ## Combined tests
Expand Down
144 changes: 128 additions & 16 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<Check, Value> extends Value
* ? Predicate<Check, Value>
* : 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<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 {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<infer Children>}
* ? 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<InclusiveDescendant<Tree>, 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<Node, Child>
* | InternalAncestor<Node, InternalParent<Node, Child>, Max, Increment<Depth>>
* )} 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['children'][number], Max, Increment<Depth>>
* : 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.
*/

/**
Expand All @@ -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.
Expand All @@ -63,7 +175,7 @@
*/

/**
* @typedef {Visitor<Visited, ParentsOf<Ancestor, Visited>>} BuildVisitorFromMatch
* @typedef {Visitor<Visited, Parent<Ancestor, Visited>>} 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`.
Expand All @@ -76,7 +188,7 @@
/**
* @typedef {(
* BuildVisitorFromMatch<
* import('unist-util-visit-parents/complex-types.js').Matches<Descendant, Check>,
* Matches<Descendant, Check>,
* Extract<Descendant, UnistParent>
* >
* )} BuildVisitorFromDescendants
Expand All @@ -92,7 +204,7 @@
/**
* @typedef {(
* BuildVisitorFromDescendants<
* import('unist-util-visit-parents/complex-types.js').InclusiveDescendant<Tree>,
* InclusiveDescendant<Tree>,
* Check
* >
* )} BuildVisitor
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@
"typeCoverage": {
"atLeast": 100,
"detail": true,
"#": "needed `any`s",
"ignoreFiles": [
"lib/index.d.ts"
],
"ignoreCatch": true,
"strict": true
},
Expand Down

0 comments on commit 3cb2732

Please sign in to comment.