import type { BlockView } from '../blockView' import { describe, expect, it } from 'vitest' import { toBlockViews } from '../blockView' import { createPythonAdapter } from '../languages/python' import { WASM_DIR, withBlocks } from '../testSupport' import { BlockAnchors } from './anchor' const py = createPythonAdapter(WASM_DIR) function views(source: string): Promise { return withBlocks(py, 'python', source, blocks => toBlockViews(blocks, { source, language: py, markers: [] }, undefined)) } function flatten(list: BlockView[]): BlockView[] { return list.flatMap(view => [view, ...flatten(view.children)]) } /** 各块的记号,按源码先后,连同它这块的第一行源码 */ async function anchors(source: string): Promise<[string, string][]> { return flatten(await views(source)).map(view => [view.anchor, view.code.split('\n')[0]]) } const SOURCE = `class ReferralRelation(Base): def display(self): if self.active: return self.name return "none" def other(self): if self.active: return 1 def helper(x): print(x) print(x) ` describe('块的位置记号', () => { it('有名字的块是外层各个有名字的块加自己的种类和名字;没名字的块是外层最近的有名字的块加种类和源码哈希;同一层重复的依次编号', async () => { const list = await anchors(SOURCE) const hashed = (anchor: string): string => anchor.replace(/@[0-9a-f]{8}/g, '@#') expect(list.map(([anchor, code]) => [hashed(anchor), code])).toEqual([ ['class ReferralRelation', 'class ReferralRelation(Base):'], ['class ReferralRelation > function display', 'def display(self):'], ['class ReferralRelation > function display > if @#', 'if self.active:'], ['class ReferralRelation > function display > return @#', 'return self.name'], ['class ReferralRelation > function display > return @#', 'return "none"'], ['class ReferralRelation > function other', 'def other(self):'], ['class ReferralRelation > function other > if @#', 'if self.active:'], ['class ReferralRelation > function other > return @#', 'return 1'], ['function helper', 'def helper(x):'], ['function helper > statement @#', 'print(x)'], ['function helper > statement @#[1]', 'print(x)'], ]) // 两个函数里一模一样的条件各在各的函数下面,互不影响;源码不同的两个返回哈希不同 expect(list[2][0].split(' > ').at(-1)).toBe(list[6][0].split(' > ').at(-1)) expect(list[3][0]).not.toBe(list[4][0]) expect(new Set(list.map(([anchor]) => anchor)).size).toBe(list.length) }) it('上面插几行、改缩进和空白:记号都不变', async () => { const before = await anchors(SOURCE) const after = await anchors(`import os\n\n# 新加的注释\n${SOURCE.replace('print(x)\n print(x)', 'print( x )\n print(x)')}`) const shifted = after.filter(([anchor]) => before.some(([old]) => old === anchor)) expect(shifted.map(([anchor]) => anchor)).toEqual(before.map(([anchor]) => anchor)) }) it('改别的函数的名字:别处的记号不变,改名的函数和它里面的块换了记号', async () => { const before = new Set((await anchors(SOURCE)).map(([anchor]) => anchor)) const after = (await anchors(SOURCE.replace('def other(self)', 'def renamed(self)'))).map(([anchor]) => anchor) const kept = after.filter(anchor => before.has(anchor)) expect(kept).toEqual([...before].filter(anchor => !anchor.includes('function other'))) expect(after.filter(anchor => !before.has(anchor)).every(anchor => anchor.includes('function renamed'))).toBe(true) }) it('没名字的块自己的源码改了:记号变了,旧问答认不回来;有名字的块改了主体、签名照样认得', async () => { const before = await anchors(SOURCE) const after = await anchors(SOURCE.replace('if self.active:\n return self.name', 'if self.visible:\n return self.name').replace('def helper(x):', 'def helper(x, y=1):')) const anchorOf = (list: [string, string][], code: string): string | undefined => list.find(([, line]) => line === code)?.[0] expect(after.map(([anchor]) => anchor)).not.toContain(anchorOf(before, 'if self.active:')) expect(anchorOf(after, 'return self.name')).toBe(anchorOf(before, 'return self.name')) expect(anchorOf(after, 'def helper(x, y=1):')).toBe(anchorOf(before, 'def helper(x):')) }) it('名字里的空白压成一个空格,空名字按没名字算', () => { const anchors = new BlockAnchors() expect(anchors.place('', 'variable', '{\n a,\n b\n}', '').anchor).toBe('variable { a, b }') const unnamed = anchors.place('scope', 'statement', ' ', 'x = 1') expect(unnamed.anchor).toMatch(/^scope > statement @[0-9a-f]{8}$/) expect(unnamed.inner).toBe('scope') }) })