✨ feat(vscode): 代码阅读注释里不带路径的引用 [Name] 能点,本文件定义的优先、再找导入的
This commit is contained in:
1 parent
0958506681
commit
300d8a07b9
16 files changed
+600
-124
No files matched your search
@@ -0,0 +1,100 @@
|
||||
import type { ReferenceSources, SymbolNode } from './symbolLinks'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { createPythonAdapter } from './languages/python'
|
||||
import { createTypeScriptAdapter } from './languages/typescript'
|
||||
import { importsIn } from './libraryRules'
|
||||
import { importedNames, resolveReference, splitReference } from './symbolLinks'
|
||||
import { readFixture, WASM_DIR, withBlocks } from './testSupport'
|
||||
|
||||
const py = createPythonAdapter(WASM_DIR)
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
|
||||
function symbol(name: string, start: number, end: number, children: SymbolNode[] = []): SymbolNode {
|
||||
return { name, range: { startIndex: start, endIndex: end }, selection: { startIndex: start, endIndex: start + name.length }, children }
|
||||
}
|
||||
|
||||
/** 假的语言扩展:几个文件的文档符号,本文件几处名字的定义;记下问过哪些。 */
|
||||
function sources(files: Record<string, SymbolNode[]>, definitions: Record<number, { uri: string, at: number, length: number }> = {}): ReferenceSources & { asked: number[] } {
|
||||
const asked: number[] = []
|
||||
return {
|
||||
uri: 'file:///here.py',
|
||||
asked,
|
||||
symbols: async uri => files[uri] ?? [],
|
||||
definition: async (offset) => {
|
||||
asked.push(offset)
|
||||
const found = definitions[offset]
|
||||
return found && { uri: found.uri, selection: { startIndex: found.at, endIndex: found.at + found.length } }
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
describe('不带路径的引用:拆名字', () => {
|
||||
it('拆开 Class.member:名字和成员,单个名字没有成员', () => {
|
||||
expect(splitReference('Agent.owner_id')).toEqual({ head: 'Agent', members: ['owner_id'] })
|
||||
expect(splitReference('USER')).toEqual({ head: 'USER', members: [] })
|
||||
})
|
||||
})
|
||||
|
||||
describe('不带路径的引用:指到哪', () => {
|
||||
const imports = [{ name: 'Agent', offset: 20, range: { startIndex: 20, endIndex: 25 } }]
|
||||
|
||||
it('本文件定义的优先于导入的;不问语言扩展', async () => {
|
||||
const found = sources({ 'file:///here.py': [symbol('Agent', 100, 200)] }, { 20: { uri: 'file:///models.py', at: 5, length: 5 } })
|
||||
expect(await resolveReference('Agent', imports, found)).toEqual({ uri: 'file:///here.py', selection: { startIndex: 100, endIndex: 105 } })
|
||||
expect(found.asked).toEqual([])
|
||||
})
|
||||
|
||||
it('本文件的 Class.member 在类底下找成员;类有、成员没有就是找不到,不再去导入里找', async () => {
|
||||
const here = sources({ 'file:///here.py': [symbol('Agent', 100, 200, [symbol('owner_id', 120, 140)])] }, { 20: { uri: 'file:///models.py', at: 5, length: 5 } })
|
||||
expect(await resolveReference('Agent.owner_id', imports, here)).toEqual({ uri: 'file:///here.py', selection: { startIndex: 120, endIndex: 128 } })
|
||||
expect(await resolveReference('Agent.missing', imports, here)).toBeUndefined()
|
||||
expect(here.asked).toEqual([])
|
||||
})
|
||||
|
||||
it('导入语句里的名字不算本文件定义的:问那个名字的定义,Class.member 在定义所在文件的那个类底下找', async () => {
|
||||
const files = {
|
||||
'file:///here.py': [symbol('Agent', 20, 25)],
|
||||
'file:///models.py': [symbol('Other', 0, 4, [symbol('owner_id', 1, 3)]), symbol('Agent', 10, 90, [symbol('owner_id', 30, 50)])],
|
||||
}
|
||||
const found = sources(files, { 20: { uri: 'file:///models.py', at: 10, length: 5 } })
|
||||
expect(await resolveReference('Agent', imports, found)).toEqual({ uri: 'file:///models.py', selection: { startIndex: 10, endIndex: 15 } })
|
||||
expect(await resolveReference('Agent.owner_id', imports, found)).toEqual({ uri: 'file:///models.py', selection: { startIndex: 30, endIndex: 38 } })
|
||||
expect(found.asked).toEqual([20, 20])
|
||||
})
|
||||
|
||||
it('导入的是整个模块(定义落在文件开头)时成员从顶层找', async () => {
|
||||
const files = { 'file:///errors.py': [symbol('NotFound', 40, 80)] }
|
||||
const found = sources(files, { 7: { uri: 'file:///errors.py', at: 0, length: 0 } })
|
||||
expect(await resolveReference('errors.NotFound', [{ name: 'errors', offset: 7, range: { startIndex: 7, endIndex: 13 } }], found))
|
||||
.toEqual({ uri: 'file:///errors.py', selection: { startIndex: 40, endIndex: 48 } })
|
||||
})
|
||||
|
||||
it('本文件没定义、也没导入,或语言扩展答不上:找不到', async () => {
|
||||
expect(await resolveReference('Nowhere', imports, sources({}))).toBeUndefined()
|
||||
expect(await resolveReference('Agent', imports, sources({}))).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe('导入引进来的名字(取自预览已经读出的导入 facts)', () => {
|
||||
it('python:from 导入问原名的位置、别名按别名认,整个模块按第一节认、起了别名问最后一节,import * 没有', async () => {
|
||||
const source = 'from a.b import C, D as E\nimport os.path\nimport x.y as z\nfrom m import *\n'
|
||||
const names = await withBlocks(py, 'python', source, blocks => importedNames(importsIn(blocks)))
|
||||
expect(names.map(item => [item.name, source.slice(item.offset, item.offset + 1), source.slice(item.range.startIndex, item.range.endIndex)])).toEqual([
|
||||
['C', 'C', 'C'],
|
||||
['E', 'D', 'D as E'],
|
||||
['os', 'o', 'os.path'],
|
||||
['z', 'y', 'x.y as z'],
|
||||
])
|
||||
})
|
||||
|
||||
it('typeScript:具名、默认、命名空间导入', async () => {
|
||||
const source = 'import d, { b as c } from \'y\'\nimport * as ns from \'z\'\n'
|
||||
const names = await withBlocks(ts, 'typescript', source, blocks => importedNames(importsIn(blocks)))
|
||||
expect(names.map(item => [item.name, source.slice(item.offset, item.offset + 1)])).toEqual([['d', 'd'], ['c', 'b'], ['ns', 'n']])
|
||||
})
|
||||
|
||||
it('gate.py 导入的 require_desktop_client、AppException 都在,本文件定义的 LocalRuntimeDisabled 不在', async () => {
|
||||
const names = await withBlocks(py, 'python', readFixture('localRuntimeGate.py'), blocks => importedNames(importsIn(blocks)))
|
||||
expect(names.map(item => item.name)).toEqual(['annotations', 'Request', 'get_local_runtime_enabled', 'AppException', 'require_desktop_client'])
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user