🐛 fix(vscode): 注释里不带路径的引用从被注释的声明起一层层往外找,同类的方法、类的成员都能链到

This commit is contained in:
cruldra committed 2026-10-12 16:56:31 +11:00
1 parent 15972c0b35
commit 5120e50228
15 files changed
+450 -167

No files matched your search

+90 -9
View File
@@ -1,9 +1,10 @@
import type { BlockView } from './blockView'
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 { importedNames, referenceScope, resolveReference, splitReference } from './symbolLinks'
import { readFixture, WASM_DIR, withBlocks } from './testSupport'
const py = createPythonAdapter(WASM_DIR)
@@ -40,14 +41,14 @@ describe('不带路径的引用:指到哪', () => {
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(await resolveReference('Agent', undefined, 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(await resolveReference('Agent.owner_id', undefined, imports, here)).toEqual({ uri: 'file:///here.py', selection: { startIndex: 120, endIndex: 128 } })
expect(await resolveReference('Agent.missing', undefined, imports, here)).toBeUndefined()
expect(here.asked).toEqual([])
})
@@ -57,21 +58,101 @@ describe('不带路径的引用:指到哪', () => {
'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(await resolveReference('Agent', undefined, imports, found)).toEqual({ uri: 'file:///models.py', selection: { startIndex: 10, endIndex: 15 } })
expect(await resolveReference('Agent.owner_id', undefined, 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))
expect(await resolveReference('errors.NotFound', undefined, [{ 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()
expect(await resolveReference('Nowhere', undefined, imports, sources({}))).toBeUndefined()
expect(await resolveReference('Agent', undefined, imports, sources({}))).toBeUndefined()
})
})
describe('不带路径的引用:从被注释的声明起一层层往外找(KDoc)', () => {
const imports = [{ name: 'Agent', offset: 20, range: { startIndex: 20, endIndex: 25 } }]
/** services.py 的样子:AgentService 里套着类 Config 和几个方法,get_default_agent 里有个局部变量;顶层还有个同名的 helper。 */
const here = (): ReturnType<typeof sources> => sources({
'file:///here.py': [
symbol('Agent', 20, 25),
symbol('AgentService', 100, 900, [
symbol('Config', 120, 180, [symbol('timeout', 140, 150)]),
symbol('get_agent_admitted', 200, 300),
symbol('get_default_agent', 400, 500, [symbol('agent', 450, 460)]),
symbol('helper', 600, 700),
]),
symbol('helper', 950, 990),
],
}, { 20: { uri: 'file:///models.py', at: 10, length: 5 } })
/** 各个声明的名字在源码里的下标:注释写在它的说明里时从这儿往外找。 */
const SERVICE = 100
const DEFAULT_AGENT = 400
const HELPER = 950
const at = (uri: string, start: number, name: string): unknown => ({ uri, selection: { startIndex: start, endIndex: start + name.length } })
it('方法说明里的 [同类的另一个方法] 找得到', async () => {
expect(await resolveReference('get_agent_admitted', DEFAULT_AGENT, imports, here())).toEqual(at('file:///here.py', 200, 'get_agent_admitted'))
})
it('类说明里的 [成员] 找得到;方法说明里的 [自己里面的名字] 也找得到', async () => {
expect(await resolveReference('get_default_agent', SERVICE, imports, here())).toEqual(at('file:///here.py', 400, 'get_default_agent'))
expect(await resolveReference('agent', DEFAULT_AGENT, imports, here())).toEqual(at('file:///here.py', 450, 'agent'))
})
it('近的先认:方法说明里的 [helper] 是同类的方法,顶层函数说明里的是顶层函数', async () => {
expect(await resolveReference('helper', DEFAULT_AGENT, imports, here())).toEqual(at('file:///here.py', 600, 'helper'))
expect(await resolveReference('helper', HELPER, imports, here())).toEqual(at('file:///here.py', 950, 'helper'))
})
it('顶层的、导入的照样找得到;导入语句里的名字不算本文件定义的', async () => {
const found = here()
expect(await resolveReference('AgentService', DEFAULT_AGENT, imports, found)).toEqual(at('file:///here.py', 100, 'AgentService'))
expect(await resolveReference('Agent', DEFAULT_AGENT, imports, found)).toEqual(at('file:///models.py', 10, 'Agent'))
expect(found.asked).toEqual([20])
})
it('同一个名字写在两块的说明里:方法说明里找得到同类的方法,顶层函数说明里、不在哪个声明的注释里都找不到', async () => {
expect(await resolveReference('get_agent_admitted', DEFAULT_AGENT, imports, here())).toEqual(at('file:///here.py', 200, 'get_agent_admitted'))
expect(await resolveReference('get_agent_admitted', HELPER, imports, here())).toBeUndefined()
expect(await resolveReference('get_agent_admitted', undefined, imports, here())).toBeUndefined()
})
it('写成 Class.member 的:第一段同样一层层找,再往下找成员', async () => {
expect(await resolveReference('Config.timeout', DEFAULT_AGENT, imports, here())).toEqual(at('file:///here.py', 140, 'timeout'))
expect(await resolveReference('AgentService.get_agent_admitted', HELPER, imports, here())).toEqual(at('file:///here.py', 200, 'get_agent_admitted'))
expect(await resolveReference('Config.timeout', HELPER, imports, here())).toBeUndefined()
})
})
describe('注释写在哪块里:从哪个声明起往外找', () => {
function block(anchor: string, defines: number | undefined, children: BlockView[] = []): BlockView {
return { anchor, children, ...(defines !== undefined && { defines: { startIndex: defines, endIndex: defines + 1 } }) } as BlockView
}
const blocks = [
block('class AgentService', 100, [
block('class AgentService > function get_default_agent', 400, [
block('class AgentService > function get_default_agent > if @3fa1c2d0', undefined),
]),
]),
block('comment @0b1c2d3e', undefined),
]
it('有名字的块(声明)是它自己的名字;没名字的(注释、语句)取外层最近的有名字的块', () => {
expect(referenceScope(blocks, 'class AgentService')).toBe(100)
expect(referenceScope(blocks, 'class AgentService > function get_default_agent')).toBe(400)
expect(referenceScope(blocks, 'class AgentService > function get_default_agent > if @3fa1c2d0')).toBe(400)
})
it('顶层没名字的块、块树里没有的块:没有,只从文件顶层找', () => {
expect(referenceScope(blocks, 'comment @0b1c2d3e')).toBeUndefined()
expect(referenceScope(blocks, 'class Gone')).toBeUndefined()
})
})