✨ feat(vscode): 代码阅读的函数和类可以看调用树
This commit is contained in:
1 parent
8dc572bf94
commit
645542eb5c
17 files changed
+1679
-5
No files matched your search
@@ -0,0 +1,173 @@
|
||||
import type { CallEdge, CallRange, CallSymbol, CallTreeNode } from './model'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { buildCallTrees } from './model'
|
||||
|
||||
function symbol(name: string, line = 0, extra: Partial<CallSymbol> = {}): CallSymbol {
|
||||
return {
|
||||
uri: `file:///${name}`,
|
||||
line,
|
||||
character: 0,
|
||||
name,
|
||||
kind: 'function',
|
||||
path: `${name}.py`,
|
||||
...extra,
|
||||
}
|
||||
}
|
||||
|
||||
function at(line: number, character = 0): CallRange {
|
||||
return { line, character, endLine: line, endCharacter: character + 4 }
|
||||
}
|
||||
|
||||
function edge(caller: CallSymbol, callee: CallSymbol, line: number, character = 0): CallEdge {
|
||||
return { caller, callee, at: at(line, character) }
|
||||
}
|
||||
|
||||
function currents(node: CallTreeNode): CallTreeNode[] {
|
||||
return [...(node.current ? [node] : []), ...node.children.flatMap(currents)]
|
||||
}
|
||||
|
||||
describe('调用边收成调用树', () => {
|
||||
const mask = symbol('mask_phone', 27, { path: 'utils/phone.py' })
|
||||
|
||||
it('两个没有更上层调用者的根是两棵树,每棵记下当前块出现几次', () => {
|
||||
const read = symbol('读取用户资料', 0, { path: 'routes/user.py' })
|
||||
const profile = symbol('get_profile', 10, { path: 'services/user.py' })
|
||||
const serialize = symbol('serialize_user', 20, { path: 'services/user.py' })
|
||||
const load = symbol('load_user', 30, { path: 'services/user.py' })
|
||||
const exp = symbol('导出通讯录', 0, { path: 'routes/export.py' })
|
||||
const trees = buildCallTrees([
|
||||
edge(serialize, mask, 40),
|
||||
edge(load, mask, 50),
|
||||
edge(profile, serialize, 12),
|
||||
edge(profile, load, 14),
|
||||
edge(read, profile, 3),
|
||||
edge(exp, mask, 8),
|
||||
], mask)
|
||||
expect(trees.map(tree => tree.name)).toEqual(['读取用户资料', '导出通讯录'])
|
||||
expect(trees.map(tree => tree.hits)).toEqual([2, 1])
|
||||
expect(trees[0].root.detail).toBe('入口')
|
||||
expect(trees[0].root.where).toBe('routes/user.py:1')
|
||||
expect(currents(trees[0].root).map(node => node.where)).toEqual(['utils/phone.py:28', 'utils/phone.py:28'])
|
||||
expect(currents(trees[1].root)).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('同一个调用者的两段不同范围是当前块的两次出现', () => {
|
||||
const caller = symbol('serialize_user', 20, { path: 'services/user.py' })
|
||||
const root = symbol('读取用户资料', 0, { path: 'routes/user.py' })
|
||||
const trees = buildCallTrees([
|
||||
edge(caller, mask, 10),
|
||||
edge(caller, mask, 20),
|
||||
edge(root, caller, 3),
|
||||
], mask)
|
||||
expect(trees).toHaveLength(1)
|
||||
expect(trees[0].hits).toBe(2)
|
||||
const parent = trees[0].root.children[0]
|
||||
expect(parent.name).toBe('serialize_user')
|
||||
expect(parent.children.map(node => node.current)).toEqual([true, true])
|
||||
expect(parent.otherCalls).toBeUndefined()
|
||||
})
|
||||
|
||||
it('不在通向当前块的路径上的调用收成一行,不把那些符号画出来', () => {
|
||||
const caller = symbol('serialize_user', 20, { path: 'services/user.py' })
|
||||
const root = symbol('读取用户资料', 0, { path: 'routes/user.py' })
|
||||
const trees = buildCallTrees([
|
||||
edge(caller, mask, 10),
|
||||
edge(caller, symbol('format_name'), 11),
|
||||
edge(caller, symbol('format_name'), 12, 2),
|
||||
edge(root, caller, 3),
|
||||
edge(root, symbol('sideways'), 4),
|
||||
edge(root, symbol('leftover'), 5),
|
||||
], mask)
|
||||
expect(trees).toHaveLength(1)
|
||||
expect(trees[0].root.otherCalls).toBe(2)
|
||||
expect(trees[0].root.children).toHaveLength(1)
|
||||
expect(trees[0].root.children[0].otherCalls).toBe(2)
|
||||
expect(trees[0].root.children[0].children.map(node => node.name)).toEqual(['mask_phone'])
|
||||
const text = JSON.stringify(trees)
|
||||
expect(text).not.toContain('format_name')
|
||||
expect(text).not.toContain('sideways')
|
||||
expect(text).not.toContain('leftover')
|
||||
})
|
||||
|
||||
it('调用边绕回去就停,不把当前块再画到自己上面', () => {
|
||||
const caller = symbol('get_profile')
|
||||
const trees = buildCallTrees([
|
||||
edge(caller, mask, 1),
|
||||
edge(mask, caller, 2),
|
||||
], mask)
|
||||
expect(trees).toHaveLength(1)
|
||||
expect(trees[0].name).toBe('get_profile')
|
||||
expect(trees[0].root.children.map(node => node.name)).toEqual(['mask_phone'])
|
||||
expect(trees[0].root.children[0].current).toBe(true)
|
||||
expect(trees[0].root.children[0].children).toEqual([])
|
||||
expect(trees[0].hits).toBe(1)
|
||||
})
|
||||
|
||||
it('往上最多 8 层,再高的调用者不进这棵树', () => {
|
||||
const above = Array.from({ length: 9 }, (_, index) => symbol(`n${index}`, index))
|
||||
const edges = [edge(above[0], mask, 1)]
|
||||
for (let index = 1; index < above.length; index++)
|
||||
edges.push(edge(above[index], above[index - 1], index + 1))
|
||||
const trees = buildCallTrees(edges, mask)
|
||||
expect(trees).toHaveLength(1)
|
||||
expect(trees[0].name).toBe('n7')
|
||||
expect(JSON.stringify(trees)).not.toContain('n8')
|
||||
})
|
||||
|
||||
it('重复的同一段调用范围只算一次', () => {
|
||||
const caller = symbol('serialize_user')
|
||||
const trees = buildCallTrees([
|
||||
edge(caller, mask, 10),
|
||||
edge(caller, mask, 10),
|
||||
], mask)
|
||||
expect(trees).toHaveLength(1)
|
||||
expect(trees[0].hits).toBe(1)
|
||||
})
|
||||
|
||||
it('同一次查询里种类变了仍是同一个符号,不拆成两棵', () => {
|
||||
const asFunction = symbol('get_profile', 4, { uri: 'file:///services/user.py', path: 'services/user.py', kind: 'function' })
|
||||
const asClass = symbol('get_profile', 4, { uri: 'file:///services/user.py', path: 'services/user.py', kind: 'class' })
|
||||
const trees = buildCallTrees([
|
||||
edge(asFunction, mask, 8),
|
||||
edge(asClass, mask, 8),
|
||||
], mask)
|
||||
expect(trees).toHaveLength(1)
|
||||
expect(trees[0].hits).toBe(1)
|
||||
expect(trees[0].root.kind).toBe('function')
|
||||
})
|
||||
|
||||
it('没有任何调用者就没有树', () => {
|
||||
expect(buildCallTrees([], mask)).toEqual([])
|
||||
})
|
||||
|
||||
it('选中的是类时,直接调用它的那一层是「新建」,类自己标当前,不编造方法', () => {
|
||||
const order = symbol('OrderService', 13, { uri: 'file:///billing/order.py', path: 'billing/order.py', kind: 'class' })
|
||||
const create = symbol('create_order', 20, { path: 'billing/order.py', detail: '创建一笔订单' })
|
||||
const post = symbol('POST /orders', 0, { path: 'routes/order.py' })
|
||||
const trees = buildCallTrees([
|
||||
edge(create, order, 39),
|
||||
edge(post, create, 5),
|
||||
edge(create, symbol('charge'), 41),
|
||||
], order, { classTarget: true })
|
||||
expect(trees).toHaveLength(1)
|
||||
expect(trees[0].hits).toBe(1)
|
||||
const created = trees[0].root.children[0]
|
||||
expect(created.name).toBe('create_order')
|
||||
expect(created.detail).toBe('创建一笔订单')
|
||||
expect(created.otherCalls).toBe(1)
|
||||
const made = created.children[0]
|
||||
expect(made.name).toBe('新建 OrderService')
|
||||
expect(made.detail).toBe('进入这个类')
|
||||
expect(made.where).toBe('billing/order.py:40')
|
||||
expect(made.current).toBe(false)
|
||||
expect(made.kind).toBe('class')
|
||||
expect(made.children).toEqual([{
|
||||
name: 'OrderService',
|
||||
kind: 'class',
|
||||
where: 'billing/order.py:14',
|
||||
current: true,
|
||||
children: [],
|
||||
}])
|
||||
expect(JSON.stringify(trees)).not.toContain('方法')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user