Files
superwork/vscode/src/codeReader/editor/referenceTargets.test.ts
T

150 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { SourceRange } from '../contract'
import type { LinkTarget, SymbolNode } from '../symbolLinks'
import type { ReferenceInputs, TargetSources } from './referenceTargets'
import { describe, expect, it, vi } from 'vitest'
import { ReferenceTargets } from './referenceTargets'
const OWN = 'file:///app/gate.py'
const BUILTINS = 'file:///typeshed/builtins.pyi'
const RUNTIME_ERROR: LinkTarget = { uri: BUILTINS, selection: { startIndex: 100, endIndex: 112 } }
function symbol(name: string, start: number, end: number): SymbolNode {
return { name, range: { startIndex: start, endIndex: end }, selection: { startIndex: start, endIndex: start + name.length }, children: [] }
}
/** 本文件定义了 `load`,代码里在 30 处用到 `RuntimeError`、在 60 处用到 `str`,问 30 处的定义落到 builtins.pyi;各文件眼下都是第 1 版 */
function sources() {
const versions = new Map([[OWN, 1], [BUILTINS, 1]])
const symbols = vi.fn(async (uri: string): Promise<SymbolNode[]> => uri === OWN ? [symbol('load', 0, 80)] : [symbol('RuntimeError', 100, 200)])
const definition = vi.fn(async (offset: number): Promise<LinkTarget | undefined> => offset === 30 ? RUNTIME_ERROR : undefined)
const given: TargetSources = {
uri: OWN,
symbols,
definition,
builtin: name => name === 'str',
versionOf: uri => versions.get(uri),
}
return { given, symbols, definition, versions }
}
function inputs(version = 1, uses = new Map<string, SourceRange[]>([['RuntimeError', [{ startIndex: 30, endIndex: 42 }]], ['str', [{ startIndex: 60, endIndex: 63 }]]])): ReferenceInputs {
return { version, sent: undefined, imports: [], uses }
}
describe('注释里引用的解析结果(ReferenceTargets)', () => {
it('同一份文档里同一个引用检查过,再悬停、再点:文档符号、定义都只问一次', async () => {
const { given, symbols, definition } = sources()
const targets = new ReferenceTargets(given)
const now = inputs()
const checked = await Promise.all([targets.resolve(now, { symbol: 'RuntimeError', block: 'function load' }), targets.resolve(now, { symbol: 'load' })])
expect(checked).toEqual([RUNTIME_ERROR, { uri: OWN, selection: { startIndex: 0, endIndex: 4 } }])
expect(await targets.resolve(now, { symbol: 'RuntimeError', block: 'function load' })).toEqual(RUNTIME_ERROR)
expect(await targets.resolve(now, { symbol: 'RuntimeError', block: 'function load' })).toEqual(RUNTIME_ERROR)
expect(symbols).toHaveBeenCalledTimes(1)
expect(definition).toHaveBeenCalledTimes(1)
})
it('同时问同一个引用:等同一个 Promise,只问一次', async () => {
const { given, definition } = sources()
const targets = new ReferenceTargets(given)
const now = inputs()
const first = targets.resolve(now, { symbol: 'RuntimeError' })
const second = targets.resolve(now, { symbol: 'RuntimeError' })
expect(second).toBe(first)
await first
expect(definition).toHaveBeenCalledTimes(1)
})
it('同一个名字写在不同块的注释里分开记', async () => {
const { given, definition } = sources()
const targets = new ReferenceTargets(given)
const now = inputs()
await targets.resolve(now, { symbol: 'RuntimeError', block: 'function load' })
await targets.resolve(now, { symbol: 'RuntimeError' })
expect(definition).toHaveBeenCalledTimes(2)
})
it('文档换了版本,或块树、导入、用到的名字换了一份:重新问', async () => {
const { given, symbols, definition } = sources()
const targets = new ReferenceTargets(given)
const reference = { symbol: 'RuntimeError' }
const first = inputs(1)
await targets.resolve(first, reference)
await targets.resolve(inputs(2), reference)
expect(definition).toHaveBeenCalledTimes(2)
expect(symbols).toHaveBeenCalledTimes(2)
const second = inputs(2)
await targets.resolve(second, reference)
await targets.resolve({ ...second, imports: [] }, reference)
await targets.resolve({ ...second, sent: { blocks: [] } }, reference)
expect(definition).toHaveBeenCalledTimes(5)
})
it('找不到的、语言自带的不留,下次再问;语言扩展先答空、后来答上了的用后来的', async () => {
const { given, definition } = sources()
const targets = new ReferenceTargets(given)
const now = inputs()
expect(await targets.resolve(now, { symbol: 'str' })).toBe('builtin')
expect(await targets.resolve(now, { symbol: 'str' })).toBe('builtin')
expect(definition.mock.calls.filter(([offset]) => offset === 60)).toHaveLength(2)
definition.mockResolvedValueOnce(undefined)
expect(await targets.resolve(now, { symbol: 'RuntimeError' })).toBeUndefined()
expect(await targets.resolve(now, { symbol: 'RuntimeError' })).toEqual(RUNTIME_ERROR)
})
it('问出错的不留:下次再问', async () => {
const { given, definition } = sources()
const targets = new ReferenceTargets(given)
const now = inputs()
definition.mockRejectedValueOnce(new Error('Pylance 还没起来'))
await expect(targets.resolve(now, { symbol: 'RuntimeError' })).rejects.toThrow('Pylance 还没起来')
expect(await targets.resolve(now, { symbol: 'RuntimeError' })).toEqual(RUNTIME_ERROR)
})
it('本文件的文档符号答空的不留:下次再问', async () => {
const { given, symbols } = sources()
const targets = new ReferenceTargets(given)
const now = inputs()
symbols.mockResolvedValueOnce([])
expect(await targets.resolve(now, { symbol: 'load' })).toBeUndefined()
expect(await targets.resolve(now, { symbol: 'load' })).toEqual({ uri: OWN, selection: { startIndex: 0, endIndex: 4 } })
expect(symbols).toHaveBeenCalledTimes(2)
})
it('指到的别的文件改过(换了版本)、关掉了:重新问', async () => {
const { given, definition, versions } = sources()
const targets = new ReferenceTargets(given)
const now = inputs()
const reference = { symbol: 'RuntimeError' }
await targets.resolve(now, reference)
versions.set(BUILTINS, 2)
await targets.resolve(now, reference)
expect(definition).toHaveBeenCalledTimes(2)
await targets.resolve(now, reference)
expect(definition).toHaveBeenCalledTimes(2)
versions.delete(BUILTINS)
await targets.resolve(now, reference)
expect(definition).toHaveBeenCalledTimes(3)
})
it('成员落在别的文件的文档符号里:那个文件改过后连文档符号一起重新问', async () => {
const { given, symbols, versions } = sources()
const targets = new ReferenceTargets(given)
symbols.mockImplementation(async uri => uri === OWN
? [symbol('load', 0, 80)]
: [{ ...symbol('RuntimeError', 100, 200), children: [symbol('args', 150, 160)] }])
const now = inputs()
const reference = { symbol: 'RuntimeError.args' }
expect(await targets.resolve(now, reference)).toEqual({ uri: BUILTINS, selection: { startIndex: 150, endIndex: 154 } })
expect(await targets.resolve(now, reference)).toEqual({ uri: BUILTINS, selection: { startIndex: 150, endIndex: 154 } })
expect(symbols.mock.calls.filter(([uri]) => uri === BUILTINS)).toHaveLength(1)
versions.set(BUILTINS, 2)
await targets.resolve(now, reference)
expect(symbols.mock.calls.filter(([uri]) => uri === BUILTINS)).toHaveLength(2)
})
})