Skip to content

Commit

Permalink
Add JSDoc based types
Browse files Browse the repository at this point in the history
  • Loading branch information
wooorm committed Apr 20, 2021
1 parent dfbcafb commit ed0bccd
Show file tree
Hide file tree
Showing 10 changed files with 269 additions and 372 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.DS_Store
*.d.ts
*.log
coverage/
node_modules/
Expand Down
80 changes: 68 additions & 12 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,74 @@
/**
* @typedef {import('unist').Node} Node
* @typedef {import('unist').Parent} Parent
* @typedef {import('unist-util-is').Type} Type
* @typedef {import('unist-util-is').Props} Props
* @typedef {import('unist-util-is').TestFunctionAnything} TestFunctionAnything
* @typedef {import('unist-util-visit-parents').Action} Action
* @typedef {import('unist-util-visit-parents').Index} Index
* @typedef {import('unist-util-visit-parents').ActionTuple} ActionTuple
*/

/**
* Invoked when a node (matching test, if given) is found.
* Visitors are free to transform node.
* They can also transform the parent of node (the last of ancestors).
* Replacing node itself, if `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 an ancestor still results in them being traversed.
*
* @template {Node} V
* @callback Visitor
* @param {V} node Found node
* @param {number|null} index Position of `node` in `parent`
* @param {Parent|null} parent Parent of `node`
* @returns {null|undefined|Action|Index|ActionTuple|void}
*/

import {visitParents, CONTINUE, SKIP, EXIT} from 'unist-util-visit-parents'

export {CONTINUE, SKIP, EXIT}

export function visit(tree, test, visitor, reverse) {
if (typeof test === 'function' && typeof visitor !== 'function') {
reverse = visitor
visitor = test
test = null
}
export const visit =
/**
* @type {(
* (<T extends Node>(tree: Node, test: T['type']|Partial<T>|import('unist-util-is').TestFunctionPredicate<T>|Array.<T['type']|Partial<T>|import('unist-util-is').TestFunctionPredicate<T>>, visitor: Visitor<T>, reverse?: boolean) => void) &
* ((tree: Node, test: null|undefined|Type|Props|TestFunctionAnything|Array<Type|Props|TestFunctionAnything>, visitor: Visitor<Node>, reverse?: boolean) => void) &
* ((tree: Node, visitor: Visitor<Node>, reverse?: boolean) => void)
* )}
*/
(
/**
* Visit children of tree which pass a test
*
* @param {Node} tree Abstract syntax tree to walk
* @param {null|undefined|Type|Props|TestFunctionAnything|Array<Type|Props|TestFunctionAnything>} test test Test node
* @param {Visitor<Node>} visitor Function to run for each node
* @param {boolean} [reverse] Fisit the tree in reverse, defaults to false
*/
function (tree, test, visitor, reverse) {
if (typeof test === 'function' && typeof visitor !== 'function') {
reverse = visitor
visitor = test
test = null
}

visitParents(tree, test, overload, reverse)
visitParents(tree, test, overload, reverse)

function overload(node, parents) {
var parent = parents[parents.length - 1]
return visitor(node, parent ? parent.children.indexOf(node) : null, parent)
}
}
/**
* @param {Node} node
* @param {Array.<Parent>} parents
*/
function overload(node, parents) {
var parent = parents[parents.length - 1]
return visitor(
node,
parent ? parent.children.indexOf(node) : null,
parent
)
}
}
)
103 changes: 103 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* eslint-disable @typescript-eslint/no-confusing-void-expression, @typescript-eslint/no-empty-function */

import {expectError} from 'tsd'
import {Node, Parent} from 'unist'
import {visit, SKIP, EXIT, CONTINUE} from './index.js'

/* 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: Record<string, unknown>
content: Node
children: Node[]
}

const isNode = (node: unknown): node is Node =>
typeof node === 'object' && node !== null && '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, (_) => {})
visit(sampleTree, (_: Node) => {})
expectError(visit(sampleTree, (_: Element) => {}))
expectError(visit(sampleTree, (_: Heading) => {}))

/* Visit with type test. */
visit(sampleTree, 'heading', (_) => {})
visit(sampleTree, 'heading', (_: Heading) => {})
expectError(visit(sampleTree, 'not-a-heading', (_: Heading) => {}))
expectError(visit(sampleTree, 'element', (_: Heading) => {}))

