import { describe, expect, it } from 'vitest' import { forEachChild, walk } from './ast-utils' import type { Node } from 'oxc-parser' describe('forEachChild', () => { it('visits only direct typed children, skipping null and non-type values', () => { const childA = { type: 'ChildA', start: 0, end: 1 } const childB = { type: 'ChildB', start: 1, end: 2 } const nested = { type: 'Nested', start: 2, end: 3 } const root = { type: 'ForEachRoot', start: 0, end: 10, // array-valued key holding two typed children items: [childA, childB], // object-valued typed child body: nested, // null field: skipped optional: null, // primitive string value: not a child (and excluded from child keys) name: 'hello', // primitive number value: not a child count: 5, } as unknown as Node const visited: Array = [] forEachChild(root, (child) => visited.push(child)) expect(visited).toEqual([childA, childB, nested]) expect(visited).toHaveLength(3) }) it('skips non-object array items and array items without a type', () => { const real = { type: 'RealItem', start: 0, end: 1 } const root = { type: 'ForEachArrayRoot', start: 0, end: 10, items: [real, null, 'string-item', 42, { noType: true }], } as unknown as Node const visited: Array = [] forEachChild(root, (child) => visited.push(child)) expect(visited).toEqual([real]) }) }) describe('walk', () => { it('visits every node depth-first with the correct parentNode', () => { const grandchild = { type: 'WalkGrandchild', start: 4, end: 5 } const childA = { type: 'WalkChildA', start: 1, end: 6, inner: grandchild, } const childB = { type: 'WalkChildB', start: 6, end: 7 } const root = { type: 'WalkRoot', start: 0, end: 10, children: [childA, childB], } as unknown as Node const visits: Array<{ node: Node; parent: Node | undefined }> = [] walk(root, (node, parentNode) => { visits.push({ node, parent: parentNode }) }) // Depth-first order: root, childA, grandchild, childB expect(visits.map((v) => v.node.type)).toEqual([ 'WalkRoot', 'WalkChildA', 'WalkGrandchild', 'WalkChildB', ]) // Correct parent for each node expect(visits[0]!.parent).toBeUndefined() expect(visits[1]!.parent).toBe(root) expect(visits[2]!.parent).toBe(childA) expect(visits[3]!.parent).toBe(root) }) it('passes the provided parentNode through to the root visit', () => { const fakeParent = { type: 'WalkExternalParent', start: 0, end: 1, } as unknown as Node const root = { type: 'WalkRootWithParent', start: 1, end: 2, } as unknown as Node const visits: Array<{ node: Node; parent: Node | undefined }> = [] walk( root, (node, parentNode) => visits.push({ node, parent: parentNode }), fakeParent, ) expect(visits).toHaveLength(1) expect(visits[0]!.node).toBe(root) expect(visits[0]!.parent).toBe(fakeParent) }) })