Download packages/tree-sitter-bash/test/node.test.ts from SaylorTwift/kimi-code: direct link, hf CLI and curl.
- Browser
- Download file 5.65 kB
-
https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/tree-sitter-bash/test/node.test.ts
- Command line
-
hf download hf://SaylorTwift/kimi-code/packages/tree-sitter-bash/test/node.test.ts
-
curl -L -o node.test.ts https://huggingface.co/SaylorTwift/kimi-code/resolve/main/packages/tree-sitter-bash/test/node.test.ts
5.65 kB
| import { describe, expect, it } from 'vitest'; | |
| import { createNode, descendantsOfType } from '#/node'; | |
| const SOURCE = 'echo foo; echo bar'; | |
| /** program | |
| * ├─ command "echo foo" | |
| * │ ├─ command_name "echo" | |
| * │ │ └─ word "echo" | |
| * │ └─ word "foo" | |
| * ├─ ";" (anonymous) | |
| * └─ command "echo bar" | |
| * └─ word "bar" | |
| */ | |
| function buildTree() { | |
| const program = createNode({ type: 'program', source: SOURCE, startIndex: 0, endIndex: SOURCE.length }); | |
| const cmd1 = program.addChild(createNode({ type: 'command', source: SOURCE, startIndex: 0, endIndex: 8 })); | |
| const name1 = cmd1.addChild(createNode({ type: 'command_name', source: SOURCE, startIndex: 0, endIndex: 4 })); | |
| name1.addChild(createNode({ type: 'word', source: SOURCE, startIndex: 0, endIndex: 4 })); | |
| cmd1.addChild(createNode({ type: 'word', source: SOURCE, startIndex: 5, endIndex: 8 })); | |
| program.addChild(createNode({ type: ';', source: SOURCE, startIndex: 8, endIndex: 9, isNamed: false })); | |
| const cmd2 = program.addChild(createNode({ type: 'command', source: SOURCE, startIndex: 10, endIndex: 18 })); | |
| cmd2.addChild(createNode({ type: 'word', source: SOURCE, startIndex: 15, endIndex: 18 })); | |
| return program; | |
| } | |
| describe('createNode', () => { | |
| it('pre-stores text as the UTF-16 slice of the source', () => { | |
| const node = createNode({ type: 'word', source: SOURCE, startIndex: 5, endIndex: 8 }); | |
| expect(node.text).toBe('foo'); | |
| expect(node.startIndex).toBe(5); | |
| expect(node.endIndex).toBe(8); | |
| expect(node.isNamed).toBe(true); | |
| expect(node.parent).toBeNull(); | |
| }); | |
| it('rejects out-of-range offsets', () => { | |
| expect(() => createNode({ type: 'word', source: SOURCE, startIndex: -1, endIndex: 2 })).toThrow(RangeError); | |
| expect(() => createNode({ type: 'word', source: SOURCE, startIndex: 3, endIndex: 2 })).toThrow(RangeError); | |
| expect(() => createNode({ type: 'word', source: SOURCE, startIndex: 0, endIndex: 100 })).toThrow(RangeError); | |
| }); | |
| it('rejects attaching a child that already has a parent', () => { | |
| const a = createNode({ type: 'program', source: SOURCE, startIndex: 0, endIndex: SOURCE.length }); | |
| const b = createNode({ type: 'program', source: SOURCE, startIndex: 0, endIndex: SOURCE.length }); | |
| const child = createNode({ type: 'word', source: SOURCE, startIndex: 0, endIndex: 4 }); | |
| a.addChild(child); | |
| expect(() => b.addChild(child)).toThrow(/already has a parent/); | |
| }); | |
| it('rejects children outside the parent range', () => { | |
| const parent = createNode({ type: 'command', source: SOURCE, startIndex: 0, endIndex: 8 }); | |
| expect(() => | |
| parent.addChild(createNode({ type: 'word', source: SOURCE, startIndex: 0, endIndex: 9 })), | |
| ).toThrow(RangeError); | |
| }); | |
| it('rejects overlapping or out-of-order siblings', () => { | |
| const parent = createNode({ type: 'command', source: SOURCE, startIndex: 0, endIndex: 8 }); | |
| parent.addChild(createNode({ type: 'word', source: SOURCE, startIndex: 2, endIndex: 5 })); | |
| expect(() => parent.addChild(createNode({ type: 'word', source: SOURCE, startIndex: 4, endIndex: 7 }))).toThrow( | |
| RangeError, | |
| ); | |
| expect(() => parent.addChild(createNode({ type: 'word', source: SOURCE, startIndex: 0, endIndex: 2 }))).toThrow( | |
| RangeError, | |
| ); | |
| // Adjacent (start == previous end) is fine. | |
| parent.addChild(createNode({ type: 'word', source: SOURCE, startIndex: 5, endIndex: 8 })); | |
| expect(parent.children).toHaveLength(2); | |
| }); | |
| }); | |
| describe('children vs namedChildren', () => { | |
| it('keeps anonymous nodes out of namedChildren but in children', () => { | |
| const program = buildTree(); | |
| expect(program.children.map((c) => c.type)).toEqual(['command', ';', 'command']); | |
| expect(program.namedChildren.map((c) => c.type)).toEqual(['command', 'command']); | |
| expect(program.children[1]?.isNamed).toBe(false); | |
| }); | |
| it('wires parent pointers', () => { | |
| const program = buildTree(); | |
| const [cmd1] = program.namedChildren; | |
| expect(cmd1?.parent).toBe(program); | |
| expect(cmd1?.namedChildren[0]?.parent).toBe(cmd1); | |
| }); | |
| }); | |
| describe('descendantsOfType', () => { | |
| it('returns matching named descendants in pre-order', () => { | |
| const program = buildTree(); | |
| const words = descendantsOfType(program, 'word'); | |
| expect(words.map((w) => w.text)).toEqual(['echo', 'foo', 'bar']); | |
| }); | |
| it('matches multiple types and skips anonymous nodes', () => { | |
| const program = buildTree(); | |
| const nodes = descendantsOfType(program, 'command', ';'); | |
| expect(nodes.map((n) => n.type)).toEqual(['command', 'command']); | |
| }); | |
| it('returns every named descendant when no type is given', () => { | |
| const program = buildTree(); | |
| expect(descendantsOfType(program).map((n) => n.type)).toEqual([ | |
| 'command', | |
| 'command_name', | |
| 'word', | |
| 'word', | |
| 'command', | |
| 'word', | |
| ]); | |
| }); | |
| it('does not include the root itself', () => { | |
| const leaf = createNode({ type: 'word', source: SOURCE, startIndex: 0, endIndex: 4 }); | |
| expect(descendantsOfType(leaf, 'word')).toEqual([]); | |
| }); | |
| it('handles pathologically deep trees without overflowing the stack', () => { | |
| // 200k-deep chain; a recursive walk would overflow the call stack. | |
| const depth = 200_000; | |
| const nodes = []; | |
| for (let i = 0; i < depth; i++) { | |
| nodes.push(createNode({ type: 'word', source: SOURCE, startIndex: 0, endIndex: 1 })); | |
| } | |
| for (let i = depth - 2; i >= 0; i--) { | |
| nodes[i]!.addChild(nodes[i + 1]!); | |
| } | |
| expect(descendantsOfType(nodes[0]!, 'word')).toHaveLength(depth - 1); | |
| }); | |
| }); | |