-
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor to move implementation to
lib/
- Loading branch information
Showing
10 changed files
with
292 additions
and
279 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
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 |
---|---|---|
@@ -1,112 +1,2 @@ | ||
/* eslint-disable @typescript-eslint/ban-types */ | ||
|
||
import type {Node, Parent} from 'unist' | ||
import type {Test} from 'unist-util-is' | ||
|
||
/** | ||
* Union of the action types | ||
*/ | ||
export type Action = boolean | 'skip' | ||
|
||
/** | ||
* Move to the sibling at index next (after node itself is completely | ||
* traversed). | ||
* Useful if mutating the tree, such as removing the node the visitor is | ||
* currently on, or any of its previous siblings (or next siblings, in case of | ||
* reverse) Results less than 0 or greater than or equal to `children.length` | ||
* stop traversing the parent. | ||
*/ | ||
export type Index = number | ||
|
||
/** | ||
* List with one or two values, the first an action, the second an index. | ||
*/ | ||
export type ActionTuple = [ | ||
(Action | null | undefined | void)?, | ||
(Index | null | undefined)? | ||
] | ||
|
||
/** | ||
* Any value that can be returned from a visitor | ||
*/ | ||
export type VisitorResult = | ||
| null | ||
| undefined | ||
| Action | ||
| Index | ||
| ActionTuple | ||
| void | ||
|
||
/** | ||
* Internal utility to collect all descendants of in `Tree`. | ||
*/ | ||
export type InclusiveDescendant< | ||
Tree extends Node = never, | ||
Found = void | ||
> = Tree extends Parent | ||
? | ||
| Tree | ||
| InclusiveDescendant< | ||
Exclude<Tree['children'][number], Found | Tree>, | ||
Found | Tree | ||
> | ||
: Tree | ||
|
||
type Predicate<Fn, Fallback = never> = Fn extends ( | ||
value: any | ||
) => value is infer Thing | ||
? Thing | ||
: Fallback | ||
|
||
type MatchesOne<Value, Check> = | ||
// Is this a node? | ||
Value extends Node | ||
? // No test. | ||
Check extends null | ||
? Value | ||
: // No test. | ||
Check extends undefined | ||
? Value | ||
: // Function test. | ||
Check extends Function | ||
? Extract<Value, Predicate<Check, Value>> | ||
: // String (type) test. | ||
Value['type'] extends Check | ||
? Value | ||
: // Partial test. | ||
Value extends Check | ||
? Value | ||
: never | ||
: never | ||
|
||
export type Matches<Value, Check> = | ||
// Is this a list? | ||
Check extends Array<any> | ||
? MatchesOne<Value, Check[keyof Check]> | ||
: MatchesOne<Value, Check> | ||
|
||
/** | ||
* Called 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. | ||
*/ | ||
export type Visitor< | ||
Visited extends Node = Node, | ||
Ancestor extends Parent = Parent | ||
> = (node: Visited, ancestors: Array<Ancestor>) => VisitorResult | ||
|
||
export type BuildVisitor< | ||
Tree extends Node = Node, | ||
Check extends Test = string | ||
> = Visitor< | ||
Matches<InclusiveDescendant<Tree>, Check>, | ||
Extract<InclusiveDescendant<Tree>, Parent> | ||
> | ||
|
||
/* eslint-enable @typescript-eslint/ban-types */ | ||
// To do: next major: remove this file. | ||
export type {Visitor, BuildVisitor} from './lib/complex-types.js' |
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,10 @@ | ||
export type {Test} from 'unist-util-is' | ||
export type { | ||
Action, | ||
ActionTuple, | ||
Index, | ||
VisitorResult, | ||
Visitor, | ||
BuildVisitor | ||
} from './lib/complex-types.js' | ||
export {CONTINUE, EXIT, SKIP, visitParents} from './lib/index.js' |
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 |
---|---|---|
@@ -1,156 +1,2 @@ | ||
/** | ||
* @typedef {import('unist').Node} Node | ||
* @typedef {import('unist').Parent} Parent | ||
* @typedef {import('unist-util-is').Test} Test | ||
* @typedef {import('./complex-types.js').Action} Action | ||
* @typedef {import('./complex-types.js').Index} Index | ||
* @typedef {import('./complex-types.js').ActionTuple} ActionTuple | ||
* @typedef {import('./complex-types.js').VisitorResult} VisitorResult | ||
* @typedef {import('./complex-types.js').Visitor} Visitor | ||
*/ | ||
|
||
import {convert} from 'unist-util-is' | ||
import {color} from './color.js' | ||
|
||
/** | ||
* Continue traversing as normal | ||
*/ | ||
export const CONTINUE = true | ||
/** | ||
* Do not traverse this node’s children | ||
*/ | ||
export const SKIP = 'skip' | ||
/** | ||
* Stop traversing immediately | ||
*/ | ||
export const EXIT = false | ||
|
||
/** | ||
* Visit children of tree which pass test. | ||
* | ||
* @param tree | ||
* Tree to walk | ||
* @param [test] | ||
* `unist-util-is`-compatible test | ||
* @param visitor | ||
* Function called for nodes that pass `test`. | ||
* @param [reverse=false] | ||
* Traverse in reverse preorder (NRL) instead of preorder (NLR) (default). | ||
*/ | ||
export const visitParents = | ||
/** | ||
* @type {( | ||
* (<Tree extends Node, Check extends Test>(tree: Tree, test: Check, visitor: import('./complex-types.js').BuildVisitor<Tree, Check>, reverse?: boolean) => void) & | ||
* (<Tree extends Node>(tree: Tree, visitor: import('./complex-types.js').BuildVisitor<Tree>, reverse?: boolean) => void) | ||
* )} | ||
*/ | ||
( | ||
/** | ||
* @param {Node} tree | ||
* @param {Test} test | ||
* @param {import('./complex-types.js').Visitor<Node>} visitor | ||
* @param {boolean} [reverse=false] | ||
*/ | ||
function (tree, test, visitor, reverse) { | ||
if (typeof test === 'function' && typeof visitor !== 'function') { | ||
reverse = visitor | ||
// @ts-expect-error no visitor given, so `visitor` is test. | ||
visitor = test | ||
test = null | ||
} | ||
|
||
const is = convert(test) | ||
const step = reverse ? -1 : 1 | ||
|
||
factory(tree, null, [])() | ||
|
||
/** | ||
* @param {Node} node | ||
* @param {number?} index | ||
* @param {Array<Parent>} parents | ||
*/ | ||
function factory(node, index, parents) { | ||
/** @type {Record<string, unknown>} */ | ||
// @ts-expect-error: hush | ||
const value = typeof node === 'object' && node !== null ? node : {} | ||
/** @type {string|undefined} */ | ||
let name | ||
|
||
if (typeof value.type === 'string') { | ||
name = | ||
typeof value.tagName === 'string' | ||
? value.tagName | ||
: typeof value.name === 'string' | ||
? value.name | ||
: undefined | ||
|
||
Object.defineProperty(visit, 'name', { | ||
value: | ||
'node (' + | ||
color(value.type + (name ? '<' + name + '>' : '')) + | ||
')' | ||
}) | ||
} | ||
|
||
return visit | ||
|
||
function visit() { | ||
/** @type {ActionTuple} */ | ||
let result = [] | ||
/** @type {ActionTuple} */ | ||
let subresult | ||
/** @type {number} */ | ||
let offset | ||
/** @type {Array<Parent>} */ | ||
let grandparents | ||
|
||
if (!test || is(node, index, parents[parents.length - 1] || null)) { | ||
result = toResult(visitor(node, parents)) | ||
|
||
if (result[0] === EXIT) { | ||
return result | ||
} | ||
} | ||
|
||
// @ts-expect-error looks like a parent. | ||
if (node.children && result[0] !== SKIP) { | ||
// @ts-expect-error looks like a parent. | ||
offset = (reverse ? node.children.length : -1) + step | ||
// @ts-expect-error looks like a parent. | ||
grandparents = parents.concat(node) | ||
|
||
// @ts-expect-error looks like a parent. | ||
while (offset > -1 && offset < node.children.length) { | ||
// @ts-expect-error looks like a parent. | ||
subresult = factory(node.children[offset], offset, grandparents)() | ||
|
||
if (subresult[0] === EXIT) { | ||
return subresult | ||
} | ||
|
||
offset = | ||
typeof subresult[1] === 'number' ? subresult[1] : offset + step | ||
} | ||
} | ||
|
||
return result | ||
} | ||
} | ||
} | ||
) | ||
|
||
/** | ||
* @param {VisitorResult} value | ||
* @returns {ActionTuple} | ||
*/ | ||
function toResult(value) { | ||
if (Array.isArray(value)) { | ||
return value | ||
} | ||
|
||
if (typeof value === 'number') { | ||
return [CONTINUE, value] | ||
} | ||
|
||
return [value] | ||
} | ||
// Note: types exported from `index.d.ts` | ||
export {CONTINUE, EXIT, SKIP, visitParents} from './lib/index.js' |
File renamed without changes.
File renamed without changes.
Oops, something went wrong.