Skip to content

Commit

Permalink
feat(types): Visitor, Visitors
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <[email protected]>
  • Loading branch information
unicornware committed Mar 18, 2024
1 parent 656675c commit f87d900
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 0 deletions.
62 changes: 62 additions & 0 deletions src/types/__tests__/visitor.spec-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* @file Type Tests - Visitor
* @module unist-util-visit/types/tests/unit-d/Visitor
*/

import type * as docast from '@flex-development/docast'
import type { Optional } from '@flex-development/tutils'
import type {
Index,
MatchInclusiveDescendant,
Type
} from '@flex-development/unist-util-types'
import type VisitedAncestor from '../visited-ancestor'
import type VisitedParent from '../visited-parent'
import type TestSubject from '../visitor'
import type VisitorResult from '../visitor-result'

describe('unit-d:types/Visitor', () => {
type Tree = docast.Root
type Check = Type<docast.InlineTag>
type Subject = TestSubject<Tree, Check>

it('should match [this: void]', () => {
expectTypeOf<Subject>().thisParameter.toBeVoid()
})

describe('parameters', () => {
it('should match [0: MatchInclusiveDescendant<Tree, Check>]', () => {
// Arrange
type Expect = MatchInclusiveDescendant<Tree, Check>

// Expect
expectTypeOf<Subject>().parameter(0).toEqualTypeOf<Expect>()
})

it('should match [1: Optional<Index>]', () => {
expectTypeOf<Subject>().parameter(1).toEqualTypeOf<Optional<Index>>()
})

it('should match [2: Optional<VisitedParent<Tree, Check>>]', () => {
// Arrange
type Expect = Optional<VisitedParent<Tree, Check>>

// Expect
expectTypeOf<Subject>().parameter(2).toEqualTypeOf<Expect>()
})

it('should match [3: VisitedAncestor<Tree, Check>[]]', () => {
// Arrange
type Expect = VisitedAncestor<Tree, Check>[]

// Expect
expectTypeOf<Subject>().parameter(3).toEqualTypeOf<Expect>()
})
})

describe('returns', () => {
it('should return VisitorResult', () => {
expectTypeOf<Subject>().returns.toEqualTypeOf<VisitorResult>()
})
})
})
29 changes: 29 additions & 0 deletions src/types/__tests__/visitors.spec-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* @file Type Tests - Visitors
* @module unist-util-visit/types/tests/unit-d/Visitors
*/

import type * as docast from '@flex-development/docast'
import type { Nilable } from '@flex-development/tutils'
import type { Type } from '@flex-development/unist-util-types'
import type Visitor from '../visitor'
import type TestSubject from '../visitors'

describe('unit-d:types/Visitors', () => {
type Tree = docast.Root
type Check = Type<docast.Comment>
type VisitorFn = Visitor<Tree, Check>
type Subject = TestSubject<Tree, Check>

it('should match [enter?: Nilable<Visitor<Tree, Check>>]', () => {
expectTypeOf<Subject>()
.toHaveProperty('enter')
.toEqualTypeOf<Nilable<VisitorFn>>()
})

it('should match [leave?: Nilable<Visitor<Tree, Check>>]', () => {
expectTypeOf<Subject>()
.toHaveProperty('leave')
.toEqualTypeOf<Nilable<VisitorFn>>()
})
})
2 changes: 2 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,6 @@ export type { default as Exit } from './exit'
export type { default as Skip } from './skip'
export type { default as VisitedAncestor } from './visited-ancestor'
export type { default as VisitedParent } from './visited-parent'
export type { default as Visitor } from './visitor'
export type { default as VisitorResult } from './visitor-result'
export type { default as Visitors } from './visitors'
75 changes: 75 additions & 0 deletions src/types/visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* @file Type Definitions - Visitor
* @module unist-util-visit/types/Visitor
*/

import type { Optional } from '@flex-development/tutils'
import type {
Index,
MatchInclusiveDescendant,
Test
} from '@flex-development/unist-util-types'
import type { Node } from 'unist'
import type Action from './action'
import type VisitedAncestor from './visited-ancestor'
import type VisitedParent from './visited-parent'
import type VisitorResult from './visitor-result'

