Skip to content

Commit

Permalink
Add typings
Browse files Browse the repository at this point in the history
Related-to: unifiedjs/unified#65.
Related-to: syntax-tree/unist-util-visit-parents#7.
Closes GH-15.

Reviewed-by: Junyoung Choi <[email protected]>
Reviewed-by: Titus Wormer <[email protected]>
  • Loading branch information
ChristianMurphy authored and wooorm committed Jul 31, 2019
1 parent 3cb11ec commit d63b8a6
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 4 deletions.
16 changes: 12 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,38 @@
"Richard Gibson <[email protected]>"
],
"files": [
"index.js"
"index.js",
"types/index.d.ts"
],
"types": "types/index.d.ts",
"dependencies": {
"unist-util-visit-parents": "^2.0.0"
"@types/unist": "^2.0.0",
"unist-util-is": "^4.0.0",
"unist-util-visit-parents": "^3.0.0"
},
"devDependencies": {
"browserify": "^16.0.0",
"dtslint": "^0.9.0",
"nyc": "^14.0.0",
"prettier": "^1.0.0",
"remark": "^10.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 unistUtilVisit > unist-util-visit.js",
"build-mangle": "browserify . -s unistUtilVisit -p tinyify > unist-util-visit.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 types",
"test": "npm run format && npm run build && npm run test-coverage && npm run test-types"
},
"nyc": {
"check-coverage": true,
Expand Down
88 changes: 88 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// TypeScript Version: 3.5

import {Node, Parent} from 'unist'
import {Test} from 'unist-util-is'
import {
Action,
ActionTuple,
Continue,
Exit,
Index,
Skip
} from 'unist-util-visit-parents'

declare namespace visit {
/**
* Invoked when a node (matching test, if given) is found.
* Visitors are free to transform node.
* They can also transform the parent of node.
* Replacing node itself, if visit.SKIP is not returned, still causes its descendants to be visited.
* If adding or removing previous siblings (or next siblings, in case of reverse) of node,
* visitor should return a new index (number) to specify the sibling to traverse after node is traversed.
* Adding or removing next siblings of node (or previous siblings, in case of reverse)
* is handled as expected without needing to return a new index.
* Removing the children property of the parent still result in them being traversed.
*
* @param node Found node
* @param index Position of found node within Parent
* @param parent Parent of found node
* @paramType V node type found
* @returns
* When Action is passed, treated as a tuple of [Action]
* When Index is passed, treated as a tuple of [CONTINUE, Index]
* When ActionTuple is passed,
* Note that passing a tuple only makes sense if the action is SKIP.
* If the action is EXIT, that action can be returned.
* If the action is CONTINUE, index can be returned.
*/
type Visitor<V extends Node> = (
node: V,
index: number,
parent: Node
) => void | Action | Index | ActionTuple
}

declare const visit: {
/**
* Visit children of tree which pass a test
*
* @param tree abstract syntax tree to visit
* @param test test node
* @param visitor function to run for each node
* @param reverse visit the tree in reverse, defaults to false
* @typeParam T tree node
* @typeParam V node type found
*/
<V extends Node>(
tree: Node,
test: Test<V> | Array<Test<any>>,
visitor: visit.Visitor<V>,
reverse?: boolean
): void

/**
* Visit children of a tree
*
* @param tree abstract syntax tree to visit
* @param visitor function to run for each node
* @param reverse visit the tree in reverse, defaults to false
*/
(tree: Node, visitor: visit.Visitor<Node>, reverse?: boolean): void

/**
* Continue traversing as normal
*/
CONTINUE: Continue

/**
* Do not traverse this node’s children
*/
SKIP: Skip

/**
* Stop traversing immediately
*/
EXIT: Exit
}

export = visit
14 changes: 14 additions & 0 deletions types/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"lib": ["es2015"],
"strict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"baseUrl": ".",
"paths": {
"unist-util-visit": ["index.d.ts"]
}
}
}
15 changes: 15 additions & 0 deletions types/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
}
}
231 changes: 231 additions & 0 deletions types/unist-util-visit-tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import {Node, Parent} from 'unist'
import unified = require('unified')
import visit = require('unist-util-visit')

