Skip to content

Commit bf5c388

Browse files
committed
Add JSDoc based types
1 parent 5fa466f commit bf5c388

10 files changed

+155
-117
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
.DS_Store
2+
*.d.ts
3+
*.log
24
node_modules/
35
coverage/

index.js

+84-34
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,100 @@
11
import {convert} from 'unist-util-is'
22

3+
/**
4+
* @typedef {import('unist').Node} Node
5+
* @typedef {import('unist').Parent} Parent
6+
*
7+
* @typedef {import('unist-util-is').Type} Type
8+
* @typedef {import('unist-util-is').Props} Props
9+
* @typedef {import('unist-util-is').TestFunctionAnything} TestFunctionAnything
10+
*/
11+
12+
/**
13+
* Options for unist util filter
14+
*
15+
* @typedef {Object} FilterOptions
16+
* @property {boolean} [cascade=true] Whether to drop parent nodes if they had children, but all their children were filtered out.
17+
*/
18+
319
var own = {}.hasOwnProperty
420

5-
export function filter(tree, options, test) {
6-
var is = convert(test || options)
7-
var cascade =
8-
options.cascade === undefined || options.cascade === null
9-
? true
10-
: options.cascade
21+
export const filter =
22+
/**
23+
* @type {(
24+
* (<T extends Node>(node: Node, options: FilterOptions, test: T['type']|Partial<T>|import('unist-util-is').TestFunctionPredicate<T>|Array.<T['type']|Partial<T>|import('unist-util-is').TestFunctionPredicate<T>>) => T|null) &
25+
* (<T extends Node>(node: Node, test: T['type']|Partial<T>|import('unist-util-is').TestFunctionPredicate<T>|Array.<T['type']|Partial<T>|import('unist-util-is').TestFunctionPredicate<T>>) => T|null) &
26+
* ((node: Node, options: FilterOptions, test?: null|undefined|Type|Props|TestFunctionAnything|Array<Type|Props|TestFunctionAnything>) => Node|null) &
27+
* ((node: Node, test?: null|undefined|Type|Props|TestFunctionAnything|Array<Type|Props|TestFunctionAnything>) => Node|null)
28+
* )}
29+
*/
30+
(
31+
/**
32+
* Create a new tree consisting of copies of all nodes that pass test.
33+
* The tree is walked in preorder (NLR), visiting the node itself, then its head, etc.
34+
*
35+
* @param {Node} tree Tree to filter
36+
* @param {FilterOptions} options
37+
* @param {null|undefined|Type|Props|TestFunctionAnything|Array<Type|Props|TestFunctionAnything>} test is-compatible test (such as a type)
38+
* @returns {Node|null}
39+
*/
40+
function (tree, options, test) {
41+
var is = convert(test || options)
42+
var cascade =
43+
options.cascade === undefined || options.cascade === null
44+
? true
45+
: options.cascade
1146

12-
return preorder(tree, null, null)
47+
return preorder(tree)
1348

14-
function preorder(node, index, parent) {
15-
var children
16-
var childIndex
17-
var result
18-
var next
19-
var key
49+
/**
50+
* @param {Node} node
51+
* @param {number|undefined} [index]
52+
* @param {Parent|undefined} [parent]
53+
* @returns {Node|null}
54+
*/
55+
function preorder(node, index, parent) {
56+
/** @type {Array.<Node>} */
57+
var children = []
58+
/** @type {number} */
59+
var childIndex
60+
/** @type {Node} */
61+
var result
62+
/** @type {typeof node} */
63+
var next
64+
/** @type {string} */
65+
var key
2066

21-
if (!is(node, index, parent)) return null
67+
if (!is(node, index, parent)) return null
2268

23-
if (node.children) {
24-
children = []
25-
childIndex = -1
69+
if (node.children) {
70+
childIndex = -1
2671

27-
while (++childIndex < node.children.length) {
28-
result = preorder(node.children[childIndex], childIndex, node)
72+
// @ts-ignore Looks like a parent.
73+
while (++childIndex < node.children.length) {
74+
// @ts-ignore Looks like a parent.
75+
result = preorder(node.children[childIndex], childIndex, node)
2976

30-
if (result) {
31-
children.push(result)
77+
if (result) {
78+
children.push(result)
79+
}
80+
}
81+
82+
// @ts-ignore Looks like a parent.
83+
if (cascade && node.children.length && !children.length) return null
3284
}
33-
}
3485

35-
if (cascade && node.children.length && !children.length) return null
36-
}
86+
// Create a shallow clone, using the new children.
87+
// @ts-ignore all the fields will be copied over.
88+
next = {}
3789

38-
// Create a shallow clone, using the new children.
39-
next = {}
90+
for (key in node) {
91+
/* istanbul ignore else - Prototype injection. */
92+
if (own.call(node, key)) {
93+
next[key] = key === 'children' ? children : node[key]
94+
}
95+
}
4096

41-
for (key in node) {
42-
/* istanbul ignore else - Prototype injection. */
43-
if (own.call(node, key)) {
44-
next[key] = key === 'children' ? children : node[key]
97+
return next
4598
}
4699
}
47-
48-
return next
49-
}
50-
}
100+
)

index.test-d.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {Node} from 'unist'
2+
import {expectType, expectError} from 'tsd'
3+
import {filter} from './index.js'
4+
import {Heading} from 'mdast'
5+
6+
expectError(filter())
7+
expectType<Node | null>(filter({type: 'root'}))
8+
expectType<Node | null>(filter({type: 'root'}, 'root'))
9+
expectType<Node | null>(filter({type: 'root'}, {}, 'root'))
10+
expectError(filter({type: 'root'}, {notAnOption: true}, 'root'))
11+
expectType<Node | null>(filter({type: 'root'}, {cascade: false}, 'root'))
12+
expectType<Heading | null>(filter<Heading>({type: 'root'}, 'heading'))
13+
expectError(filter<Heading>({type: 'root'}, 'notAHeading'))

package.json

+17-6
Original file line numberDiff line numberDiff line change
@@ -27,31 +27,37 @@
2727
"sideEffects": false,
2828
"type": "module",
2929
"main": "index.js",
30-
"types": "types/index.d.ts",
30+
"types": "index.d.ts",
3131
"files": [
32-
"index.js",
33-
"types/index.d.ts"
32+
"index.d.ts",
33+
"index.js"
3434
],
3535
"dependencies": {
36+
"@types/unist": "^2.0.0",
3637
"unist-util-is": "^5.0.0"
3738
},
3839
"devDependencies": {
3940
"@types/mdast": "^3.0.0",
41+
"@types/tape": "^4.0.0",
4042
"c8": "^7.0.0",
41-
"dtslint": "^4.0.0",
4243
"prettier": "^2.0.0",
4344
"remark-cli": "^9.0.0",
4445
"remark-preset-wooorm": "^8.0.0",
46+
"rimraf": "^3.0.0",
4547
"tape": "^5.0.0",
48+
"tsd": "^0.14.0",
49+
"type-coverage": "^2.0.0",
50+
"typescript": "^4.0.0",
4651
"unist-builder": "^3.0.0",
4752
"xo": "^0.38.0"
4853
},
4954
"scripts": {
55+
"prepack": "npm run build && npm run format",
56+
"build": "rimraf \"*.d.ts\" && tsc && tsd && type-coverage",
5057
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
5158
"test-api": "node test.js",
5259
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js",
53-
"test-types": "# dtslint types",
54-
"test": "npm run format && npm run test-coverage && npm run test-types"
60+
"test": "npm run build && npm run format && npm run test-coverage"
5561
},
5662
"prettier": {
5763
"tabWidth": 2,
@@ -73,5 +79,10 @@
7379
"plugins": [
7480
"preset-wooorm"
7581
]
82+
},
83+
"typeCoverage": {
84+
"atLeast": 100,
85+
"detail": true,
86+
"strict": true
7687
}
7788
}

test.js

+23-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
/**
2+
* @typedef {import('unist').Node} Node
3+
* @typedef {import('unist').Parent} Parent
4+
*/
5+
16
import test from 'tape'
27
import {u} from 'unist-builder'
38
import {filter} from './index.js'
@@ -11,6 +16,9 @@ test('should not traverse into children of filtered out nodes', function (t) {
1116

1217
t.end()
1318

19+
/**
20+
* @param {Node} node
21+
*/
1422
function predicate(node) {
1523
types[node.type] = (types[node.type] || 0) + 1
1624
return node.type !== 'node'
@@ -37,10 +45,16 @@ test('should cascade-remove parent nodes', function (t) {
3745

3846
t.end()
3947

48+
/**
49+
* @param {Node} node
50+
*/
4051
function notOne(node) {
4152
return node.value !== '1'
4253
}
4354

55+
/**
56+
* @param {Node} node
57+
*/
4458
function notLeaf(node) {
4559
return node.type !== 'leaf'
4660
}
@@ -56,13 +70,15 @@ test('should not cascade-remove nodes that were empty initially', function (t) {
5670

5771
test('should call iterator with `index` and `parent` args', function (t) {
5872
var tree = u('root', [u('node', [u('leaf', '1')]), u('leaf', '2')])
73+
/** @type {Array.<[Node, number|undefined, Parent|undefined]>} */
5974
var callLog = []
6075

6176
t.deepEqual(filter(tree, predicate), tree)
6277

6378
t.deepEqual(callLog, [
64-
[tree, null, null],
79+
[tree, undefined, undefined],
6580
[tree.children[0], 0, tree],
81+
// @ts-ignore yeah, it exists.
6682
[tree.children[0].children[0], 0, tree.children[0]],
6783
[tree.children[1], 1, tree]
6884
])
@@ -105,6 +121,9 @@ test('opts.cascade', function (t) {
105121

106122
t.end()
107123

124+
/**
125+
* @param {Node} node
126+
*/
108127
function predicate(node) {
109128
return node.type !== 'leaf'
110129
}
@@ -125,6 +144,9 @@ test('example from README', function (t) {
125144

126145
t.end()
127146

147+
/**
148+
* @param {Node} node
149+
*/
128150
function predicate(node) {
129151
return node.type !== 'leaf' || Number(node.value) % 2 === 0
130152
}

tsconfig.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"include": ["*.js"],
3+
"compilerOptions": {
4+
"target": "ES2020",
5+
"lib": ["ES2020"],
6+
"module": "ES2020",
7+
"moduleResolution": "node",
8+
"allowJs": true,
9+
"checkJs": true,
10+
"declaration": true,
11+
"emitDeclarationOnly": true,
12+
"allowSyntheticDefaultImports": true,
13+
"skipLibCheck": true,
14+
"strictNullChecks": true
15+
}
16+
}

types/index.d.ts

-47
This file was deleted.

types/tsconfig.json

-10
This file was deleted.

types/tslint.json

-8
This file was deleted.

types/unist-util-filter-tests.ts

-11
This file was deleted.

0 commit comments

Comments
 (0)