From 7d5432df3b67679b0199e691b8142325255ede38 Mon Sep 17 00:00:00 2001 From: cruldra Date: Tue, 13 Oct 2026 13:34:12 +1100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(vscode):=20=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=89=BE=E5=89=8D=E7=AB=AF=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E6=97=B6=EF=BC=8C=E7=BD=91=E5=9D=80=E6=9C=80=E5=90=8E=E4=B8=80?= =?UTF-8?q?=E6=AE=B5=E5=AD=97=E9=9D=A2=E9=87=8F=E5=90=8E=E9=9D=A2=E7=B4=A7?= =?UTF-8?q?=E8=B4=B4=E7=9A=84=20${...}=EF=BC=88messages${qs}=20=E8=BF=99?= =?UTF-8?q?=E7=B1=BB=EF=BC=89=E5=BD=93=E6=9F=A5=E8=AF=A2=E4=B8=B2=E5=8E=BB?= =?UTF-8?q?=E6=8E=89=E5=86=8D=E5=AF=B9=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/codeReader/callTree/requests.test.ts | 34 +++++++++++++++++++ vscode/src/codeReader/callTree/requests.ts | 20 ++++++++--- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/vscode/src/codeReader/callTree/requests.test.ts b/vscode/src/codeReader/callTree/requests.test.ts index f0f8fe3..b860133 100644 --- a/vscode/src/codeReader/callTree/requests.test.ts +++ b/vscode/src/codeReader/callTree/requests.test.ts @@ -337,6 +337,40 @@ const SESSION_API = [ '', ].join('\n') +/** 网址末尾拼查询串:`messages${qs}`;和看着像、却不是查询串的。 */ +const QUERY = [ + 'const API_PREFIX = "/api/v1";', + 'const TAIL = "messages";', + '', + 'export async function list(id: string, qs: string, name: string, n: number, a: string) {', + ` await fetch(\`\${API_PREFIX}/sessions/\${id}/messages\${qs}\`);`, + ` await fetch(\`/api/v1/sessions/\${id}/\${TAIL}\${qs}\`);`, + ` await fetch(\`/api/v1/files/\${name}\`);`, + ` await fetch(\`/api/v\${n}/agents\`);`, + ` await fetch(\`/api/v1/sessions/\${id}\${qs}\`);`, + ` await fetch(\`/api/v1/sessions/\${id}messages\${qs}\`);`, + ` await fetch(\`/api/v1/sessions/messages\${a}\${qs}\`);`, + ` await fetch(\`/api/v1/sessions/messages\${qs}/\`);`, + '}', + '', +].join('\n') + +describe('网址末尾拼的查询串', () => { + it('最后一段是字面量文字(常量展开的也算)后面紧贴一个插值时,插值当查询串去掉;整段就是插值的、这一段前面还有插值的、紧贴两个的、不在末尾的照写', async () => { + const file = 'web/src/lib/messages.ts' + expect((await requests({ [file]: QUERY }, 'GET')).map(line => line.split(' ')[3])).toEqual([ + `/api/v1/sessions/\${id}/messages`, + `/api/v1/sessions/\${id}/messages`, + `/api/v1/files/\${name}`, + `/api/v\${n}/agents`, + `/api/v1/sessions/\${id}\${qs}`, + `/api/v1/sessions/\${id}messages\${qs}`, + `/api/v1/sessions/messages\${a}\${qs}`, + `/api/v1/sessions/messages\${qs}/`, + ]) + }) +}) + describe('网址里拼的常量', () => { const files = { 'web/tsconfig.json': TSCONFIG, 'web/src/lib/root.ts': ROOT, 'web/src/lib/config.ts': CONFIG, 'web/src/app/(main)/chat/code/api.ts': SESSION_API } const API = 'web/src/app/(main)/chat/code/api.ts' diff --git a/vscode/src/codeReader/callTree/requests.ts b/vscode/src/codeReader/callTree/requests.ts index 7e1f1bd..6065ff5 100644 --- a/vscode/src/codeReader/callTree/requests.ts +++ b/vscode/src/codeReader/callTree/requests.ts @@ -31,7 +31,7 @@ type Method = string | typeof FROM_CALLER * 不在有名字的函数里时,名字是被调用的 fetch / GET / 包装函数本身。 */ export interface RequestEntry extends IndexEntry { - /** 写的路径:模板里指向字符串常量的 `${名字}` 换成常量的值,别的 `${...}` 照写,`?`、`#` 后面的去掉 */ + /** 写的路径:模板里指向字符串常量的 `${名字}` 换成常量的值,别的 `${...}` 照写;`?`、`#` 后面的,和最后一段末尾紧贴字面量的 `${...}`(查询串)去掉 */ path: string /** 发请求那个调用的范围,行和列从 0 起 */ at: CallRange @@ -494,7 +494,8 @@ async function senders(find: IndexFind, resolve: ModuleResolver): Promise<{ call } /** - * 网址拼成路径(uri 是网址所在的文件):`${名字}` 取得到常量的值就拼进去,取不到的照写;`?`、`#` 后面的去掉,照写的 `${...}` 里的不算。 + * 网址拼成路径(uri 是网址所在的文件):`${名字}` 取得到常量的值就拼进去,取不到的照写;`?`、`#` 后面的去掉,照写的 `${...}` 里的不算; + * 最后一段是字面量文字后面紧贴一个照写的 `${...}`(`messages${qs}`)时,它是拼上去的查询串,也去掉。 * 导入的常量和包装函数一样按 import 来源对到导出它的那处(见 exportOf),它的值里又导入的接着取;对不上、绕回正在取的(循环)取不到。 * 一份查询里同一个常量只取一次;取值时记着正在取的,所以要一处处拼,不能同时拼。 */ @@ -534,18 +535,27 @@ function pathsOf(find: IndexFind, resolve: ModuleResolver): (uri: string, url: r return async (uri, url) => { let path = '' + // 上一个照写的 `${...}` 在 path 里的结尾;末尾照写的 `${...}` 当查询串去掉时从哪截 + let written = 0 + let query: number | undefined for (const piece of url) { const value = typeof piece === 'string' ? piece : await valueOf(uri, piece.constant) if (value === undefined) { + const segment = path.lastIndexOf('/') + 1 + query = segment >= written && segment < path.length ? path.length : undefined path += typeof piece === 'string' ? piece : piece.written + written = path.length continue } const end = value.search(/[?#]/) + const literal = end >= 0 ? value.slice(0, end) : value + path += literal + if (literal) + query = undefined if (end >= 0) - return path + value.slice(0, end) - path += value + break } - return path + return query === undefined ? path : path.slice(0, query) } }