Skip to content

Commit cee9967

Browse files
committed
Add improved types
1 parent db3a50a commit cee9967

File tree

4 files changed

+233
-87
lines changed

4 files changed

+233
-87
lines changed

index.test-d.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {expectType} from 'tsd'
2+
import type {
3+
Heading,
4+
PhrasingContent,
5+
Root,
6+
RootContent,
7+
RowContent,
8+
TableCell,
9+
TableRow,
10+
Text
11+
} from 'mdast'
12+
import {findAllAfter} from './index.js'
13+
14+
const text: Text = {type: 'text', value: 'alpha'}
15+
const heading: Heading = {type: 'heading', depth: 1, children: [text]}
16+
const root: Root = {type: 'root', children: [heading]}
17+
const cell: TableCell = {type: 'tableCell', children: [text]}
18+
const row: TableRow = {type: 'tableRow', children: [cell]}
19+
20+
// @ts-expect-error: parent needed.
21+
findAllAfter()
22+
23+
// @ts-expect-error: child or index needed.
24+
findAllAfter(heading)
25+
26+
findAllAfter(
27+
// @ts-expect-error: parent needed.
28+
text,
29+
0
30+
)
31+
32+
expectType<PhrasingContent[]>(findAllAfter(heading, text))
33+
34+
expectType<Text[]>(findAllAfter(heading, text, 'text'))
35+
36+
expectType<Text[]>(findAllAfter(heading, 0, 'text'))
37+
38+
expectType<RootContent[]>(findAllAfter(root, 0))
39+
40+
expectType<Text[]>(findAllAfter(root, 0, 'text'))
41+
42+
expectType<RowContent[]>(findAllAfter(row, 0))

lib/index.js

+116-49
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,131 @@
11
/**
2-
* @typedef {import('unist').Node} Node
3-
* @typedef {import('unist').Parent} Parent
4-
* @typedef {import('unist-util-is').Test} Test
2+
* @typedef {import('unist').Node} UnistNode
3+
* @typedef {import('unist').Parent} UnistParent
4+
*/
5+
6+
/**
7+
* @typedef {Exclude<import('unist-util-is').Test, undefined> | undefined} Test
8+
* Test from `unist-util-is`.
9+
*
10+
* Note: we have remove and add `undefined`, because otherwise when generating
11+
* automatic `.d.ts` files, TS tries to flatten paths from a local perspective,
12+
* which doesn’t work when publishing on npm.
13+
*/
14+
15+
/**
16+
* @typedef {(
17+
* Fn extends (value: any) => value is infer Thing
18+
* ? Thing
19+
* : Fallback
20+
* )} Predicate
21+
* Get the value of a type guard `Fn`.
22+
* @template Fn
23+
* Value; typically function that is a type guard (such as `(x): x is Y`).
24+
* @template Fallback
25+
* Value to yield if `Fn` is not a type guard.
26+
*/
27+
28+
/**
29+
* @typedef {(
30+
* Check extends null | undefined // No test.
31+
* ? Value
32+
* : Value extends {type: Check} // String (type) test.
33+
* ? Value
34+
* : Value extends Check // Partial test.
35+
* ? Value
36+
* : Check extends Function // Function test.
37+
* ? Predicate<Check, Value> extends Value
38+
* ? Predicate<Check, Value>
39+
* : never
40+
* : never // Some other test?
41+
* )} MatchesOne
42+
* Check whether a node matches a primitive check in the type system.
43+
* @template Value
44+
* Value; typically unist `Node`.
45+
* @template Check
46+
* Value; typically `unist-util-is`-compatible test, but not arrays.
47+
*/
48+
49+
/**
50+
* @typedef {(
51+
* Check extends Array<any>
52+
* ? MatchesOne<Value, Check[keyof Check]>
53+
* : MatchesOne<Value, Check>
54+
* )} Matches
55+
* Check whether a node matches a check in the type system.
56+
* @template Value
57+
* Value; typically unist `Node`.
58+
* @template Check
59+
* Value; typically `unist-util-is`-compatible test.
60+
*/
61+
62+
/**
63+
* @typedef {(
64+
* Kind extends {children: Array<infer Child>}
65+
* ? Child
66+
* : never
67+
* )} Child
68+
* Collect nodes that can be parents of `Child`.
69+
* @template {UnistNode} Kind
70+
* All node types.
571
*/
672

773
import {convert} from 'unist-util-is'
874