/**
* Handle visiting `node`.
*
* Visitors are free to transform `node`. They can also transform [`parent`][1],
* or the grandparent of `node` (the last of [`ancestors`][2]).
*
* > 👉 **Note**: Replacing `node` itself, if `SKIP` is not returned, still
* > causes its [*descendants*][3] to be walked (which is a bug).
*
* When adding or removing previous [*siblings*][4] of `node`, the `Visitor`
* should return a new `Index` to specify the sibling to traverse after `node`
* is traversed. Adding or removing next siblings of `node` is handled as
* expected without needing to return a new `Index`.
*
* Removing the [*children*][5] of an [*ancestor*][2] still results in those
* child nodes being traversed.
*
* The `Visitor` should return the next action, or `Index`, as explained above.
* An `Index` is treated as a tuple of `[CONTINUE, Index]`. An `Action` is
* treated as a tuple of `[Action]`. Returning a tuple only makes sense if the
* `Action` is `SKIP`. When the `Action` is `EXIT`, that action can be returned.
* When the `Action` is `CONTINUE`, `Index` can be returned.
*
* [1]: https://github.com/syntax-tree/unist#parent-1
* [2]: https://github.com/syntax-tree/unist#ancestor
* [3]: https://github.com/syntax-tree/unist#descendant
* [4]: https://github.com/syntax-tree/unist#sibling
* [5]: https://github.com/syntax-tree/unist#child
*
* @see {@linkcode Action}
* @see {@linkcode Index}
* @see {@linkcode MatchInclusiveDescendant}
* @see {@linkcode Node}
* @see {@linkcode Test}
* @see {@linkcode VisitedAncestor}
* @see {@linkcode VisitedParent}
* @see {@linkcode VisitorResult}
*
* @template {Node} [Tree=Node] - Tree being visited
* @template {Test} [Check=Test] - Node test
*
* @this {void}
*
* @param {MatchInclusiveDescendant<Tree, Check>} node - Found node
* @param {Optional<Index>} index - Index of `node` in `parent.children`
* @param {Optional<VisitedParent<Tree, Check>>} parent - Parent of `node`
* @param {VisitedAncestor<Tree, Check>[]} ancestors - Ancestors of `node`
* @return {VisitorResult} What to do next
*/
type Visitor<Tree extends Node = Node, Check extends Test = Test> = (
this: void,
node: MatchInclusiveDescendant<Tree, Check>,
index: Optional<Index>,
parent: Optional<VisitedParent<Tree, Check>>,
ancestors: VisitedAncestor<Tree, Check>[]
) => VisitorResult

export type { Visitor as default }
43 changes: 43 additions & 0 deletions src/types/visitors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @file Type Definitions - Visitors
* @module unist-util-visit/types/Visitors
*/

import type { Nilable } from '@flex-development/tutils'
import type { Test } from '@flex-development/unist-util-types'
import type { Node } from 'unist'
import type Visitor from './visitor'

/**
* Handle nodes when entering ([*preorder*][1]) and leaving ([*postorder*][2]).
*
* [1]: https://github.com/syntax-tree/unist#preorder
* [2]: https://github.com/syntax-tree/unist#postorder
*
* @see {@linkcode Node}
* @see {@linkcode Test}
*
* @template {Node} [Tree=Node] - Tree being visited
* @template {Test} [Check=Test] - Node test
*/
type Visitors<Tree extends Node = Node, Check extends Test = Test> = {
/**
* Handle nodes when entering ([*preorder*][1]).
*
* [1]: https://github.com/syntax-tree/unist#preorder
*
* @see {@linkcode Visitor}
*/
enter?: Nilable<Visitor<Tree, Check>>

/**
* Handle nodes when leaving ([*postorder*][1]).
*
* [1]: https://github.com/syntax-tree/unist#postorder
*
* @see {@linkcode Visitor}
*/
leave?: Nilable<Visitor<Tree, Check>>
}

export type { Visitors as default }

0 comments on commit f87d900

Please sign in to comment.