Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions convert.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import {Test, TestFunction} from './'
import {Node} from 'unist'

declare function convert<T extends Node>(test: Test<T>): TestFunction<T>

export = convert
66 changes: 66 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// TypeScript Version: 3.5

import {Node, Parent} from 'unist'

declare namespace unistUtilIs {
/**
* Check that type property matches expectation for a node
*
* @typeParam T type of node that passes test
*/
type TestType<T extends Node> = T['type']

/**
* Check that some attributes on a node are matched
*
* @typeParam T type of node that passes test
*/
type TestObject<T extends Node> = Partial<T>

/**
* Check if a node passes a test
*
* @param node node to check
* @param index index of node in parent
* @param parent parent of node
* @typeParam T type of node that passes test
* @returns true if type T is found, false otherwise
*/
type TestFunction<T extends Node> = (
node: unknown,
index?: number,
parent?: Parent
) => node is T

/**
* Union of all the types of tests
*
* @typeParam T type of node that passes test
*/
type Test<T extends Node> = TestType<T> | TestObject<T> | TestFunction<T>
}

/**
* Unist utility to check if a node passes a test.
*
* @param node Node to check.
* @param test When not given, checks if `node` is a `Node`.
* When `string`, works like passing `function (node) {return node.type === test}`.
* When `function` checks if function passed the node is true.
* When `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
* When `array`, checks any one of the subtests pass.
* @param index Position of `node` in `parent`
* @param parent Parent of `node`
* @param context Context object to invoke `test` with
* @typeParam T type that node is compared with
* @returns Whether test passed and `node` is a `Node` (object with `type` set to non-empty `string`).
*/
declare function unistUtilIs<T extends Node>(
node: unknown,
test: unistUtilIs.Test<T> | Array<unistUtilIs.Test<any>>,
index?: number,
parent?: Parent,
context?: any
): node is T

export = unistUtilIs
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,34 @@
],
"files": [
"index.js",
"convert.js"
"convert.js",
"index.d.ts",
"convert.d.ts"
],
"types": "index.d.ts",
"dependencies": {},
"devDependencies": {
"browserify": "^16.0.0",
"dtslint": "^0.9.0",
"nyc": "^14.0.0",
"prettier": "^1.0.0",
"remark-cli": "^6.0.0",
"remark-preset-wooorm": "^5.0.0",
"tape": "^4.0.0",
"tinyify": "^2.0.0",
"typescript": "^3.5.3",
"unified": "^8.3.2",
"xo": "^0.24.0"
},
"scripts": {
"format": "remark . -qfo && prettier --write \"**/*.js\" && xo --fix",
"format": "remark . -qfo && prettier --write \"**/*.{js,ts}\" && xo --fix",
"build-bundle": "browserify . -s unistUtilIs > unist-util-is.js",
"build-mangle": "browserify . -s unistUtilIs -p tinyify > unist-util-is.min.js",
"build": "npm run build-bundle && npm run build-mangle",
"test-api": "node test",
"test-coverage": "nyc --reporter lcov tape test.js",
"test": "npm run format && npm run build && npm run test-coverage"
"test-types": "dtslint .",
"test": "npm run format && npm run build && npm run test-coverage && npm run test-types"
},
"prettier": {
"tabWidth": 2,
Expand Down
15 changes: 15 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"lib": ["es2015"],
"strict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": ".",
"paths": {
"unist-util-is": ["index.d.ts"],
"unist-util-is/convert": ["convert.d.ts"]
}
}
}
15 changes: 15 additions & 0 deletions tslint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"extends": "dtslint/dtslint.json",
"rules": {
"callable-types": false,
"max-line-length": false,
"no-redundant-jsdoc": false,
"no-void-expression": false,
"only-arrow-functions": false,
"semicolon": false,
"unified-signatures": false,
"whitespace": false,
"interface-over-type-literal": false,
"no-unnecessary-generics": false
}
}
190 changes: 190 additions & 0 deletions unist-util-is-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import {Node, Parent} from 'unist'
import unified = require('unified')
import is = require('unist-util-is')
import convert = require('unist-util-is/convert')

/*=== setup ===*/
interface Heading extends Parent {
type: 'heading'
depth: number
children: Node[]
}

