✨ feat(vscode): FastAPI 接口卡片的调用树带上前端调用它的地方
This commit is contained in:
1 parent
d4459e93b3
commit
29cedcb751
30 files changed
+1316
-130
No files matched your search
@@ -33,16 +33,28 @@ export interface CallEdge {
|
||||
caller: CallSymbol
|
||||
callee: CallSymbol
|
||||
at: CallRange
|
||||
/** 前端发请求调到接口的才有:方法和前端写的路径(`GET /api/v1/agents`),树上在调用方和接口之间多一个「请求」节点 */
|
||||
request?: string
|
||||
}
|
||||
|
||||
/** 点节点跳到哪:文件和位置,行和列从 0 起。 */
|
||||
export interface CallJump {
|
||||
uri: string
|
||||
line: number
|
||||
character: number
|
||||
}
|
||||
|
||||
/** 树上的一个节点。当前块可以在同一棵里出现多次,每次一个节点。 */
|
||||
export interface CallTreeNode {
|
||||
name: string
|
||||
/** 块种类;「请求」节点是 request */
|
||||
kind: string
|
||||
/** 第二行。根是「入口」,新建类是「进入这个类」,其余是符号自己的说明(没有就不写) */
|
||||
/** 第二行。根是「入口」(Next.js 页面是「页面 网址」),新建类是「进入这个类」,其余是符号自己的说明(没有就不写) */
|
||||
detail?: string
|
||||
/** 文件:行,行从 1 起 */
|
||||
where: string
|
||||
/** 符号是它的名字起点;「新建」「请求」节点是发生调用的那一处 */
|
||||
jump: CallJump
|
||||
current: boolean
|
||||
children: CallTreeNode[]
|
||||
/** 不在通向当前块的路径上的调用次数;没有就不画那一行 */
|
||||
@@ -76,10 +88,30 @@ function whereOf(symbol: CallSymbol): string {
|
||||
return `${symbol.path}:${symbol.line + 1}`
|
||||
}
|
||||
|
||||
/** 调用方里发生这次调用的那一处:「新建」「请求」节点写它、点了跳到它。 */
|
||||
function callSite(caller: CallSymbol, at: CallRange): Pick<CallTreeNode, 'where' | 'jump'> {
|
||||
return { where: `${caller.path}:${at.line + 1}`, jump: { uri: caller.uri, line: at.line, character: at.character } }
|
||||
}
|
||||
|
||||
/**
|
||||
* Next.js 页面的网址:文件在某个 `app/` 目录下、名字是 `page.(tsx|ts|jsx|js)` 时按目录算,路由分组 `(xxx)`、并行路由 `@slot` 去掉,
|
||||
* 动态段 `[id]`、`[[...section]]` 原样留;`app/page.tsx` 是 `/`。不是页面文件时没有。
|
||||
* @param path `/` 分隔的路径,取第一层叫 app 的目录
|
||||
*/
|
||||
export function nextPageUrl(path: string): string | undefined {
|
||||
const segments = path.split('/')
|
||||
const app = segments.indexOf('app')
|
||||
if (app < 0 || !/^page\.[jt]sx?$/.test(segments.at(-1)!))
|
||||
return undefined
|
||||
const route = segments.slice(app + 1, -1).filter(segment => !/^\(.*\)$/.test(segment) && !segment.startsWith('@'))
|
||||
return `/${route.join('/')}`
|
||||
}
|
||||
|
||||
/**
|
||||
* 从目标往上收到根,再顺着走回目标。
|
||||
* 不通向目标的调用不展开,记在父节点的 otherCalls 上。
|
||||
* 选中的是类时,直接调用它的那一层写成「新建 类名」。
|
||||
* 选中的是类时,直接调用它的那一层写成「新建 类名」;前端发请求调到接口的那一下写成「请求 方法 路径」。
|
||||
* 根的第二行是「入口」,Next.js 页面写成「页面 网址」。
|
||||
*/
|
||||
export function buildCallTrees(edges: readonly CallEdge[], target: CallSymbol, options?: { classTarget?: boolean }): CallTree[] {
|
||||
const symbols = new Map<string, CallSymbol>()
|
||||
@@ -96,7 +128,7 @@ export function buildCallTrees(edges: readonly CallEdge[], target: CallSymbol, o
|
||||
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 normalized: CallEdge = { ...edge, caller: remember(edge.caller), callee: remember(edge.callee) }
|
||||
const key = edgeKey(normalized)
|
||||
if (seen.has(key))
|
||||
continue
|
||||
@@ -171,17 +203,7 @@ export function buildCallTrees(edges: readonly CallEdge[], target: CallSymbol, o
|
||||
}
|
||||
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)
|
||||
children.push(calleeKey === targetKey ? targetChild(node, edge, nextPath) : render(edge.callee, nextPath))
|
||||
}
|
||||
}
|
||||
return {
|
||||
@@ -189,15 +211,27 @@ export function buildCallTrees(edges: readonly CallEdge[], target: CallSymbol, o
|
||||
kind: node.kind,
|
||||
...(node.detail && { detail: node.detail }),
|
||||
where: whereOf(node),
|
||||
jump: { uri: node.uri, line: node.line, character: node.character },
|
||||
current,
|
||||
children,
|
||||
...(otherCalls > 0 && { otherCalls }),
|
||||
}
|
||||
}
|
||||
|
||||
/** 直接调到目标的那一下:前端发的请求、新建类,各在中间多一个节点写调用的那一处;其余直接是目标。 */
|
||||
function targetChild(caller: CallSymbol, edge: CallEdge, path: ReadonlySet<string>): CallTreeNode {
|
||||
const target = render(edge.callee, path)
|
||||
if (edge.request)
|
||||
return { name: `请求 ${edge.request}`, kind: 'request', ...callSite(caller, edge.at), current: false, children: [target] }
|
||||
if (classTarget)
|
||||
return { name: `新建 ${targetSymbol.name}`, kind: 'class', detail: '进入这个类', ...callSite(caller, edge.at), current: false, children: [target] }
|
||||
return target
|
||||
}
|
||||
|
||||
return roots.map((root) => {
|
||||
const rendered = render(root, new Set())
|
||||
rendered.detail = '入口'
|
||||
const page = nextPageUrl(root.path)
|
||||
rendered.detail = page === undefined ? '入口' : `页面 ${page}`
|
||||
return { id: symbolKey(root), name: root.name, hits: countCurrent(rendered), root: rendered }
|
||||
})
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user