✨ 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,215 @@
|
||||
/**
|
||||
* 把调用边收成调用树。边从测试里喂,不碰编辑器。
|
||||
* 身份是文件加名字起点;同一次查询里种类会变,种类只用来画图标。
|
||||
*/
|
||||
|
||||
/** 往上走到第 8 层就停,这一层当作这棵树的根。 */
|
||||
export const CALL_TREE_MAX_DEPTH = 8
|
||||
|
||||
/** 一个符号:文件、名字起点、给人看的路径和种类。 */
|
||||
export interface CallSymbol {
|
||||
uri: string
|
||||
/** 名字起点,行和列都从 0 起 */
|
||||
line: number
|
||||
character: number
|
||||
name: string
|
||||
/** 块种类,只决定图标,不参与认人 */
|
||||
kind: string
|
||||
/** 从工作区文件夹算起的路径 */
|
||||
path: string
|
||||
detail?: string
|
||||
}
|
||||
|
||||
/** 调用出现的范围,在调用方文件里,行和列从 0 起。 */
|
||||
export interface CallRange {
|
||||
line: number
|
||||
character: number
|
||||
endLine: number
|
||||
endCharacter: number
|
||||
}
|
||||
|
||||
/** 一次调用:谁在哪一段范围里调用了谁。同一段范围重复出现只算一次。 */
|
||||
export interface CallEdge {
|
||||
caller: CallSymbol
|
||||
callee: CallSymbol
|
||||
at: CallRange
|
||||
}
|
||||
|
||||
/** 树上的一个节点。当前块可以在同一棵里出现多次,每次一个节点。 */
|
||||
export interface CallTreeNode {
|
||||
name: string
|
||||
kind: string
|
||||
/** 第二行。根是「入口」,新建类是「进入这个类」,其余是符号自己的说明(没有就不写) */
|
||||
detail?: string
|
||||
/** 文件:行,行从 1 起 */
|
||||
where: string
|
||||
current: boolean
|
||||
children: CallTreeNode[]
|
||||
/** 不在通向当前块的路径上的调用次数;没有就不画那一行 */
|
||||
otherCalls?: number
|
||||
}
|
||||
|
||||
/** 一棵调用树:根是没有更上层调用者的入口。 */
|
||||
export interface CallTree {
|
||||
id: string
|
||||
/** 入口的名字 */
|
||||
name: string
|
||||
/** 这一棵里当前块出现几次 */
|
||||
hits: number
|
||||
root: CallTreeNode
|
||||
}
|
||||
|
||||
/** 文件加名字起点。种类不放进来。 */
|
||||
export function symbolKey(symbol: Pick<CallSymbol, 'uri' | 'line' | 'character'>): string {
|
||||
return `${symbol.uri}#${symbol.line}:${symbol.character}`
|
||||
}
|
||||
|
||||
function rangeKey(at: CallRange): string {
|
||||
return `${at.line}:${at.character}-${at.endLine}:${at.endCharacter}`
|
||||
}
|
||||
|
||||
function edgeKey(edge: CallEdge): string {
|
||||
return `${symbolKey(edge.caller)}>${symbolKey(edge.callee)}@${rangeKey(edge.at)}`
|
||||
}
|
||||
|
||||
function whereOf(symbol: CallSymbol): string {
|
||||
return `${symbol.path}:${symbol.line + 1}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 从目标往上收到根,再顺着走回目标。
|
||||
* 不通向目标的调用不展开,记在父节点的 otherCalls 上。
|
||||
* 选中的是类时,直接调用它的那一层写成「新建 类名」。
|
||||
*/
|
||||
export function buildCallTrees(edges: readonly CallEdge[], target: CallSymbol, options?: { classTarget?: boolean }): CallTree[] {
|
||||
const symbols = new Map<string, CallSymbol>()
|
||||
const remember = (symbol: CallSymbol): CallSymbol => {
|
||||
const key = symbolKey(symbol)
|
||||
const known = symbols.get(key)
|
||||
if (known)
|
||||
return known
|
||||
symbols.set(key, symbol)
|
||||
return symbol
|
||||
}
|
||||
const targetSymbol = remember(target)
|
||||
const targetKey = symbolKey(targetSymbol)
|
||||
const unique: CallEdge[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const edge of edges) {
|
||||
const normalized: CallEdge = { caller: remember(edge.caller), callee: remember(edge.callee), at: edge.at }
|
||||
const key = edgeKey(normalized)
|
||||
if (seen.has(key))
|
||||
continue
|
||||
seen.add(key)
|
||||
unique.push(normalized)
|
||||
}
|
||||
|
||||
const byCallee = new Map<string, CallEdge[]>()
|
||||
const byCaller = new Map<string, CallEdge[]>()
|
||||
for (const edge of unique) {
|
||||
pushTo(byCallee, symbolKey(edge.callee), edge)
|
||||
pushTo(byCaller, symbolKey(edge.caller), edge)
|
||||
}
|
||||
if ((byCallee.get(targetKey) ?? []).length === 0)
|
||||
return []
|
||||
|
||||
const depthOf = new Map<string, number>()
|
||||
const roots: CallSymbol[] = []
|
||||
const rootSeen = new Set<string>()
|
||||
const addRoot = (symbol: CallSymbol) => {
|
||||
const key = symbolKey(symbol)
|
||||
if (rootSeen.has(key))
|
||||
return
|
||||
rootSeen.add(key)
|
||||
roots.push(symbol)
|
||||
}
|
||||
const climb = (node: CallSymbol, depth: number, path: ReadonlySet<string>) => {
|
||||
const key = symbolKey(node)
|
||||
const known = depthOf.get(key)
|
||||
if (known !== undefined && known <= depth)
|
||||
return
|
||||
depthOf.set(key, depth)
|
||||
if (depth >= CALL_TREE_MAX_DEPTH) {
|
||||
addRoot(node)
|
||||
return
|
||||
}
|
||||
const callers = new Map<string, CallSymbol>()
|
||||
for (const edge of byCallee.get(key) ?? []) {
|
||||
const callerKey = symbolKey(edge.caller)
|
||||
if (!path.has(callerKey) && !callers.has(callerKey))
|
||||
callers.set(callerKey, edge.caller)
|
||||
}
|
||||
if (callers.size === 0) {
|
||||
addRoot(node)
|
||||
return
|
||||
}
|
||||
const nextPath = new Set(path)
|
||||
nextPath.add(key)
|
||||
for (const caller of callers.values())
|
||||
climb(caller, depth + 1, nextPath)
|
||||
}
|
||||
climb(targetSymbol, 0, new Set())
|
||||
|
||||
const classTarget = options?.classTarget === true
|
||||
const render = (node: CallSymbol, path: ReadonlySet<string>): CallTreeNode => {
|
||||
const key = symbolKey(node)
|
||||
const current = key === targetKey
|
||||
const nodeDepth = depthOf.get(key) ?? 0
|
||||
const children: CallTreeNode[] = []
|
||||
let otherCalls = 0
|
||||
if (!current) {
|
||||
const nextPath = new Set(path)
|
||||
nextPath.add(key)
|
||||
for (const edge of byCaller.get(key) ?? []) {
|
||||
const calleeKey = symbolKey(edge.callee)
|
||||
if (nextPath.has(calleeKey))
|
||||
continue
|
||||
const calleeDepth = depthOf.get(calleeKey)
|
||||
if (calleeDepth === undefined) {
|
||||
otherCalls++
|
||||
continue
|
||||
}
|
||||
if (calleeDepth >= nodeDepth)
|
||||
continue
|
||||
const child = classTarget && calleeKey === targetKey
|
||||
? {
|
||||
name: `新建 ${targetSymbol.name}`,
|
||||
kind: 'class',
|
||||
detail: '进入这个类',
|
||||
where: `${node.path}:${edge.at.line + 1}`,
|
||||
current: false,
|
||||
children: [render(edge.callee, nextPath)],
|
||||
}
|
||||
: render(edge.callee, nextPath)
|
||||
children.push(child)
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: node.name,
|
||||
kind: node.kind,
|
||||
...(node.detail && { detail: node.detail }),
|
||||
where: whereOf(node),
|
||||
current,
|
||||
children,
|
||||
...(otherCalls > 0 && { otherCalls }),
|
||||
}
|
||||
}
|
||||
|
||||
return roots.map((root) => {
|
||||
const rendered = render(root, new Set())
|
||||
rendered.detail = '入口'
|
||||
return { id: symbolKey(root), name: root.name, hits: countCurrent(rendered), root: rendered }
|
||||
})
|
||||
}
|
||||
|
||||
function pushTo(map: Map<string, CallEdge[]>, key: string, edge: CallEdge): void {
|
||||
const list = map.get(key)
|
||||
if (list)
|
||||
list.push(edge)
|
||||
else
|
||||
map.set(key, [edge])
|
||||
}
|
||||
|
||||
function countCurrent(node: CallTreeNode): number {
|
||||
return (node.current ? 1 : 0) + node.children.reduce((sum, child) => sum + countCurrent(child), 0)
|
||||
}
|
||||
Reference in new issue
Block a user