✨ feat(vscode): FastAPI 接口卡片的调用树带上前端调用它的地方

This commit is contained in:
cruldra committed 2026-10-12 12:22:51 +11:00
1 parent d4459e93b3
commit 29cedcb751
30 files changed
+1316 -130

No files matched your search

@@ -0,0 +1,95 @@
import { describe, expect, it } from 'vitest'
import { INDEX_GRAMMARS, MemoryIndexHost } from '../testSupport'
import { WorkspaceIndexes } from '../workspaceIndex'
import { REQUESTS_INDEX } from './requests'
const STORE = [
'export function useAgentStore() {',
' let later = () => fetch("/api/v1/agents/later", { method: "PATCH" })',
' return later',
'}',
'',
'class AgentApi {',
` rename = async (id: string) => { await this.http.PATCH(\`/api/v1/agents/\${id}\`) }`,
'}',
'',
].join('\n')
const PAGE = [
'import { api } from "@/lib/api"',
'',
'const MyAgentsPage = async () => {',
' const { data } = await api.GET("/api/v1/agents", { params: { query: {} } })',
' return <div>{data?.length}</div>',
'}',
'',
'export default MyAgentsPage',
'',
].join('\n')
const AGENTS = [
'export async function fetchDefaultAgent() {',
' const res = await fetch("/api/v1/agents/default", { credentials: "include" })',
' return res.json()',
'}',
'',
'export const createAgent = async (body: unknown) => {',
` await fetch(\`/api/v1/agents?force=\${1}\`, { method: "POST", body: JSON.stringify(body) })`,
'}',
'',
'export function useRemoveAgent(id: string) {',
` return useMutation({ mutationFn: () => api.DELETE(\`/api/v1/agents/\${id}\`) })`,
'}',
'',
'export async function guessed(method: string, init: RequestInit) {',
' await fetch("/api/v1/agents", { method })',
' await fetch("/api/v1/agents", init)',
' await fetch("/api/v1/agents", { ...init })',
' await fetch(url)',
' await axios.get("/api/v1/agents")',
'}',
'',
'class Client {',
` load(orderId: string) { /* 中文 */ return this.http.GET(\`/api/v1/commerce/orders/\${orderId}\` as never) }`,
'}',
'',
'const pending = fetch(\'/api/v1/health#top\')',
'',
].join('\n')
async function requests(files: Record<string, string>, method: string): Promise<string[]> {
const found = await new WorkspaceIndexes(new MemoryIndexHost(files, INDEX_GRAMMARS)).find(REQUESTS_INDEX, method)
return found.map(({ location, entry }) => {
const file = location.uri.replace(`${MemoryIndexHost.FOLDER}/`, '')
const call = `${entry.at.line}:${entry.at.character}`
return `${file} ${entry.caller ?? '(顶层)'}@${location.start.line}:${location.start.character} ${entry.key} ${entry.path} ${call}`
})
}
describe('前端发请求的地方', () => {
const files = { 'web/src/app/(main)/my-agents/page.tsx': PAGE, 'web/src/lib/agents.ts': AGENTS }
it('认 X.GET 这类成员名是大写 HTTP 方法的调用,名字归最内层有名字的函数(箭头函数取赋值的名字)', async () => {
expect(await requests(files, 'GET')).toEqual([
'web/src/app/(main)/my-agents/page.tsx MyAgentsPage@2:6 GET /api/v1/agents 3:25',
'web/src/lib/agents.ts fetchDefaultAgent@0:22 GET /api/v1/agents/default 1:20',
`web/src/lib/agents.ts load@22:2 GET /api/v1/commerce/orders/\${orderId} 22:42`,
'web/src/lib/agents.ts (顶层)@25:16 GET /api/v1/health 25:16',
])
})
it('fetch 的方法看 method 字面量,没写是 GET;method 不是字面量、第二个参数不是对象字面量、带展开的都不认;查询串去掉', async () => {
expect(await requests(files, 'POST')).toEqual(['web/src/lib/agents.ts createAgent@5:13 POST /api/v1/agents 6:8'])
})
it('赋给 let 的箭头函数不算有名字,归到外层;赋给类字段的取字段名', async () => {
expect(await requests({ 'web/src/lib/store.ts': STORE }, 'PATCH')).toEqual([
'web/src/lib/store.ts useAgentStore@0:16 PATCH /api/v1/agents/later 1:20',
`web/src/lib/store.ts rename@6:2 PATCH /api/v1/agents/\${id} 6:41`,
])
})
it('匿名回调里发的请求归到外层有名字的函数', async () => {
expect(await requests(files, 'DELETE')).toEqual([`web/src/lib/agents.ts useRemoveAgent@9:16 DELETE /api/v1/agents/\${id} 10:41`])
})
})