134 lines
5.7 KiB
TypeScript
134 lines
5.7 KiB
TypeScript
import type { IndexedFile, IndexEntry, Node, WorkspaceIndex } from './contract'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { createPythonAdapter } from './languages/python'
|
|
import { MemoryIndexHost, WASM_DIR } from './testSupport'
|
|
import { WorkspaceIndexes } from './workspaceIndex'
|
|
|
|
const py = createPythonAdapter(WASM_DIR)
|
|
|
|
/** 测试用的索引:每个函数一项,键是「包路径:函数名」,文件所在目录有 __init__.py 时包路径是目录,否则是空的。 */
|
|
function functionIndex(seen: string[] = []): WorkspaceIndex {
|
|
return {
|
|
id: 'test.functions',
|
|
languageId: 'python',
|
|
include: '**/*.py',
|
|
mentions: source => source.includes('def '),
|
|
entries: (root: Node, file: IndexedFile): IndexEntry[] => {
|
|
seen.push(file.path)
|
|
const directory = file.path.split('/').slice(0, -1).join('/')
|
|
const prefix = file.exists(`${directory}/./__init__.py`) ? directory : ''
|
|
return root.descendantsOfType('function_definition').flatMap((fn) => {
|
|
const name = fn?.childForFieldName('name')
|
|
return name ? [{ key: `${prefix}:${name.text}`, name: { startIndex: name.startIndex, endIndex: name.endIndex } }] : []
|
|
})
|
|
},
|
|
}
|
|
}
|
|
|
|
function setup(files: Record<string, string>): { host: MemoryIndexHost, indexes: WorkspaceIndexes } {
|
|
const host = new MemoryIndexHost(files, py)
|
|
return { host, indexes: new WorkspaceIndexes(host) }
|
|
}
|
|
|
|
describe('工作区索引', () => {
|
|
it('第一次查才建;查到的是名字的行列(列按 UTF-16 算),照文件、行排好', async () => {
|
|
const { host, indexes } = setup({
|
|
'b.py': '# 中文注释\ndef load():\n pass\n',
|
|
'a.py': 'def load():\n pass\n\n\ndef load():\n pass\n',
|
|
'notes.txt': 'def load():',
|
|
})
|
|
expect(host.lists).toBe(0)
|
|
expect(await indexes.lookup(functionIndex(), ':load')).toEqual([
|
|
{ uri: 'file:///w/a.py', start: { line: 0, character: 4 }, end: { line: 0, character: 8 } },
|
|
{ uri: 'file:///w/a.py', start: { line: 4, character: 4 }, end: { line: 4, character: 8 } },
|
|
{ uri: 'file:///w/b.py', start: { line: 1, character: 4 }, end: { line: 1, character: 8 } },
|
|
])
|
|
expect(await indexes.lookup(functionIndex(), ':missing')).toEqual([])
|
|
expect(host.lists).toBe(1)
|
|
})
|
|
|
|
it('预筛不过的文件不解析', async () => {
|
|
const seen: string[] = []
|
|
const { indexes } = setup({ 'a.py': 'def a(): pass\n', 'b.py': 'x = 1\n' })
|
|
await indexes.lookup(functionIndex(seen), ':a')
|
|
expect(seen).toEqual(['a.py'])
|
|
})
|
|
|
|
it('文件改了只重看那一份;新建的加进来;删掉的目录连里面的文件一起去掉', async () => {
|
|
const { host, indexes } = setup({ 'a.py': 'def a(): pass\n', 'pkg/b.py': 'def b(): pass\n', 'pkg/deep/c.py': 'def c(): pass\n' })
|
|
const index = functionIndex()
|
|
await indexes.lookup(index, ':a')
|
|
host.reads.length = 0
|
|
|
|
host.emit('change', 'a.py', 'def renamed(): pass\n')
|
|
expect(await indexes.lookup(index, ':a')).toEqual([])
|
|
expect(await indexes.lookup(index, ':renamed')).toHaveLength(1)
|
|
expect(host.reads).toEqual(['a.py'])
|
|
|
|
host.emit('create', 'd.py', '\n\ndef d(): pass\n')
|
|
expect((await indexes.lookup(index, ':d'))[0].start).toEqual({ line: 2, character: 4 })
|
|
|
|
host.emit('delete', 'pkg')
|
|
expect(await indexes.lookup(index, ':b')).toEqual([])
|
|
expect(await indexes.lookup(index, ':c')).toEqual([])
|
|
expect(host.reads).toEqual(['a.py', 'd.py'])
|
|
})
|
|
|
|
it('新建、删掉别的文件问过在不在的文件(__init__.py)时,问过它的文件跟着重看', async () => {
|
|
const { host, indexes } = setup({ 'pkg/mod.py': 'def run(): pass\n', 'other.py': 'def other(): pass\n' })
|
|
const index = functionIndex()
|
|
expect(await indexes.lookup(index, ':run')).toHaveLength(1)
|
|
host.reads.length = 0
|
|
|
|
host.emit('create', 'pkg/__init__.py', '')
|
|
expect(await indexes.lookup(index, 'pkg:run')).toHaveLength(1)
|
|
expect(host.reads.sort()).toEqual(['pkg/__init__.py', 'pkg/mod.py'])
|
|
|
|
host.emit('delete', 'pkg/__init__.py')
|
|
expect(await indexes.lookup(index, ':run')).toHaveLength(1)
|
|
})
|
|
|
|
it('建索引期间的改动建好后补上', async () => {
|
|
const { host, indexes } = setup({ 'a.py': 'def a(): pass\n' })
|
|
const index = functionIndex()
|
|
const first = indexes.lookup(index, ':a')
|
|
host.emit('change', 'a.py', 'def b(): pass\n')
|
|
await first
|
|
expect(await indexes.lookup(index, ':b')).toHaveLength(1)
|
|
expect(await indexes.lookup(index, ':a')).toEqual([])
|
|
})
|
|
|
|
it('读不了、规则出错的文件记下来,别的照常', async () => {
|
|
const { host, indexes } = setup({ 'a.py': 'def a(): pass\n', 'bad.py': 'def boom(): pass\n' })
|
|
host.files.delete('bad.py')
|
|
host.files.set('b.py', 'def b(): pass\n')
|
|
const index = functionIndex()
|
|
const broken: WorkspaceIndex = {
|
|
...index,
|
|
id: 'test.broken',
|
|
entries: (root, file) => {
|
|
if (file.path === 'a.py')
|
|
throw new Error('认不了')
|
|
return index.entries(root, file)
|
|
},
|
|
}
|
|
// bad.py 在找到的文件里,读的时候已经没了
|
|
host.list = async () => ['a.py', 'bad.py', 'b.py'].map(relative => host.file(relative))
|
|
expect(await indexes.lookup(broken, ':b')).toHaveLength(1)
|
|
expect([...host.reports].sort()).toEqual([
|
|
'索引 test.broken 看 file:///w/a.py 时出错:认不了',
|
|
'索引 test.broken 读不了 file:///w/bad.py:没有 bad.py',
|
|
])
|
|
})
|
|
|
|
it('同一个 id 只建一份;dispose 后不再盯着文件', async () => {
|
|
const { host, indexes } = setup({ 'a.py': 'def a(): pass\n' })
|
|
await indexes.lookup(functionIndex(), ':a')
|
|
await indexes.lookup(functionIndex(), ':a')
|
|
expect(host.lists).toBe(1)
|
|
expect(host.watching).toBe(1)
|
|
indexes.dispose()
|
|
expect(host.watching).toBe(0)
|
|
})
|
|
})
|