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) } }