Skip to content

Commit

Permalink
Add improved types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Jul 7, 2023
1 parent c31f7da commit 16ac2e1
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 78 deletions.
42 changes: 42 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {expectType} from 'tsd'
import type {
Heading,
PhrasingContent,
Root,
RootContent,
RowContent,
TableCell,
TableRow,
Text
} from 'mdast'
import {findAfter} from './index.js'

const text: Text = {type: 'text', value: 'alpha'}
const heading: Heading = {type: 'heading', depth: 1, children: [text]}
const root: Root = {type: 'root', children: [heading]}
const cell: TableCell = {type: 'tableCell', children: [text]}
const row: TableRow = {type: 'tableRow', children: [cell]}

// @ts-expect-error: parent needed.
findAfter()

// @ts-expect-error: child or index needed.
findAfter(heading)

findAfter(
// @ts-expect-error: parent needed.
text,
0
)

expectType<PhrasingContent | undefined>(findAfter(heading, text))

expectType<Text | undefined>(findAfter(heading, text, 'text'))

expectType<Text | undefined>(findAfter(heading, 0, 'text'))

expectType<RootContent | undefined>(findAfter(root, 0))

expectType<Text | undefined>(findAfter(root, 0, 'text'))

expectType<RowContent | undefined>(findAfter(row, 0))
162 changes: 114 additions & 48 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,73 @@
/**
* @typedef {import('unist').Node} Node
* @typedef {import('unist').Parent} Parent
* @typedef {import('unist-util-is').Test} Test
* @typedef {import('unist').Node} UnistNode
* @typedef {import('unist').Parent} UnistParent
*/

/**
* @typedef {Exclude<import('unist-util-is').Test, undefined> | undefined} Test
* Test from `unist-util-is`.
*
* Note: we have remove and add `undefined`, because otherwise when generating
* automatic `.d.ts` files, TS tries to flatten paths from a local perspective,
* which doesn’t work when publishing on npm.
*/

/**
* @typedef {(
* Fn extends (value: any) => value is infer Thing
* ? Thing
* : Fallback
* )} Predicate
* Get the value of a type guard `Fn`.
* @template Fn
* Value; typically function that is a type guard (such as `(x): x is Y`).
* @template Fallback
* Value to yield if `Fn` is not a type guard.
*/

/**
* @typedef {(
* Check extends null | undefined // No test.
* ? Value
* : Value extends {type: Check} // String (type) test.
* ? Value
* : Value extends Check // Partial test.
* ? Value
* : Check extends Function // Function test.
* ? Predicate<Check, Value> extends Value
* ? Predicate<Check, Value>
* : never
* : never // Some other test?
* )} MatchesOne
* Check whether a node matches a primitive check in the type system.
* @template Value
* Value; typically unist `Node`.
* @template Check
* Value; typically `unist-util-is`-compatible test, but not arrays.
*/

/**
* @typedef {(
* Check extends Array<any>
* ? MatchesOne<Value, Check[keyof Check]>
* : MatchesOne<Value, Check>
* )} Matches
* Check whether a node matches a check in the type system.
* @template Value
* Value; typically unist `Node`.
* @template Check
* Value; typically `unist-util-is`-compatible test.
*/

/**
* @typedef {(
* Kind extends {children: Array<infer Child>}
* ? Child
* : never
* )} Child
* Collect nodes that can be parents of `Child`.
* @template {UnistNode} Kind
* All node types.
*/

import {convert} from 'unist-util-is'
Expand All @@ -10,55 +76,55 @@ import {convert} from 'unist-util-is'
* Find the first node in `parent` after another `node` or after an index,
* that passes `test`.
*
* @template {Node} Kind
* Node type.
*
* @overload
* @param {Parent} parent
* @param {Node | number} index
* @param {import('unist-util-is').Test} test
* @returns {Kind | undefined}
*
* @overload
* @param {Parent} parent
* @param {Node | number} index
* @param {Test} [test]
* @returns {Node | undefined}
*
* @param {Parent} parent
* @param parent
* Parent node.
* @param {Node | number} index
* Child of `parent` or it’s index.
* @param {Test} [test]
* `unist-util-is`-compatible test.
* @returns {Node | undefined}
* Child of `parent` or `undefined`.
* @param index
* Child node or index.
* @param [test=undefined]
* Test for child to look for (optional).
* @returns
* A child (matching `test`, if given) or `undefined`.
*/
// To do: next major: `undefined`.
export function findAfter(parent, index, test) {
const is = convert(test)
export const findAfter =
// Note: overloads like this are needed to support optional generics.
/**
* @type {(
* (<Kind extends UnistParent, Check extends Test>(parent: Kind, index: Child<Kind> | number, test: Check) => Matches<Child<Kind>, Check> | undefined) &
* (<Kind extends UnistParent>(parent: Kind, index: Child<Kind> | number, test?: null | undefined) => Child<Kind> | undefined)
* )}
*/
(
/**
* @param {UnistParent} parent
* @param {UnistNode | number} index
* @param {Test} [test]
* @returns {UnistNode | undefined}
*/
function (parent, index, test) {
const is = convert(test)

if (!parent || !parent.type || !parent.children) {
throw new Error('Expected parent node')
}
if (!parent || !parent.type || !parent.children) {
throw new Error('Expected parent node')
}

if (typeof index === 'number') {
if (index < 0 || index === Number.POSITIVE_INFINITY) {
throw new Error('Expected positive finite number as index')
}
} else {
index = parent.children.indexOf(index)
if (typeof index === 'number') {
if (index < 0 || index === Number.POSITIVE_INFINITY) {
throw new Error('Expected positive finite number as index')
}
} else {
index = parent.children.indexOf(index)

if (index < 0) {
throw new Error('Expected child node or index')
}
}
if (index < 0) {
throw new Error('Expected child node or index')
}
}

while (++index < parent.children.length) {
if (is(parent.children[index], index, parent)) {
return parent.children[index]
}
}
while (++index < parent.children.length) {
if (is(parent.children[index], index, parent)) {
return parent.children[index]
}
}

return undefined
}
return undefined
}
)
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,21 @@
"unist-util-is": "^6.0.0"
},
"devDependencies": {
"@types/mdast": "^4.0.0",
"@types/node": "^20.0.0",
"c8": "^8.0.0",
"mdast-util-from-markdown": "^1.0.0",
"prettier": "^2.0.0",
"remark-cli": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"tsd": "^0.28.0",
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"xo": "^0.54.0"
},
"scripts": {
"prepack": "npm run build && npm run format",
"build": "tsc --build --clean && tsc --build && type-coverage",
"build": "tsc --build --clean && tsc --build && tsd && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node --conditions development test.js",
"test-coverage": "c8 --100 --reporter lcov npm run test-api",
Expand All @@ -70,6 +72,10 @@
"atLeast": 100,
"detail": true,
"ignoreCatch": true,
"#": "needed `any`s",
"ignoreFiles": [
"lib/index.d.ts"
],
"strict": true
},
"xo": {
Expand Down
Loading

0 comments on commit 16ac2e1

Please sign in to comment.