Skip to content

Commit a76200b

Browse files
authored
Add improved parent type
Previously, a basic `Parent` from `@types/unist` was used for the third parameter of a visitor (`parent`). This changes that to instead use an array of descendants in `tree` which implement the abstract `Parent` interface and can have `node` as a child. Closes GH-30. Closes GH-31. Related-to: syntax-tree/unist-util-visit-parents#11.
1 parent 6ca3b63 commit a76200b

File tree

6 files changed

+79
-32
lines changed

6 files changed

+79
-32
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.DS_Store
2-
*.d.ts
2+
index.d.ts
3+
test.d.ts
34
*.log
45
coverage/
56
node_modules/

complex-types.d.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import type {Node, Parent} from 'unist'
2+
import type {Test} from 'unist-util-is'
3+
import type {
4+
VisitorResult,
5+
Matches,
6+
InclusiveDescendant
7+
} from 'unist-util-visit-parents/complex-types'
8+
9+
/**
10+
* Called when a node (matching test, if given) is found.
11+
* Visitors are free to transform node.
12+
* They can also transform the parent of node (the last of ancestors).
13+
* Replacing node itself, if `SKIP` is not returned, still causes its descendants to be visited.
14+
* If adding or removing previous siblings (or next siblings, in case of reverse) of node,
15+
* visitor should return a new index (number) to specify the sibling to traverse after node is traversed.
16+
* Adding or removing next siblings of node (or previous siblings, in case of reverse)
17+
* is handled as expected without needing to return a new index.
18+
* Removing the children property of an ancestor still results in them being traversed.
19+
*/
20+
export type Visitor<
21+
Visited extends Node = Node,
22+
Ancestor extends Parent = Parent
23+
> = (
24+
node: Visited,
25+
index: Visited extends Node ? number | null : never,
26+
parent: Ancestor extends Node ? Ancestor | null : Ancestor
27+
) => VisitorResult
28+
29+
type ParentsOf<
30+
Ancestor extends Node,
31+
Child extends Node
32+
> = Ancestor extends Parent
33+
? Child extends Ancestor['children'][number]
34+
? Ancestor
35+
: never
36+
: never
37+
38+
type BuildVisitorFromMatch<
39+
Visited extends Node,
40+
Ancestor extends Parent
41+
> = Visitor<Visited, ParentsOf<Ancestor, Visited>>
42+
43+
type BuildVisitorFromDescendants<
44+
Descendant extends Node,
45+
Check extends Test
46+
> = BuildVisitorFromMatch<
47+
Matches<Descendant, Check>,
48+
Extract<Descendant, Parent>
49+
>
50+
51+
export type BuildVisitor<
52+
Tree extends Node = Node,
53+
Check extends Test = string
54+
> = BuildVisitorFromDescendants<InclusiveDescendant<Tree>, Check>

index.js

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,7 @@
33
* @typedef {import('unist').Parent} Parent
44
* @typedef {import('unist-util-is').Test} Test
55
* @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult
6-
*/
7-
8-
/**
9-
* Called when a node (matching test, if given) is found.
10-
* Visitors are free to transform node.
11-
* They can also transform the parent of node (the last of ancestors).
12-
* Replacing node itself, if `SKIP` is not returned, still causes its descendants to be visited.
13-
* If adding or removing previous siblings (or next siblings, in case of reverse) of node,
14-
* visitor should return a new index (number) to specify the sibling to traverse after node is traversed.
15-
* Adding or removing next siblings of node (or previous siblings, in case of reverse)
16-
* is handled as expected without needing to return a new index.
17-
* Removing the children property of an ancestor still results in them being traversed.
18-
*
19-
* @template {Node} V
20-
* @callback Visitor
21-
* @param {V} node Found node
22-
* @param {number|null} index Position of `node` in `parent`
23-
* @param {Parent|null} parent Parent of `node`
24-
* @returns {VisitorResult}
6+
* @typedef {import('./complex-types').Visitor} Visitor
257
*/
268