visit(sampleTree, 'element', (_) => {})
visit(sampleTree, 'element', (_: Element) => {})
expectError(visit(sampleTree, 'not-an-element', (_: Element) => {}))
expectError(visit(sampleTree, 'heading', (_: Element) => {}))

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

visit(sampleTree, {type: 'heading'}, (_: Heading) => {})
visit(sampleTree, {type: 'heading', depth: 2}, (_: Heading) => {})
expectError(visit(sampleTree, {type: 'element'}, (_: Heading) => {}))
expectError(
visit(sampleTree, {type: 'heading', depth: '2'}, (_: Heading) => {})
)

visit(sampleTree, {type: 'element'}, (_: Element) => {})
visit(sampleTree, {type: 'element', tagName: 'section'}, (_: Element) => {})

expectError(visit(sampleTree, {type: 'heading'}, (_: Element) => {}))

expectError(
visit(sampleTree, {type: 'element', tagName: true}, (_: Element) => {})
)

/* Visit with function test. */
visit(sampleTree, headingTest, (_) => {})
visit(sampleTree, headingTest, (_: Heading) => {})
expectError(visit(sampleTree, headingTest, (_: Element) => {}))

visit(sampleTree, elementTest, (_) => {})
visit(sampleTree, elementTest, (_: Element) => {})
expectError(visit(sampleTree, elementTest, (_: Heading) => {}))

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

/* Visit returns action. */
visit(sampleTree, 'heading', (_) => CONTINUE)
visit(sampleTree, 'heading', (_) => EXIT)
visit(sampleTree, 'heading', (_) => SKIP)
expectError(visit(sampleTree, 'heading', (_) => 'random'))

/* Visit returns index. */
visit(sampleTree, 'heading', (_) => 0)
visit(sampleTree, 'heading', (_) => 1)

/* Visit returns tuple. */
visit(sampleTree, 'heading', (_) => [CONTINUE, 1])
visit(sampleTree, 'heading', (_) => [EXIT, 1])
visit(sampleTree, 'heading', (_) => [SKIP, 1])
visit(sampleTree, 'heading', (_) => [SKIP])
expectError(visit(sampleTree, 'heading', (_) => [1]))
expectError(visit(sampleTree, 'heading', (_) => ['random', 1]))
22 changes: 15 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
"sideEffects": false,
"type": "module",
"main": "index.js",
"types": "types/index.d.ts",
"types": "index.d.ts",
"files": [
"types/index.d.ts",
"index.d.ts",
"index.js"
],
"dependencies": {
Expand All @@ -52,22 +52,28 @@
"unist-util-visit-parents": "^4.0.0"
},
"devDependencies": {
"@types/tape": "^4.0.0",
"c8": "^7.0.0",
"prettier": "^2.0.0",
"remark": "^13.0.0",
"remark-cli": "^9.0.0",
"remark-gfm": "^1.0.0",
"remark-preset-wooorm": "^8.0.0",
"rimraf": "^3.0.0",
"tape": "^5.0.0",
"tsd": "^0.14.0",
"type-coverage": "^2.0.0",
"typescript": "^4.0.0",
"unified": "^9.0.0",
"xo": "^0.38.0"
},
"scripts": {
"prepack": "npm run build && npm run format",
"build": "rimraf \"*.d.ts\" && tsc && tsd && type-coverage",
"format": "remark . -qfo && prettier . -w --loglevel warn && xo --fix",
"test-api": "node test.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov node test.js",
"test": "npm run format && npm run test-coverage"
"test": "npm run build && npm run format && npm run test-coverage"
},
"prettier": {
"tabWidth": 2,
Expand All @@ -82,14 +88,16 @@
"rules": {
"no-var": "off",
"prefer-arrow-callback": "off"
},
"ignore": [
"types/"
]
}
},
"remarkConfig": {
"plugins": [
"preset-wooorm"
]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true
}
}

0 comments on commit ed0bccd

Please sign in to comment.