Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions packages/oxc-unshadowed-visitor/src/bindingNames.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { describe, test, expect } from 'vitest'
import { parseSync, type ESTree } from 'rolldown/utils'
import { extractBindingNames } from './bindingNames.ts'

function parse(code: string) {
return parseSync('test.js', code).program
}

describe('extractBindingNames', () => {
function extractFromParam(code: string): string[] {
const program = parse(`function f(${code}) {}`)
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
const fn = program.body[0] as ESTree.Function
const names: string[] = []
for (const param of fn.params) {
extractBindingNames(param, names)
}
return names
}

function extractFromDecl(code: string): string[] {
const program = parse(code)
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
const decl = program.body[0] as ESTree.VariableDeclaration
const names: string[] = []
for (const declarator of decl.declarations) {
extractBindingNames(declarator.id, names)
}
return names
}

test('simple identifier', () => {
expect(extractFromParam('a')).toEqual(['a'])
})

test('multiple params', () => {
expect(extractFromParam('a, b, c')).toEqual(['a', 'b', 'c'])
})

test('rest param', () => {
expect(extractFromParam('a, ...rest')).toEqual(['a', 'rest'])
})

test('array destructuring', () => {
expect(extractFromDecl('const [a, b] = arr')).toEqual(['a', 'b'])
})

test('array destructuring with holes', () => {
expect(extractFromDecl('const [a, , b] = arr')).toEqual(['a', 'b'])
})

test('object destructuring', () => {
expect(extractFromDecl('const { a, b } = obj')).toEqual(['a', 'b'])
})

test('renamed object destructuring', () => {
expect(extractFromDecl('const { x: a, y: b } = obj')).toEqual(['a', 'b'])
})

test('rest element in array', () => {
expect(extractFromDecl('const [a, ...rest] = arr')).toEqual(['a', 'rest'])
})

test('rest element in object', () => {
expect(extractFromDecl('const { a, ...rest } = obj')).toEqual(['a', 'rest'])
})

test('assignment pattern', () => {
expect(extractFromParam('a = 1, b = 2')).toEqual(['a', 'b'])
})

test('nested destructuring', () => {
expect(extractFromDecl('const { a: { b, c }, d } = obj')).toEqual(['b', 'c', 'd'])
})

test('deeply nested mixed destructuring', () => {
expect(extractFromDecl('const { a: [b, { c: d }], ...e } = obj')).toEqual(['b', 'd', 'e'])
})
})
107 changes: 107 additions & 0 deletions packages/oxc-unshadowed-visitor/src/mergeVisitors.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// oxlint-disable unicorn/consistent-function-scoping
import { describe, test, expect } from 'vitest'
import { mergeVisitors } from './mergeVisitors.ts'
import type { VisitorContext } from './types.ts'
import type { ESTree } from 'rolldown/utils'

describe('mergeVisitors', () => {
const dummyIdentifierNode: ESTree.IdentifierReference = {
type: 'Identifier',
name: 'foo',
start: 0,
end: 3,
}
function makeCtx(): VisitorContext<string> {
return {
record() {},
}
}

test('user enter runs after internal enter', () => {
const order: string[] = []
const merged = mergeVisitors(
{
Identifier: () => order.push('user-enter'),
},
makeCtx(),
{ Identifier: () => order.push('internal-enter') },
{},
)
merged.Identifier(dummyIdentifierNode)
expect(order).toEqual(['internal-enter', 'user-enter'])
})

test('user exit runs before internal exit', () => {
const order: string[] = []
const merged = mergeVisitors(
{
'Identifier:exit': () => order.push('user-exit'),
},
makeCtx(),
{},
{ 'Identifier:exit': () => order.push('internal-exit') },
)
merged['Identifier:exit'](dummyIdentifierNode)
expect(order).toEqual(['user-exit', 'internal-exit'])
})

test('internal-only visitors are included', () => {
const ctx = makeCtx()
const enterFn = () => {}
const exitFn = () => {}
const merged = mergeVisitors({}, ctx, { Identifier: enterFn }, { 'Identifier:exit': exitFn })
expect(merged.Identifier).toBe(enterFn)
expect(merged['Identifier:exit']).toBe(exitFn)
})

test('user-only visitors are included', () => {
const called: string[] = []
const ctx = makeCtx()
const merged = mergeVisitors(
{
Identifier: () => called.push('identifier'),
'Identifier:exit': () => called.push('identifier-exit'),
},
ctx,
{},
{},
)
merged.Identifier(dummyIdentifierNode)
merged['Identifier:exit'](dummyIdentifierNode)
expect(called).toEqual(['identifier', 'identifier-exit'])
})

test('ctx is passed to user visitor functions', () => {
let receivedCtx: unknown
const ctx = makeCtx()
const merged = mergeVisitors(
{
Identifier: (_node, c) => {
receivedCtx = c
},
},
ctx,
{},
{},
)
merged.Identifier(dummyIdentifierNode)
expect(receivedCtx).toBe(ctx)
})

test('ctx is passed to user exit visitor functions', () => {
let receivedCtx: unknown
const ctx = makeCtx()
const merged = mergeVisitors(
{
'Identifier:exit': (_node, c) => {
receivedCtx = c
},
},
ctx,
{},
{},
)
merged['Identifier:exit'](dummyIdentifierNode)
expect(receivedCtx).toBe(ctx)
})
})