279
import {visitParents, CONTINUE, SKIP, EXIT} from 'unist-util-visit-parents'
@@ -39,15 +21,15 @@ export {CONTINUE, SKIP, EXIT}
3921
export const visit =
4022
/**
4123
* @type {(
42-
* (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: Visitor<import('unist-util-visit-parents/complex-types').Matches<import('unist-util-visit-parents/complex-types').InclusiveDescendant<Tree>, Check>>, reverse?: boolean) => void) &
43-
* (<Tree extends Node>(tree: Tree, visitor: Visitor<import('unist-util-visit-parents/complex-types').InclusiveDescendant<Tree>>, reverse?: boolean) => void)
24+
* (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types').BuildVisitor<Tree, Check>, reverse?: boolean) => void) &
25+
* (<Tree extends Node>(tree: Tree, visitor: import('./complex-types').BuildVisitor<Tree>, reverse?: boolean) => void)
4426
* )}
4527
*/
4628
(
4729
/**
4830
* @param {Node} tree
4931
* @param {Test} test
50-
* @param {Visitor<Node>} visitor
32+
* @param {import('./complex-types').Visitor} visitor
5133
* @param {boolean} [reverse]
5234
*/
5335
function (tree, test, visitor, reverse) {

index.test-d.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,21 @@ expectError(visit())
8888
expectError(visit(sampleTree))
8989

9090
/* Visit without test. */
91-
visit(sampleTree, (node) => {
91+
visit(sampleTree, (node, _, parent) => {
9292
expectType<Root | Content>(node)
93+
expectType<Extract<Root | Content, Parent> | null>(parent)
9394
})
9495

9596
/* Visit with type test. */
96-
visit(sampleTree, 'heading', (node) => {
97+
visit(sampleTree, 'heading', (node, _, parent) => {
9798
expectType<Heading>(node)
99+
expectType<Root | Blockquote | null>(parent)
98100
})
99-
visit(sampleTree, 'element', (node) => {
101+
visit(sampleTree, 'element', (node, index, parent) => {
100102
// Not in tree.
101103
expectType<never>(node)
104+
expectType<never>(index)
105+
expectType<never>(parent)
102106
})
103107
expectError(visit(sampleTree, 'heading', (_: Element) => {}))
104108

@@ -157,29 +161,34 @@ expectError(visit(sampleTree, () => [1]))
157161
expectError(visit(sampleTree, () => ['random', 1]))
158162

159163
/* Should infer children from the given tree. */
160-
visit(complexTree, (node) => {
164+
visit(complexTree, (node, _, parent) => {
161165
expectType<Root | Content>(node)
166+
expectType<Extract<Root | Content, Parent> | null>(parent)
162167
})
163168

164169
const blockquote = complexTree.children[0]
165170
if (is<Blockquote>(blockquote, 'blockquote')) {
166-
visit(blockquote, (node) => {
171+
visit(blockquote, (node, _, parent) => {
167172
expectType<Content>(node)
173+
expectType<Extract<Content, Parent> | null>(parent)
168174
})
169175
}
170176

171177
const paragraph = complexTree.children[1]
172178
if (is<Paragraph>(paragraph, 'paragraph')) {
173-
visit(paragraph, (node) => {
179+
visit(paragraph, (node, _, parent) => {
174180
expectType<Paragraph | Phrasing>(node)
181+
expectType<Paragraph | Emphasis | null>(parent)
175182
})
176183

177184
const child = paragraph.children[1]
178185

179186
if (is<Emphasis>(child, 'emphasis')) {
180-
visit(child, 'blockquote', (node) => {
187+
visit(child, 'blockquote', (node, index, parent) => {
181188
// `blockquote` does not exist in phrasing.
182189
expectType<never>(node)
190+
expectType<never>(index)
191+
expectType<never>(parent)
183192
})
184193
}
185194
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"main": "index.js",
4444
"types": "index.d.ts",
4545
"files": [
46+
"complex-types.d.ts",
4647
"index.d.ts",
4748
"index.js"
4849
],
@@ -68,7 +69,7 @@
6869
},
6970
"scripts": {
7071
"prepack": "npm run build && npm run format",
71-
"build": "rimraf \"*.d.ts\" && tsc && tsd && type-coverage",
72+
"build": "rimraf \"{index,test}.d.ts\" && tsc && tsd && type-coverage",
7273
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
7374
"test-api": "node test.js",
7475
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js",

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"include": ["*.js"],
2+
"include": ["index.js", "test.js"],
33
"compilerOptions": {
44
"target": "ES2020",
55
"lib": ["ES2020"],

0 commit comments

Comments
 (0)