Skip to content

Commit d63b8a6

Browse files
ChristianMurphywooorm
authored andcommitted
Add typings
Related-to: unifiedjs/unified#65. Related-to: syntax-tree/unist-util-visit-parents#7. Closes GH-15. Reviewed-by: Junyoung Choi <[email protected]> Reviewed-by: Titus Wormer <[email protected]>
1 parent 3cb11ec commit d63b8a6

5 files changed

Lines changed: 360 additions & 4 deletions

File tree

package.json

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,38 @@
2525
"Richard Gibson <[email protected]>"
2626
],
2727
"files": [
28-
"index.js"
28+
"index.js",
29+
"types/index.d.ts"
2930
],
31+
"types": "types/index.d.ts",
3032
"dependencies": {
31-
"unist-util-visit-parents": "^2.0.0"
33+
"@types/unist": "^2.0.0",
34+
"unist-util-is": "^4.0.0",
35+
"unist-util-visit-parents": "^3.0.0"
3236
},
3337
"devDependencies": {
3438
"browserify": "^16.0.0",
39+
"dtslint": "^0.9.0",
3540
"nyc": "^14.0.0",
3641
"prettier": "^1.0.0",
3742
"remark": "^10.0.0",
3843
"remark-cli": "^6.0.0",
3944
"remark-preset-wooorm": "^5.0.0",
4045
"tape": "^4.0.0",
4146
"tinyify": "^2.0.0",
47+
"typescript": "^3.5.3",
48+
"unified": "^8.3.2",
4249
"xo": "^0.24.0"
4350
},
4451
"scripts": {
45-
"format": "remark . -qfo && prettier --write \"**/*.js\" && xo --fix",
52+
"format": "remark . -qfo && prettier --write \"**/*.{js,ts}\" && xo --fix",
4653
"build-bundle": "browserify . -s unistUtilVisit > unist-util-visit.js",
4754
"build-mangle": "browserify . -s unistUtilVisit -p tinyify > unist-util-visit.min.js",
4855
"build": "npm run build-bundle && npm run build-mangle",
4956
"test-api": "node test",
5057
"test-coverage": "nyc --reporter lcov tape test.js",
51-
"test": "npm run format && npm run build && npm run test-coverage"
58+
"test-types": "dtslint types",
59+
"test": "npm run format && npm run build && npm run test-coverage && npm run test-types"
5260
},
5361
"nyc": {
5462
"check-coverage": true,

types/index.d.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// TypeScript Version: 3.5
2+
3+
import {Node, Parent} from 'unist'
4+
import {Test} from 'unist-util-is'
5+
import {
6+
Action,
7+
ActionTuple,
8+
Continue,
9+
Exit,
10+
Index,
11+
Skip
12+
} from 'unist-util-visit-parents'
13+
14+
declare namespace visit {
15+
/**
16+
* Invoked when a node (matching test, if given) is found.
17+
* Visitors are free to transform node.
18+
* They can also transform the parent of node.
19+
* Replacing node itself, if visit.SKIP is not returned, still causes its descendants to be visited.
20+
* If adding or removing previous siblings (or next siblings, in case of reverse) of node,
21+
* visitor should return a new index (number) to specify the sibling to traverse after node is traversed.
22+
* Adding or removing next siblings of node (or previous siblings, in case of reverse)
23+
* is handled as expected without needing to return a new index.
24+
* Removing the children property of the parent still result in them being traversed.
25+
*
26+
* @param node Found node
27+
* @param index Position of found node within Parent
28+
* @param parent Parent of found node
29+
* @paramType V node type found
30+
* @returns
31+
* When Action is passed, treated as a tuple of [Action]
32+
* When Index is passed, treated as a tuple of [CONTINUE, Index]
33+
* When ActionTuple is passed,
34+
* Note that passing a tuple only makes sense if the action is SKIP.
35+
* If the action is EXIT, that action can be returned.
36+
* If the action is CONTINUE, index can be returned.
37+
*/
38+
type Visitor<V extends Node> = (
39+
node: V,
40+
index: number,
41+
parent: Node
42+
) => void | Action | Index | ActionTuple
43+
}
44+
45+
declare const visit: {
46+
/**
47+
* Visit children of tree which pass a test
48+
*
49+
* @param tree abstract syntax tree to visit
50+
* @param test test node
51+
* @param visitor function to run for each node
52+
* @param reverse visit the tree in reverse, defaults to false
53+
* @typeParam T tree node
54+
* @typeParam V node type found
55+
*/
56+
<V extends Node>(
57+
tree: Node,
58+
test: Test<V> | Array<Test<any>>,
59+
visitor: visit.Visitor<V>,
60+
reverse?: boolean
61+
): void
62+
63+
/**
64+
* Visit children of a tree
65+
*
66+
* @param tree abstract syntax tree to visit
67+
* @param visitor function to run for each node
68+
* @param reverse visit the tree in reverse, defaults to false
69+
*/
70+
(tree: Node, visitor: visit.Visitor<Node>, reverse?: boolean): void
71+
72+
/**
73+
* Continue traversing as normal
74+
*/
75+
CONTINUE: Continue
76+
77+
/**
78+
* Do not traverse this node’s children
79+
*/
80+
SKIP: Skip
81+
82+
/**
83+
* Stop traversing immediately
84+
*/
85+
EXIT: Exit
86+
}
87+
88+
export = visit

types/tsconfig.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"compilerOptions": {
3+
"lib": ["es2015"],
4+
"strict": true,
5+
"noImplicitAny": true,
6+
"noImplicitThis": true,
7+
"strictNullChecks": true,
8+
"strictFunctionTypes": true,
9+
"baseUrl": ".",
10+
"paths": {
11+
"unist-util-visit": ["index.d.ts"]
12+
}
13+
}
14+
}

