Skip to content

Commit 3eb1a53

Browse files
ChristianMurphyRokt33r
authored andcommitted
Add typings
Closes GH-9. Reviewed-by: Junyoung Choi <[email protected]> Reviewed-by: Titus Wormer <[email protected]> Co-authored-by: Junyoung Choi <[email protected]>
1 parent aee63b5 commit 3eb1a53

File tree

6 files changed

+302
-3
lines changed

6 files changed

+302
-3
lines changed

convert.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import {Test, TestFunction} from './'
2+
import {Node} from 'unist'
3+
4+
declare function convert<T extends Node>(test: Test<T>): TestFunction<T>
5+
6+
export = convert

index.d.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// TypeScript Version: 3.5
2+
3+
import {Node, Parent} from 'unist'
4+
5+
declare namespace unistUtilIs {
6+
/**
7+
* Check that type property matches expectation for a node
8+
*
9+
* @typeParam T type of node that passes test
10+
*/
11+
type TestType<T extends Node> = T['type']
12+
13+
/**
14+
* Check that some attributes on a node are matched
15+
*
16+
* @typeParam T type of node that passes test
17+
*/
18+
type TestObject<T extends Node> = Partial<T>
19+
20+
/**
21+
* Check if a node passes a test
22+
*
23+
* @param node node to check
24+
* @param index index of node in parent
25+
* @param parent parent of node
26+
* @typeParam T type of node that passes test
27+
* @returns true if type T is found, false otherwise
28+
*/
29+
type TestFunction<T extends Node> = (
30+
node: unknown,
31+
index?: number,
32+
parent?: Parent
33+
) => node is T
34+
35+
/**
36+
* Union of all the types of tests
37+
*
38+
* @typeParam T type of node that passes test
39+
*/
40+
type Test<T extends Node> = TestType<T> | TestObject<T> | TestFunction<T>
41+
}
42+
43+
/**
44+
* Unist utility to check if a node passes a test.
45+
*
46+
* @param node Node to check.
47+
* @param test When not given, checks if `node` is a `Node`.
48+
* When `string`, works like passing `function (node) {return node.type === test}`.
49+
* When `function` checks if function passed the node is true.
50+
* When `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
51+
* When `array`, checks any one of the subtests pass.
52+
* @param index Position of `node` in `parent`
53+
* @param parent Parent of `node`
54+
* @param context Context object to invoke `test` with
55+
* @typeParam T type that node is compared with
56+
* @returns Whether test passed and `node` is a `Node` (object with `type` set to non-empty `string`).
57+
*/
58+
declare function unistUtilIs<T extends Node>(
59+
node: unknown,
60+
test: unistUtilIs.Test<T> | Array<unistUtilIs.Test<any>>,
61+
index?: number,
62+
parent?: Parent,
63+
context?: any
64+
): node is T
65+
66+
export = unistUtilIs

package.json

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,34 @@
2121
],
2222
"files": [
2323
"index.js",
24-
"convert.js"
24+
"convert.js",
25+
"index.d.ts",
26+
"convert.d.ts"
2527
],
28+
"types": "index.d.ts",
2629
"dependencies": {},
2730
"devDependencies": {
2831
"browserify": "^16.0.0",
32+
"dtslint": "^0.9.0",
2933
"nyc": "^14.0.0",
3034
"prettier": "^1.0.0",
3135
"remark-cli": "^6.0.0",
3236
"remark-preset-wooorm": "^5.0.0",
3337
"tape": "^4.0.0",
3438
"tinyify": "^2.0.0",
39+
"typescript": "^3.5.3",
40+
"unified": "^8.3.2",
3541
"xo": "^0.24.0"
3642
},
3743
"scripts": {
38-
"format": "remark . -qfo && prettier --write \"**/*.js\" && xo --fix",
44+
"format": "remark . -qfo && prettier --write \"**/*.{js,ts}\" && xo --fix",
3945
"build-bundle": "browserify . -s unistUtilIs > unist-util-is.js",
4046
"build-mangle": "browserify . -s unistUtilIs -p tinyify > unist-util-is.min.js",
4147
"build": "npm run build-bundle && npm run build-mangle",
4248
"test-api": "node test",
4349
"test-coverage": "nyc --reporter lcov tape test.js",
44-
"test": "npm run format && npm run build && npm run test-coverage"
50+
"test-types": "dtslint .",
51+
"test": "npm run format && npm run build && npm run test-coverage && npm run test-types"
4552
},
4653
"prettier": {
4754
"tabWidth": 2,

tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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-is": ["index.d.ts"],
12+
"unist-util-is/convert": ["convert.d.ts"]
13+
}
14+
}
15+
}

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+
}