/*=== setup ===*/
const sampleTree = {
type: 'root',
children: [
{
type: 'heading',
depth: 1,
children: []
}
]
}

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[]
}

const isNode = (node: unknown): node is Node =>
typeof node === 'object' && !!node && 'type' in node
const headingTest = (node: unknown): node is Heading =>
isNode(node) && node.type === 'heading'
const elementTest = (node: unknown): node is Element =>
isNode(node) && node.type === 'element'

/*=== missing params ===*/
// $ExpectError
visit()
// $ExpectError
visit(sampleTree)

/*=== visit without test ===*/
visit(sampleTree, node => {})
visit(sampleTree, (node: Node) => {})
// $ExpectError
visit(sampleTree, (node: Element) => {})
// $ExpectError
visit(sampleTree, (node: Heading) => {})

/*=== visit with type test ===*/
visit(sampleTree, 'heading', node => {})
visit(sampleTree, 'heading', (node: Heading) => {})
// $ExpectError
visit(sampleTree, 'not-a-heading', (node: Heading) => {})
// $ExpectError
visit(sampleTree, 'element', (node: Heading) => {})

visit(sampleTree, 'element', node => {})
visit(sampleTree, 'element', (node: Element) => {})
// $ExpectError
visit(sampleTree, 'not-an-element', (node: Element) => {})
// $ExpectError
visit(sampleTree, 'heading', (node: Element) => {})

/*=== visit with object test ===*/
visit(sampleTree, {type: 'heading'}, node => {})
visit(sampleTree, {random: 'property'}, node => {})

visit(sampleTree, {type: 'heading'}, (node: Heading) => {})
visit(sampleTree, {type: 'heading', depth: 2}, (node: Heading) => {})
// $ExpectError
visit(sampleTree, {type: 'element'}, (node: Heading) => {})
// $ExpectError
visit(sampleTree, {type: 'heading', depth: '2'}, (node: Heading) => {})

visit(sampleTree, {type: 'element'}, (node: Element) => {})
visit(sampleTree, {type: 'element', tagName: 'section'}, (node: Element) => {})
// $ExpectError
visit(sampleTree, {type: 'heading'}, (node: Element) => {})
// $ExpectError
visit(sampleTree, {type: 'element', tagName: true}, (node: Element) => {})

/*=== visit with function test ===*/
visit(sampleTree, headingTest, node => {})
visit(sampleTree, headingTest, (node: Heading) => {})
// $ExpectError
visit(sampleTree, headingTest, (node: Element) => {})

visit(sampleTree, elementTest, node => {})
visit(sampleTree, elementTest, (node: Element) => {})
// $ExpectError
visit(sampleTree, elementTest, (node: Heading) => {})

/*=== visit with array of tests ===*/
visit(sampleTree, ['ParagraphNode', {type: 'element'}, headingTest], node => {})

/*=== visit returns action ===*/
visit(sampleTree, 'heading', node => visit.CONTINUE)
visit(sampleTree, 'heading', node => visit.EXIT)
visit(sampleTree, 'heading', node => visit.SKIP)
visit(sampleTree, 'heading', node => true)
visit(sampleTree, 'heading', node => false)
visit(sampleTree, 'heading', node => 'skip')
// $ExpectError
visit(sampleTree, 'heading', node => 'random')

/*=== visit returns index ===*/
visit(sampleTree, 'heading', node => 0)
visit(sampleTree, 'heading', node => 1)

/*=== visit returns tuple ===*/
visit(sampleTree, 'heading', node => [visit.CONTINUE, 1])
visit(sampleTree, 'heading', node => [visit.EXIT, 1])
visit(sampleTree, 'heading', node => [visit.SKIP, 1])
visit(sampleTree, 'heading', node => [true, 1])
visit(sampleTree, 'heading', node => [false, 1])
visit(sampleTree, 'heading', node => ['skip', 1])
// $ExpectError
visit(sampleTree, 'heading', node => ['skip'])
// $ExpectError
visit(sampleTree, 'heading', node => [1])
// $ExpectError
visit(sampleTree, 'heading', node => ['random', 1])

