-
-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
3cb11ec
commit d63b8a6
Showing
5 changed files
with
360 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"] | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
}) |