interface Element extends Parent {
type: 'element'
tagName: string
properties: {
[key: string]: unknown
}
content: Node
children: Node[]
}

interface Paragraph extends Parent {
type: 'ParagraphNode'
}

const heading: Node = {
type: 'heading',
depth: 2,
children: []
}

const element: Node = {
type: 'element',
tagName: 'section',
properties: {},
content: {type: 'text'},
children: []
}

const isHeading = (node: unknown): node is Heading =>
typeof node === 'object' && node !== null && (node as Node).type === 'heading'
const isElement = (node: unknown): node is Element =>
typeof node === 'object' && node !== null && (node as Node).type === 'element'

/*=== types cannot be narrowed without predicate ===*/
// $ExpectError
const maybeHeading: Heading = heading
// $ExpectError
const maybeElement: Element = element

/*=== missing params ===*/
// $ExpectError
is()
// $ExpectError
is<Node>()
// $ExpectError
is<Node>(heading)

/*=== invalid generic ===*/
// $ExpectError
is<string>(heading, 'heading')
// $ExpectError
is<boolean>(heading, 'heading')
// $ExpectError
is<{}>(heading, 'heading')

/*=== assignable to boolean ===*/
const wasItAHeading: boolean = is<Heading>(heading, 'heading')

/*=== type string test ===*/
is<Heading>(heading, 'heading')
is<Heading>(element, 'heading')
// $ExpectError
is<Heading>(heading, 'element')

if (is<Heading>(heading, 'heading')) {
const maybeHeading: Heading = heading
// $ExpectError
const maybeNotHeading: Element = heading
}

is<Element>(element, 'element')
is<Element>(heading, 'element')
// $ExpectError
is<Element>(element, 'heading')

if (is<Element>(element, 'element')) {
const maybeElement: Element = element
// $ExpectError
const maybeNotElement: Heading = element
}

/*=== type predicate function test ===*/
is(heading, isHeading)
is(element, isHeading)
// $ExpectError
is<Heading>(heading, isElement)

if (is(heading, isHeading)) {
const maybeHeading: Heading = heading
// $ExpectError
const maybeNotHeading: Element = heading
}

is(element, isElement)
is(heading, isElement)
// $ExpectError
is<Element>(element, isHeading)

if (is(element, isElement)) {
const maybeElement: Element = element
// $ExpectError
const maybeNotElement: Heading = element
}

/*=== type object test ===*/
is<Heading>(heading, {type: 'heading', depth: 2})
is<Heading>(element, {type: 'heading', depth: 2})
// $ExpectError
is<Heading>(heading, {type: 'heading', depth: '2'})

if (is<Heading>(heading, {type: 'heading', depth: 2})) {
const maybeHeading: Heading = heading
// $ExpectError
const maybeNotHeading: Element = heading
}

is<Element>(element, {type: 'element', tagName: 'section'})
is<Element>(heading, {type: 'element', tagName: 'section'})
// $ExpectError
is<Element>(element, {type: 'element', tagName: true})

if (is<Element>(element, {type: 'element', tagName: 'section'})) {
const maybeElement: Element = element
// $ExpectError
const maybeNotElement: Heading = element
}

/*=== type array of tests ===*/
is<Heading | Element | Paragraph>(heading, [
'heading',
isElement,
{type: 'ParagraphNode'}
])
if (
is<Heading | Element | Paragraph>(heading, [
'heading',
isElement,
{type: 'ParagraphNode'}
])
) {
switch (heading.type) {
case 'heading': {
heading // $ExpectType Heading
break
}
case 'element': {
heading // $ExpectType Element
break
}
case 'ParagraphNode': {
heading // $ExpectType Paragraph
break
}
// $ExpectError
case 'dne': {
break
}
}
}

/*=== usable in unified transform ===*/
unified().use(() => tree => {
if (is<Heading>(tree, 'heading')) {
// do something
}
return tree
})

/*=== convert ===*/
convert<Heading>('heading')
// $ExpectError
convert<Heading>('element')
convert<Heading>({type: 'heading', depth: 2})
// $ExpectError
convert<Element>({type: 'heading', depth: 2})
convert<Heading>(isHeading)
// $ExpectError
convert<Element>(isHeading)