/*=== usage as unified plugin ===*/
unified().use(() => sampleTree => {
// duplicates the above type tests but passes in the unified transformer input

/*=== missing params ===*/
// $ExpectError
visit()
// $ExpectError
visit(sampleTree)

/*=== visit without test ===*/
visit(sampleTree, node => {})
visit(sampleTree, (node: Node) => {})
// $ExpectError
visit(sampleTree, (node: Element) => {})
// $ExpectError
visit(sampleTree, (node: Heading) => {})

/*=== visit with type test ===*/
visit(sampleTree, 'heading', node => {})
visit(sampleTree, 'heading', (node: Heading) => {})
// $ExpectError
visit(sampleTree, 'not-a-heading', (node: Heading) => {})
// $ExpectError
visit(sampleTree, 'element', (node: Heading) => {})

visit(sampleTree, 'element', node => {})
visit(sampleTree, 'element', (node: Element) => {})
// $ExpectError
visit(sampleTree, 'not-an-element', (node: Element) => {})
// $ExpectError
visit(sampleTree, 'heading', (node: Element) => {})

/*=== visit with object test ===*/
visit(sampleTree, {type: 'heading'}, node => {})
visit(sampleTree, {random: 'property'}, node => {})

visit(sampleTree, {type: 'heading'}, (node: Heading) => {})
visit(sampleTree, {type: 'heading', depth: 2}, (node: Heading) => {})
// $ExpectError
visit(sampleTree, {type: 'element'}, (node: Heading) => {})
// $ExpectError
visit(sampleTree, {type: 'heading', depth: '2'}, (node: Heading) => {})

visit(sampleTree, {type: 'element'}, (node: Element) => {})
visit(
sampleTree,
{type: 'element', tagName: 'section'},
(node: Element) => {}
)
// $ExpectError
visit(sampleTree, {type: 'heading'}, (node: Element) => {})
// $ExpectError
visit(sampleTree, {type: 'element', tagName: true}, (node: Element) => {})

/*=== visit with function test ===*/
visit(sampleTree, headingTest, node => {})
visit(sampleTree, headingTest, (node: Heading) => {})
// $ExpectError
visit(sampleTree, headingTest, (node: Element) => {})

visit(sampleTree, elementTest, node => {})
visit(sampleTree, elementTest, (node: Element) => {})
// $ExpectError
visit(sampleTree, elementTest, (node: Heading) => {})

/*=== visit with array of tests ===*/
visit(
sampleTree,
['ParagraphNode', {type: 'element'}, headingTest],
node => {}
)

/*=== visit returns action ===*/
visit(sampleTree, 'heading', node => visit.CONTINUE)
visit(sampleTree, 'heading', node => visit.EXIT)
visit(sampleTree, 'heading', node => visit.SKIP)
visit(sampleTree, 'heading', node => true)
visit(sampleTree, 'heading', node => false)
visit(sampleTree, 'heading', node => 'skip')
// $ExpectError
visit(sampleTree, 'heading', node => 'random')

/*=== visit returns index ===*/
visit(sampleTree, 'heading', node => 0)
visit(sampleTree, 'heading', node => 1)

/*=== visit returns tuple ===*/
visit(sampleTree, 'heading', node => [visit.CONTINUE, 1])
visit(sampleTree, 'heading', node => [visit.EXIT, 1])
visit(sampleTree, 'heading', node => [visit.SKIP, 1])
visit(sampleTree, 'heading', node => [true, 1])
visit(sampleTree, 'heading', node => [false, 1])
visit(sampleTree, 'heading', node => ['skip', 1])
// $ExpectError
visit(sampleTree, 'heading', node => ['skip'])
// $ExpectError
visit(sampleTree, 'heading', node => [1])
// $ExpectError
visit(sampleTree, 'heading', node => ['random', 1])

return sampleTree
})

0 comments on commit d63b8a6

Please sign in to comment.