975
/**
1076
* Find nodes in `parent` after a `child` or after an index, that pass `test`.
1177
*
12-
* @template {Node} Kind
13-
* Node type.
14-
*
15-
* @overload
16-
* @param {Parent} parent
17-
* @param {Node | number} index
18-
* @param {import('unist-util-is').Test} test
19-
* @returns {Array<Kind>}
20-
*
21-
* @overload
22-
* @param {Parent} parent
23-
* @param {Node | number} index
24-
* @param {Test} [test]
25-
* @returns {Array<Node>}
26-
*
27-
* @param {Parent} parent
78+
* @param parent
2879
* Parent node.
29-
* @param {Node | number} index
30-
* Child of `parent` or it’s index.
31-
* @param {Test} [test]
32-
* `unist-util-is`-compatible test.
33-
* @returns {Array<Node>}
34-
* Children of `parent` that pass `test`.
80+
* @param index
81+
* Child node or index.
82+
* @param [test=undefined]
83+
* Test for child to look for (optional).
84+
* @returns
85+
* Children (matching `test`, if given).
3586
*/
36-
export function findAllAfter(parent, index, test) {
37-
const is = convert(test)
38-
/** @type {Array<Node>} */
39-
const results = []
87+
export const findAllAfter =
88+
// Note: overloads like this are needed to support optional generics.
89+
/**
90+
* @type {(
91+
* (<Kind extends UnistParent, Check extends Test>(parent: Kind, index: Child<Kind> | number, test: Check) => Array<Matches<Child<Kind>, Check>>) &
92+
* (<Kind extends UnistParent>(parent: Kind, index: Child<Kind> | number, test?: null | undefined) => Array<Child<Kind>>)
93+
* )}
94+
*/
95+
(
96+
/**
97+
* @param {UnistParent} parent
98+
* @param {UnistNode | number} index
99+
* @param {Test} [test=undefined]
100+
* @returns {Array<UnistNode>}
101+
*/
102+
function (parent, index, test) {
103+
const is = convert(test)
104+
/** @type {Array<UnistNode>} */
105+
const results = []
40106

41-
if (!parent || !parent.type || !parent.children) {
42-
throw new Error('Expected parent node')
43-
}
107+
if (!parent || !parent.type || !parent.children) {
108+
throw new Error('Expected parent node')
109+
}
44110

45-
if (typeof index === 'number') {
46-
if (index < 0 || index === Number.POSITIVE_INFINITY) {
47-
throw new Error('Expected positive finite number as index')
48-
}
49-
} else {
50-
index = parent.children.indexOf(index)
111+
if (typeof index === 'number') {
112+
if (index < 0 || index === Number.POSITIVE_INFINITY) {
113+
throw new Error('Expected positive finite number as index')
114+
}
115+
} else {
116+
index = parent.children.indexOf(index)
51117

52-
if (index < 0) {
53-
throw new Error('Expected child node or index')
54-
}
55-
}
118+
if (index < 0) {
119+
throw new Error('Expected child node or index')
120+
}
121+
}
56122

57-
while (++index < parent.children.length) {
58-
if (is(parent.children[index], index, parent)) {
59-
results.push(parent.children[index])
60-
}
61-
}
123+
while (++index < parent.children.length) {
124+
if (is(parent.children[index], index, parent)) {
125+
results.push(parent.children[index])
126+
}
127+
}
62128

63-
return results
64-
}
129+
return results
130+
}
131+
)

package.json

+7-1
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,21 @@
3737
"unist-util-is": "^6.0.0"
3838
},
3939
"devDependencies": {
40+
"@types/mdast": "^4.0.0",
4041
"@types/node": "^20.0.0",
4142
"c8": "^8.0.0",
4243
"mdast-util-from-markdown": "^1.0.0",
4344
"prettier": "^2.0.0",
4445
"remark-cli": "^11.0.0",
4546
"remark-preset-wooorm": "^9.0.0",
47+
"tsd": "^0.28.1",
4648
"type-coverage": "^2.0.0",
4749
"typescript": "^5.0.0",
4850
"xo": "^0.54.0"
4951
},
5052
"scripts": {
5153
"prepack": "npm run build && npm run format",
52-
"build": "tsc --build --clean && tsc --build && type-coverage",
54+
"build": "tsc --build --clean && tsc --build && tsd && type-coverage",
5355
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
5456
"test-api": "node --conditions development test.js",
5557
"test-coverage": "c8 --100 --reporter lcov npm run test-api",
@@ -72,6 +74,10 @@
7274
"atLeast": 100,
7375
"detail": true,
7476
"ignoreCatch": true,
77+
"#": "needed `any`s",
78+
"ignoreFiles": [
79+
"lib/index.d.ts"
80+
],
7581
"strict": true
7682
},
7783
"xo": {

0 commit comments

Comments
 (0)