types/tslint.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"extends": "dtslint/dtslint.json",
3+
"rules": {
4+
"callable-types": false,
5+
"max-line-length": false,
6+
"no-redundant-jsdoc": false,
7+
"no-void-expression": false,
8+
"only-arrow-functions": false,
9+
"semicolon": false,
10+
"unified-signatures": false,
11+
"whitespace": false,
12+
"interface-over-type-literal": false,
13+
"no-unnecessary-generics": false
14+
}
15+
}

types/unist-util-visit-tests.ts

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import {Node, Parent} from 'unist'
2+
import unified = require('unified')
3+
import visit = require('unist-util-visit')
4+
5+
/*=== setup ===*/
6+
const sampleTree = {
7+
type: 'root',
8+
children: [
9+
{
10+
type: 'heading',
11+
depth: 1,
12+
children: []
13+
}
14+
]
15+
}
16+
17+
interface Heading extends Parent {
18+
type: 'heading'
19+
depth: number
20+
children: Node[]
21+
}
22+
23+
interface Element extends Parent {
24+
type: 'element'
25+
tagName: string
26+
properties: {
27+
[key: string]: unknown
28+
}
29+
content: Node
30+
children: Node[]
31+
}
32+
33+
const isNode = (node: unknown): node is Node =>
34+
typeof node === 'object' && !!node && 'type' in node
35+
const headingTest = (node: unknown): node is Heading =>
36+
isNode(node) && node.type === 'heading'
37+
const elementTest = (node: unknown): node is Element =>
38+
isNode(node) && node.type === 'element'
39+
40+
/*=== missing params ===*/
41+
// $ExpectError
42+
visit()
43+
// $ExpectError
44+
visit(sampleTree)
45+
46+
/*=== visit without test ===*/
47+
visit(sampleTree, node => {})
48+
visit(sampleTree, (node: Node) => {})
49+
// $ExpectError
50+
visit(sampleTree, (node: Element) => {})
51+
// $ExpectError
52+
visit(sampleTree, (node: Heading) => {})
53+
54+
/*=== visit with type test ===*/
55+
visit(sampleTree, 'heading', node => {})
56+
visit(sampleTree, 'heading', (node: Heading) => {})
57+
// $ExpectError
58+
visit(sampleTree, 'not-a-heading', (node: Heading) => {})
59+
// $ExpectError
60+
visit(sampleTree, 'element', (node: Heading) => {})
61+
62+
visit(sampleTree, 'element', node => {})
63+
visit(sampleTree, 'element', (node: Element) => {})
64+
// $ExpectError
65+
visit(sampleTree, 'not-an-element', (node: Element) => {})
66+
// $ExpectError
67+
visit(sampleTree, 'heading', (node: Element) => {})
68+
69+
/*=== visit with object test ===*/
70+
visit(sampleTree, {type: 'heading'}, node => {})
71+
visit(sampleTree, {random: 'property'}, node => {})
72+
73+
visit(sampleTree, {type: 'heading'}, (node: Heading) => {})
74+
visit(sampleTree, {type: 'heading', depth: 2}, (node: Heading) => {})
75+
// $ExpectError
76+
visit(sampleTree, {type: 'element'}, (node: Heading) => {})
77+
// $ExpectError
78+
visit(sampleTree, {type: 'heading', depth: '2'}, (node: Heading) => {})
79+
80+
visit(sampleTree, {type: 'element'}, (node: Element) => {})
81+
visit(sampleTree, {type: 'element', tagName: 'section'}, (node: Element) => {})
82+
// $ExpectError
83+
visit(sampleTree, {type: 'heading'}, (node: Element) => {})
84+
// $ExpectError
85+
visit(sampleTree, {type: 'element', tagName: true}, (node: Element) => {})
86+
87+
/*=== visit with function test ===*/
88+
visit(sampleTree, headingTest, node => {})
89+
visit(sampleTree, headingTest, (node: Heading) => {})
90+
// $ExpectError
91+
visit(sampleTree, headingTest, (node: Element) => {})
92+
93+
visit(sampleTree, elementTest, node => {})
94+
visit(sampleTree, elementTest, (node: Element) => {})
95+
// $ExpectError
96+
visit(sampleTree, elementTest, (node: Heading) => {})
97+
98+
/*=== visit with array of tests ===*/
99+
visit(sampleTree, ['ParagraphNode', {type: 'element'}, headingTest], node => {})
100+
101+
/*=== visit returns action ===*/
102+
visit(sampleTree, 'heading', node => visit.CONTINUE)
103+
visit(sampleTree, 'heading', node => visit.EXIT)
104+
visit(sampleTree, 'heading', node => visit.SKIP)
105+
visit(sampleTree, 'heading', node => true)
106+
visit(sampleTree, 'heading', node => false)
107+
visit(sampleTree, 'heading', node => 'skip')
108+
// $ExpectError
109+
visit(sampleTree, 'heading', node => 'random')
110+
111+
/*=== visit returns index ===*/
112+
visit(sampleTree, 'heading', node => 0)
113+
visit(sampleTree, 'heading', node => 1)
114+
115+
/*=== visit returns tuple ===*/
116+
visit(sampleTree, 'heading', node => [visit.CONTINUE, 1])
117+
visit(sampleTree, 'heading', node => [visit.EXIT, 1])
118+
visit(sampleTree, 'heading', node => [visit.SKIP, 1])
119+
visit(sampleTree, 'heading', node => [true, 1])
120+
visit(sampleTree, 'heading', node => [false, 1])
121+
visit(sampleTree, 'heading', node => ['skip', 1])
122+
// $ExpectError
123+
visit(sampleTree, 'heading', node => ['skip'])
124+
// $ExpectError
125+
visit(sampleTree, 'heading', node => [1])
126+
// $ExpectError
127+
visit(sampleTree, 'heading', node => ['random', 1])
128+
129+
/*=== usage as unified plugin ===*/
130+
unified().use(() => sampleTree => {
131+
// duplicates the above type tests but passes in the unified transformer input
132+
133+
/*=== missing params ===*/
134+
// $ExpectError
135+
visit()
136+
// $ExpectError
137+
visit(sampleTree)
138+
139+
/*=== visit without test ===*/
140+
visit(sampleTree, node => {})
141+
visit(sampleTree, (node: Node) => {})
142+
// $ExpectError
143+
visit(sampleTree, (node: Element) => {})
144+
// $ExpectError
145+
visit(sampleTree, (node: Heading) => {})
146+
147+
/*=== visit with type test ===*/
148+
visit(sampleTree, 'heading', node => {})
149+
visit(sampleTree, 'heading', (node: Heading) => {})
150+
// $ExpectError
151+
visit(sampleTree, 'not-a-heading', (node: Heading) => {})
152+
// $ExpectError
153+
visit(sampleTree, 'element', (node: Heading) => {})
154+
155+
visit(sampleTree, 'element', node => {})
156+
visit(sampleTree, 'element', (node: Element) => {})
157+
// $ExpectError
158+
visit(sampleTree, 'not-an-element', (node: Element) => {})
159+
// $ExpectError
160+
visit(sampleTree, 'heading', (node: Element) => {})
161+
162+
/*=== visit with object test ===*/
163+
visit(sampleTree, {type: 'heading'}, node => {})
164+
visit(sampleTree, {random: 'property'}, node => {})
165+
166+
visit(sampleTree, {type: 'heading'}, (node: Heading) => {})
167+
visit(sampleTree, {type: 'heading', depth: 2}, (node: Heading) => {})
168+
// $ExpectError
169+
visit(sampleTree, {type: 'element'}, (node: Heading) => {})
170+
// $ExpectError
171+
visit(sampleTree, {type: 'heading', depth: '2'}, (node: Heading) => {})
172+
173+
visit(sampleTree, {type: 'element'}, (node: Element) => {})
174+
visit(
175+
sampleTree,
176+
{type: 'element', tagName: 'section'},
177+
(node: Element) => {}
178+
)
179+
// $ExpectError
180+
visit(sampleTree, {type: 'heading'}, (node: Element) => {})
181+
// $ExpectError
182+
visit(sampleTree, {type: 'element', tagName: true}, (node: Element) => {})
183+
184+
/*=== visit with function test ===*/
185+
visit(sampleTree, headingTest, node => {})
186+
visit(sampleTree, headingTest, (node: Heading) => {})
187+
// $ExpectError
188+
visit(sampleTree, headingTest, (node: Element) => {})
189+
190+
visit(sampleTree, elementTest, node => {})
191+
visit(sampleTree, elementTest, (node: Element) => {})
192+
// $ExpectError
193+
visit(sampleTree, elementTest, (node: Heading) => {})
194+
195+
/*=== visit with array of tests ===*/
196+
visit(
197+
sampleTree,
198+
['ParagraphNode', {type: 'element'}, headingTest],
199+
node => {}
200+
)
201+
202+
/*=== visit returns action ===*/
203+
visit(sampleTree, 'heading', node => visit.CONTINUE)
204+
visit(sampleTree, 'heading', node => visit.EXIT)
205+
visit(sampleTree, 'heading', node => visit.SKIP)
206+
visit(sampleTree, 'heading', node => true)
207+
visit(sampleTree, 'heading', node => false)
208+
visit(sampleTree, 'heading', node => 'skip')
209+
// $ExpectError
210+
visit(sampleTree, 'heading', node => 'random')
211+
212+
/*=== visit returns index ===*/
213+
visit(sampleTree, 'heading', node => 0)
214+
visit(sampleTree, 'heading', node => 1)
215+
216+
/*=== visit returns tuple ===*/
217+
visit(sampleTree, 'heading', node => [visit.CONTINUE, 1])
218+
visit(sampleTree, 'heading', node => [visit.EXIT, 1])
219+
visit(sampleTree, 'heading', node => [visit.SKIP, 1])
220+
visit(sampleTree, 'heading', node => [true, 1])
221+
visit(sampleTree, 'heading', node => [false, 1])
222+
visit(sampleTree, 'heading', node => ['skip', 1])
223+
// $ExpectError
224+
visit(sampleTree, 'heading', node => ['skip'])
225+
// $ExpectError
226+
visit(sampleTree, 'heading', node => [1])
227+
// $ExpectError
228+
visit(sampleTree, 'heading', node => ['random', 1])
229+
230+
return sampleTree
231+
})

0 commit comments

Comments
 (0)