✨ feat(vscode): 代码阅读的函数和类可以看调用树

This commit is contained in:
cruldra committed 2026-10-11 00:37:24 -04:00
1 parent 8dc572bf94
commit 645542eb5c
17 files changed
+1679 -5

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
import type { CallRange, CallSymbol } from './model'
import { describe, expect, it } from 'vitest'
import { collectCallEdges } from './walk'
function symbol(name: string, kind = 'function'): CallSymbol {
return { uri: `file:///${name}`, line: 0, character: 0, name, kind, path: `${name}.py` }
}
function range(line: number, character = 0): CallRange {
return { line, character, endLine: line, endCharacter: character + 3 }
}
describe('顺着调用者往上收集调用边', () => {
it('有环就停,不再问已经走过的那个', async () => {
const target = symbol('mask_phone')
const caller = symbol('get_profile')
const asked: string[] = []
const edges = await collectCallEdges(target, async (node) => {
asked.push(node.name)
if (node.name === 'mask_phone')
return [{ from: caller, ranges: [range(1)] }]
return [{ from: target, ranges: [range(2)] }]
}, async () => [])
expect(asked).toEqual(['mask_phone', 'get_profile'])
expect(edges).toEqual([{ caller, callee: target, at: range(1) }])
})
it('往上最多问 8 层,第 9 层的调用者不再问', async () => {
const names = ['t', ...Array.from({ length: 9 }, (_, index) => `n${index}`)]
const asked: string[] = []
await collectCallEdges(symbol('t'), async (node) => {
asked.push(node.name)
const index = names.indexOf(node.name)
const next = names[index + 1]
return next ? [{ from: symbol(next), ranges: [range(index + 1)] }] : []
}, async () => [])
expect(asked).toEqual(['t', 'n0', 'n1', 'n2', 'n3', 'n4', 'n5', 'n6'])
})
it('重复的调用范围去掉,同一调用者的两段不同范围都留下', async () => {
const target = symbol('mask_phone')
const caller = symbol('serialize_user')
const edges = await collectCallEdges(target, async () => [{
from: caller,
ranges: [range(10), range(10), range(20)],
}], async () => [])
expect(edges.map(item => item.at.line)).toEqual([10, 20])
})
it('种类不同但名字起点相同的调用者是同一个人,只往上问一次', async () => {
const target = symbol('mask_phone')
const asFunction = symbol('get_profile', 'function')
const asClass = symbol('get_profile', 'class')
const asked: string[] = []
const edges = await collectCallEdges(target, async (node) => {
asked.push(`${node.name}:${node.kind}`)
if (node.name === 'mask_phone')
return [{ from: asFunction, ranges: [range(1)] }, { from: asClass, ranges: [range(1)] }]
return []
}, async () => [])
expect(asked.filter(item => item.startsWith('get_profile'))).toEqual(['get_profile:function'])
expect(edges).toHaveLength(1)
})
it('路径上的调用者还要问它调用了谁,不通向当前块的边也留下;当前块自己不再往下问', async () => {
const target = symbol('mask_phone')
const caller = symbol('serialize_user')
const side = symbol('format_name')
const outgoingAsked: string[] = []
const edges = await collectCallEdges(target, async (node) => {
if (node.name === 'mask_phone')
return [{ from: caller, ranges: [range(4)] }]
return []
}, async (node) => {
outgoingAsked.push(node.name)
return [
{ to: target, ranges: [range(4)] },
{ to: side, ranges: [range(8), range(8)] },
]
})
expect(outgoingAsked).toEqual(['serialize_user'])
expect(edges).toEqual([
{ caller, callee: target, at: range(4) },
{ caller, callee: side, at: range(8) },
])
})
it('取消之后不再问下一个人', async () => {
const target = symbol('mask_phone')
const caller = symbol('get_profile')
let stop = false
const asked: string[] = []
const edges = await collectCallEdges(target, async (node) => {
asked.push(node.name)
stop = true
return [{ from: caller, ranges: [range(1)] }]
}, async () => [], () => stop)
expect(asked).toEqual(['mask_phone'])
expect(edges).toEqual([])
})
})