Skip to content

Commit

Permalink
feat(visitor): visitor
Browse files Browse the repository at this point in the history
Signed-off-by: Lexus Drumgold <[email protected]>
  • Loading branch information
unicornware committed Mar 5, 2024
1 parent 5a71bd4 commit 9ebb69b
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 21 deletions.
1 change: 1 addition & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,4 @@ ignore:
profiling:
critical_files_paths:
- src/utils/compare.ts
- src/visitor.ts
3 changes: 1 addition & 2 deletions .commitlintrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ const config: UserConfig = {
rules: {
'scope-enum': [Severity.Error, 'always', scopes([
'chore',
'handlers',
'util',
'visitors'
'visitor'
])]
}
}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[![vitest](https://img.shields.io/badge/-vitest-6e9f18?style=flat&logo=vitest&logoColor=ffffff)](https://vitest.dev/)
[![yarn](https://img.shields.io/badge/-yarn-2c8ebb?style=flat&logo=yarn&logoColor=ffffff)](https://yarnpkg.com/)

[esast][esast](and [estree][estree]) utility to attach comments
[esast][esast] (and [estree][estree]) utility to attach comments

## Contents

Expand Down
Empty file removed __fixtures__/.gitkeep
Empty file.
30 changes: 30 additions & 0 deletions __fixtures__/pure-comments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @file Fixtures - PURE_COMMENTS
* @module fixtures/PURE_COMMENTS
*/

import type { Comment } from 'estree'

/**
* `@__PURE__` block comments.
*
* @type {ReadonlyArray<Comment>}
*/
export default Object.freeze<Comment[]>([
{
position: {
end: { column: 33, line: 11, offset: 411 },
start: { column: 18, line: 11, offset: 396 }
},
type: 'Block',
value: ' @__PURE__ '
},
{
position: {
end: { column: 27, line: 14, offset: 535 },
start: { column: 12, line: 14, offset: 520 }
},
type: 'Block',
value: ' @__PURE__ '
}
])
143 changes: 143 additions & 0 deletions src/__tests__/visitor.functional.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/**
* @file Functional Tests - visitor
* @module esast-util-attach-comments/tests/functional/visitor
*/

import PURE_COMMENTS from '#fixtures/pure-comments'
import type { State } from '#src/types'
import * as utils from '#src/utils'
import type { Spy } from '#tests/interfaces'
import type { NewExpression, Program } from 'estree'
import testSubject from '../visitor'

describe('functional:visitor', () => {
let emptyProgram: Program
let emptyState: State
let keycheck: Spy<(typeof utils)['keycheck']>
let slice: Spy<(typeof utils)['slice']>

beforeAll(() => {
emptyProgram = { body: [], sourceType: 'module', type: 'Program' }
emptyState = { comments: [], index: 0 }
})

beforeEach(() => {
keycheck = vi.spyOn(utils, 'keycheck').mockName('keycheck')
slice = vi.spyOn(utils, 'slice').mockName('slice')
})

describe('enter', () => {
describe('!state.comments.length', () => {
beforeEach(() => {
testSubject(emptyState)(emptyProgram, undefined)
})

it('should do nothing if no comments to attach', () => {
expect(keycheck).not.toHaveBeenCalled()
})
})

describe('state.comments.length > 0', () => {
let node: NewExpression
let state: State

beforeAll(() => {
state = {
comments: [...PURE_COMMENTS],
index: 1
}

node = {
arguments: [],
callee: {
name: 'WeakSet',
position: {
end: { column: 39, line: 14, offset: 547 },
start: { column: 32, line: 14, offset: 540 }
},
type: 'Identifier'
},
position: {
end: { column: 41, line: 14, offset: 549 },
start: { column: 28, line: 14, offset: 536 }
},
type: 'NewExpression'
}
})

beforeEach(() => {
testSubject(state)(node, 'value')
})

it('should attach leading comments', () => {
// Arrange
const property: string = 'leadingComments'

// Expect
expect(slice).toHaveBeenCalledOnce()
expect(slice).toHaveBeenCalledWith(state, node)
expect(node).to.have.deep.property(property, [state.comments[1]])
})
})
})

describe('leave', () => {
let leave: true

beforeAll(() => {
leave = true
})

describe('!state.comments.length', () => {
beforeEach(() => {
testSubject(emptyState, leave)(emptyProgram, undefined)
})

it('should do nothing if no comments to attach', () => {
expect(keycheck).not.toHaveBeenCalled()
})
})

describe('state.comments.length > 0', () => {
let node: Program
let state: State

beforeAll(() => {
state = {
comments: [
{
position: {
end: { column: 38, line: 2, offset: 38 },
start: { column: 1, line: 2, offset: 1 }
},
type: 'Line',
value: '# sourceMappingURL=polyfill.mjs.map'
}
],
index: 0
}

node = {
body: [],
position: {
end: { column: 1, line: 3, offset: 39 },
start: { column: 1, line: 1, offset: 0 }
},
sourceType: 'module',
type: 'Program'
}
})

beforeEach(() => {
testSubject(state)(node, undefined)
testSubject(state, leave)(node, undefined)
})

it('should attach comments', () => {
expect(slice).toHaveBeenCalledWith(state, node)
expect(node).to.have.deep.property('comments', state.comments)
expect(node).to.have.deep.property('trailingComments', state.comments)
})
})
})
})
12 changes: 12 additions & 0 deletions src/__tests__/visitor.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* @file Unit Tests - visitor
* @module esast-util-attach-comments/tests/unit/visitor
*/

import testSubject from '../visitor'

describe('unit:visitor', () => {
it('should return visitor function', () => {
expect(testSubject({ comments: [], index: 0 })).to.be.a('function')
})
})
23 changes: 23 additions & 0 deletions src/types/__tests__/visitor.spec-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* @file Type Tests - Visitor
* @module esast-util-attach-comments/types/tests/unit-d/Visitor
*/

import type { Optional } from '@flex-development/tutils'
import type { NewExpression } from 'estree'
import type { CONTINUE, EXIT } from 'estree-util-visit'
import type TestSubject from '../visitor'

describe('unit-d:types/Visitor', () => {
it('should be callable with [T, Optional<string>]', () => {
expectTypeOf<TestSubject<NewExpression>>()
.parameters
.toEqualTypeOf<[NewExpression, Optional<string>]>()
})

it('should return typeof CONTINUE | typeof EXIT', () => {
expectTypeOf<TestSubject>()
.returns
.toEqualTypeOf<typeof CONTINUE | typeof EXIT>()
})
})
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
*/

export type { default as State } from './state'
export type { default as Visitor } from './visitor'
31 changes: 31 additions & 0 deletions src/types/visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* @file Type Definitions - Visitor
* @module esast-util-attach-comments/types/Visitor
*/

import type { Optional } from '@flex-development/tutils'
import type { Node } from 'estree'
import type { CONTINUE, EXIT } from 'estree-util-visit'

/**
* Attach comments when entering or leaving `node`.
*
* @see {@linkcode CONTINUE}
* @see {@linkcode EXIT}
* @see {@linkcode Node}
*
* @internal
*
* @template {Node} [T=Node] - Node type
*
* @param {T} node - Node being entered or exited
* @param {Optional<string>} key - Field at which `node` lives in its parent
* (or where a list of nodes live if `parent[key]` is an array)
* @return {typeof CONTINUE | typeof EXIT} Next action
*/
type Visitor<T extends Node = Node> = (
node: T,
key: Optional<string>
) => typeof CONTINUE | typeof EXIT

export type { Visitor as default }
20 changes: 2 additions & 18 deletions src/utils/__tests__/slice.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* @module esast-util-attach-comments/utils/tests/unit/slice
*/

import comments from '#fixtures/pure-comments'
import type { State } from '#src/types'
import type { NewExpression } from 'estree'
import testSubject from '../slice'
Expand All @@ -13,24 +14,7 @@ describe('unit:utils/slice', () => {

beforeAll(() => {
state = {
comments: [
{
position: {
end: { column: 33, line: 11, offset: 411 },
start: { column: 18, line: 11, offset: 396 }
},
type: 'Block',
value: ' @__PURE__ '
},
{
position: {
end: { column: 27, line: 14, offset: 535 },
start: { column: 12, line: 14, offset: 520 }
},
type: 'Block',
value: ' @__PURE__ '
}
],
comments: [...comments],
index: 0
}

Expand Down
57 changes: 57 additions & 0 deletions src/visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @file visitor
* @module esast-util-attach-comments/visitor
*/

import type { Optional } from '@flex-development/tutils'
import type { Node } from 'estree'
import { CONTINUE, EXIT } from 'estree-util-visit'
import type { State, Visitor } from './types'
import { keycheck, slice } from './utils'

declare module 'estree' {
interface BaseNode {
comments?: Comment[] | undefined
}
}

/**
* Create a visitor to attach comments when entering or leaving a node.
*
* @see {@linkcode State}
* @see {@linkcode Visitor}
*
* @param {State} state - Visitor state
* @param {boolean?} [leave] - Visiting nodes on exit?
* @return {Visitor} Visitor function
*/
function visitor(state: State, leave?: boolean): Visitor {
/**
* Attach comments when entering or leaving `node`.
*
* @param {Node} node - Node being entered or exited
* @param {Optional<string>} key - Field at which `node` lives in its parent
* (or where a list of nodes live if `parent[key]` is an array)
* @return {typeof CONTINUE | typeof EXIT} Next action
*/
return (node: Node, key: Optional<string>): typeof CONTINUE | typeof EXIT => {
if (!state.comments.length) return EXIT

if (keycheck(key)) {
if ((state.leave = !!leave)) {
node.comments = []
node.trailingComments = []
node.trailingComments.push(...slice(state, node))
node.comments.push(...node.leadingComments!)
node.comments.push(...node.trailingComments)
} else {
node.leadingComments = []
node.leadingComments.push(...slice(state, node))
}
}

return CONTINUE
}
}

export default visitor

0 comments on commit 9ebb69b

Please sign in to comment.