unist-util-is-test.ts

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import {Node, Parent} from 'unist'
2+
import unified = require('unified')
3+
import is = require('unist-util-is')
4+
import convert = require('unist-util-is/convert')
5+
6+
/*=== setup ===*/
7+
interface Heading extends Parent {
8+
type: 'heading'
9+
depth: number
10+
children: Node[]
11+
}
12+
13+
interface Element extends Parent {
14+
type: 'element'
15+
tagName: string
16+
properties: {
17+
[key: string]: unknown
18+
}
19+
content: Node
20+
children: Node[]
21+
}
22+
23+
interface Paragraph extends Parent {
24+
type: 'ParagraphNode'
25+
}
26+
27+
const heading: Node = {
28+
type: 'heading',
29+
depth: 2,
30+
children: []
31+
}
32+
33+
const element: Node = {
34+
type: 'element',
35+
tagName: 'section',
36+
properties: {},
37+
content: {type: 'text'},
38+
children: []
39+
}
40+
41+
const isHeading = (node: unknown): node is Heading =>
42+
typeof node === 'object' && node !== null && (node as Node).type === 'heading'
43+
const isElement = (node: unknown): node is Element =>
44+
typeof node === 'object' && node !== null && (node as Node).type === 'element'
45+
46+
/*=== types cannot be narrowed without predicate ===*/
47+
// $ExpectError
48+
const maybeHeading: Heading = heading
49+
// $ExpectError
50+
const maybeElement: Element = element
51+
52+
/*=== missing params ===*/
53+
// $ExpectError
54+
is()
55+
// $ExpectError
56+
is<Node>()
57+
// $ExpectError
58+
is<Node>(heading)
59+
60+
/*=== invalid generic ===*/
61+
// $ExpectError
62+
is<string>(heading, 'heading')
63+
// $ExpectError
64+
is<boolean>(heading, 'heading')
65+
// $ExpectError
66+
is<{}>(heading, 'heading')
67+
68+
/*=== assignable to boolean ===*/
69+
const wasItAHeading: boolean = is<Heading>(heading, 'heading')
70+
71+
/*=== type string test ===*/
72+
is<Heading>(heading, 'heading')
73+
is<Heading>(element, 'heading')
74+
// $ExpectError
75+
is<Heading>(heading, 'element')
76+
77+
if (is<Heading>(heading, 'heading')) {
78+
const maybeHeading: Heading = heading
79+
// $ExpectError
80+
const maybeNotHeading: Element = heading
81+
}
82+
83+
is<Element>(element, 'element')
84+
is<Element>(heading, 'element')
85+
// $ExpectError
86+
is<Element>(element, 'heading')
87+
88+
if (is<Element>(element, 'element')) {
89+
const maybeElement: Element = element
90+
// $ExpectError
91+
const maybeNotElement: Heading = element
92+
}
93+
94+
/*=== type predicate function test ===*/
95+
is(heading, isHeading)
96+
is(element, isHeading)
97+
// $ExpectError
98+
is<Heading>(heading, isElement)
99+
100+
if (is(heading, isHeading)) {
101+
const maybeHeading: Heading = heading
102+
// $ExpectError
103+
const maybeNotHeading: Element = heading
104+
}
105+
106+
is(element, isElement)
107+
is(heading, isElement)
108+
// $ExpectError
109+
is<Element>(element, isHeading)
110+
111+
if (is(element, isElement)) {
112+
const maybeElement: Element = element
113+
// $ExpectError
114+
const maybeNotElement: Heading = element
115+
}
116+
117+
/*=== type object test ===*/
118+
is<Heading>(heading, {type: 'heading', depth: 2})
119+
is<Heading>(element, {type: 'heading', depth: 2})
120+
// $ExpectError
121+
is<Heading>(heading, {type: 'heading', depth: '2'})
122+
123+
if (is<Heading>(heading, {type: 'heading', depth: 2})) {
124+
const maybeHeading: Heading = heading
125+
// $ExpectError
126+
const maybeNotHeading: Element = heading
127+
}
128+
129+
is<Element>(element, {type: 'element', tagName: 'section'})
130+
is<Element>(heading, {type: 'element', tagName: 'section'})
131+
// $ExpectError
132+
is<Element>(element, {type: 'element', tagName: true})
133+
134+
if (is<Element>(element, {type: 'element', tagName: 'section'})) {
135+
const maybeElement: Element = element
136+
// $ExpectError
137+
const maybeNotElement: Heading = element
138+
}
139+
140+
/*=== type array of tests ===*/
141+
is<Heading | Element | Paragraph>(heading, [
142+
'heading',
143+
isElement,
144+
{type: 'ParagraphNode'}
145+
])
146+
if (
147+
is<Heading | Element | Paragraph>(heading, [
148+
'heading',
149+
isElement,
150+
{type: 'ParagraphNode'}
151+
])
152+
) {
153+
switch (heading.type) {
154+
case 'heading': {
155+
heading // $ExpectType Heading
156+
break
157+
}
158+
case 'element': {
159+
heading // $ExpectType Element
160+
break
161+
}
162+
case 'ParagraphNode': {
163+
heading // $ExpectType Paragraph
164+
break
165+
}
166+
// $ExpectError
167+
case 'dne': {
168+
break
169+
}
170+
}
171+
}
172+
173+
/*=== usable in unified transform ===*/
174+
unified().use(() => tree => {
175+
if (is<Heading>(tree, 'heading')) {
176+
// do something
177+
}
178+
return tree
179+
})
180+
181+
/*=== convert ===*/
182+
convert<Heading>('heading')
183+
// $ExpectError
184+
convert<Heading>('element')
185+
convert<Heading>({type: 'heading', depth: 2})
186+
// $ExpectError
187+
convert<Element>({type: 'heading', depth: 2})
188+
convert<Heading>(isHeading)
189+
// $ExpectError
190+
convert<Element>(isHeading)

0 commit comments

Comments
 (0)