From 6ad2a5b27a45cc3bfcefc4e8b4d8b0d7f1e004e0 Mon Sep 17 00:00:00 2001 From: cruldra Date: Thu, 15 Oct 2026 21:06:58 +1100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(vscode):=20=E5=AE=9E?= =?UTF-8?q?=E5=8F=82=E9=87=8C=E7=9A=84=E9=97=AD=E5=8C=85=E3=80=81async=20?= =?UTF-8?q?=E5=9D=97=E5=86=99=E4=BA=86=E6=9F=A5=E8=AF=A2=E6=88=96=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E6=97=B6=E4=B8=8D=E5=86=8D=E5=A4=96=E9=9D=A2=E9=82=A3?= =?UTF-8?q?=E5=8F=A5=E3=80=81=E5=AD=90=E5=9D=97=E5=90=84=E5=87=BA=E4=B8=80?= =?UTF-8?q?=E5=BC=A0=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit valueCard 交给规则一个 own:拆成子块的那几段不归这一块,sqlx 找查询、axum 找路由时跳过;顺带去掉 rustRows 里 nameRow 没用的参数(页面那边的类型检查报它) --- vscode/src/codeReader/rules/axum.ts | 11 +++++---- vscode/src/codeReader/rules/rustLibs.test.ts | 25 ++++++++++++++++++++ vscode/src/codeReader/rules/rustRows.ts | 25 +++++++++++--------- vscode/src/codeReader/rules/sqlx.ts | 12 ++++++---- 4 files changed, 53 insertions(+), 20 deletions(-) diff --git a/vscode/src/codeReader/rules/axum.ts b/vscode/src/codeReader/rules/axum.ts index a84029f..a79d708 100644 --- a/vscode/src/codeReader/rules/axum.ts +++ b/vscode/src/codeReader/rules/axum.ts @@ -23,8 +23,8 @@ export const axumRule: LibraryRule = { const route = attributeRoute(block) return route ? card(block, route, context) : undefined } - return valueCard(block, context, (value) => { - const [route] = routesOn(value).filter(item => !handled(value, item.handler)) + return valueCard(block, context, (value, own) => { + const [route] = routesOn(value, own).filter(item => !handled(value, item.handler)) return route ? routerCard(value, route, context) : undefined }) }, @@ -116,7 +116,10 @@ function requestRow(route: Route, params: Inline[][]): PropertyRow { } } -function routesOn(node: Node, found: (Route & { handlerNode?: Node })[] = []): (Route & { handlerNode?: Node })[] { +/** 式子里写的各个 `.route(路径, 方法(函数))`,外层在前;拆成子块的那几段(`own` 为假)不看。 */ +function routesOn(node: Node, own: (node: Node) => boolean, found: (Route & { handlerNode?: Node })[] = []): (Route & { handlerNode?: Node })[] { + if (!own(node)) + return found if (node.type === 'call_expression') { const callee = node.childForFieldName('function') const field = callee?.type === 'field_expression' ? callee.childForFieldName('field')?.text : undefined @@ -137,7 +140,7 @@ function routesOn(node: Node, found: (Route & { handlerNode?: Node })[] = []): ( } } for (const child of named(node)) - routesOn(child, found) + routesOn(child, own, found) return found } diff --git a/vscode/src/codeReader/rules/rustLibs.test.ts b/vscode/src/codeReader/rules/rustLibs.test.ts index 99238ee..7cc190d 100644 --- a/vscode/src/codeReader/rules/rustLibs.test.ts +++ b/vscode/src/codeReader/rules/rustLibs.test.ts @@ -279,3 +279,28 @@ describe('函数体里的 sqlx 查询、axum 路由', () => { ]) }) }) + +describe('实参里的闭包、async 块拆成子块时', () => { + it('里面写的查询、路由只出在子块那一张卡上,外面那句不再跟着认成查询、路由', async () => { + const source = [ + 'use axum::Router;', + 'use axum::routing::get;', + 'use sqlx::PgPool;', + '', + 'async fn start(pool: PgPool) {', + ' tokio::spawn(async move {', + ' sqlx::query!("DELETE FROM jobs").execute(&pool).await;', + ' });', + ' rows.iter().for_each(|row| {', + ' let app = Router::new().route("/health", get(health));', + ' });', + '}', + '', + ].join('\n') + const [start] = (await views(source)).filter(block => block.kind === 'function') + expect((start?.children ?? []).map(block => [block.kind, block.children.map(child => child.kind)])).toEqual([ + ['statement', ['sqlx.query']], + ['statement', ['axum.route']], + ]) + }) +}) diff --git a/vscode/src/codeReader/rules/rustRows.ts b/vscode/src/codeReader/rules/rustRows.ts index eb3b947..f318e57 100644 --- a/vscode/src/codeReader/rules/rustRows.ts +++ b/vscode/src/codeReader/rules/rustRows.ts @@ -55,7 +55,7 @@ export function structRows(block: ClassBlock, say: RustSay): PropertyRow[] { const docs = paramDocs(doc, fields.map(field => field.name.text)) const described = fields.some(field => docs.of(field.name.text)) return [ - nameRow(block.facts.name, say, 'type'), + nameRow(block.facts.name, 'type'), ...docRow(doc), ...unmatchedParamRows(docs.unmatched), visibilityRow(block), @@ -73,7 +73,7 @@ export function structRows(block: ClassBlock, say: RustSay): PropertyRow[] { export function moduleRows(block: ModuleBlock, say: RustSay): PropertyRow[] { return [ - nameRow(block.facts.name, say, 'variable'), + nameRow(block.facts.name, 'variable'), ...docRow(block.facts.doc && say.doc(block.facts.doc)), visibilityRow(block), ] @@ -172,7 +172,7 @@ function cfgText(predicate: CfgPredicate, nested: boolean): Inline[] { export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] { return [ - nameRow(block.facts.name, say, 'type'), + nameRow(block.facts.name, 'type'), ...docRow(block.facts.doc && say.doc(block.facts.doc)), ...tableRow('变体', ['变体', '携带的数据'], block.facts.members.map(member => ({ cells: [[sourceName(member.name, 'type')], payloadValue(member.value, say)], @@ -192,7 +192,7 @@ export function functionRows(block: FunctionBlock, say: RustSay): PropertyRow[] const docs = paramDocs(doc, parameters.map(param => param.name.text)) const described = parameters.some(param => docs.of(param.name.text)) return [ - nameRow(block.facts.name, say, 'function'), + nameRow(block.facts.name, 'function'), ...docRow(doc), ...unmatchedParamRows(docs.unmatched), ...(block.facts.async ? [{ label: '异步', value: [keyword('是')] }] : []), @@ -212,7 +212,7 @@ export function functionRows(block: FunctionBlock, say: RustSay): PropertyRow[] export function traitRows(block: TraitBlock, say: RustSay): PropertyRow[] { return [ - nameRow(block.facts.name, say, 'type'), + nameRow(block.facts.name, 'type'), ...docRow(block.facts.doc && say.doc(block.facts.doc)), ...methodRows(block, say), ] @@ -245,7 +245,7 @@ function methodRows(block: TraitBlock | ImplBlock, say: RustSay): PropertyRow[] })) } -function nameRow(name: Node | undefined, say: RustSay, role: TokenRole): PropertyRow { +function nameRow(name: Node | undefined, role: TokenRole): PropertyRow { return { label: '名称', value: name ? [sourceName(name, role)] : [hint('匿名')], @@ -296,22 +296,25 @@ export function payloadValue(body: Node | undefined, say: RustSay): Inline[] { * Rust 库规则认函数体里的一块时看它算出的值:语句看整句,变量看初始值,返回块看返回的值,交给 `card` 认成卡片。 * * 认领变量时变量名排在卡片第一行「变量」,卡片和这一行的定义范围都是它,同没认领时的变量块; - * `let … else` 的 else、实参里的闭包这些子块不并进卡片,照常是它的子块。 + * `let … else` 的 else、实参里的闭包这些子块不并进卡片,照常是它的子块。拆成子块的那几段讲在子块自己的卡片上, + * `card` 在值里找东西时跳过它们(`own` 为假的节点),同一句查询、路由不会外面那句、子块各出一张卡。 * * @param block 要认的那一块 * @param context 把变量名翻成一行用 - * @param card 从值里认出卡片,认不出是 `undefined` + * @param card 从值里认出卡片,认不出是 `undefined`;`own` 说一个节点是不是这一块自己的(不在哪个子块里) * @return 认出的卡片;别的种类的块、没写初始值的变量是 `undefined` */ -export function valueCard(block: CodeBlock, context: RuleContext, card: (value: Node) => RuleCard | undefined): RuleCard | undefined { +export function valueCard(block: CodeBlock, context: RuleContext, card: (value: Node, own: (node: Node) => boolean) => RuleCard | undefined): RuleCard | undefined { + // 子块从头部开头(else 子块从 else 这个词)到它的节点结尾;else 的节点是整条 let,不能拿节点的开头算 + const own = (node: Node): boolean => !block.children.some(child => node.startIndex >= child.header.startIndex && node.endIndex <= child.node.endIndex) if (block.kind === 'statement' || block.kind === 'return') - return card(block.node) + return card(block.node, own) if (block.kind !== 'variable') return undefined const [declarator, ...rest] = block.facts.declarators if (!declarator?.value || rest.length > 0) return undefined - const found = card(declarator.value) + const found = card(declarator.value, own) if (!found) return undefined const defines = range(declarator.name) diff --git a/vscode/src/codeReader/rules/sqlx.ts b/vscode/src/codeReader/rules/sqlx.ts index 80b8249..f5fedd3 100644 --- a/vscode/src/codeReader/rules/sqlx.ts +++ b/vscode/src/codeReader/rules/sqlx.ts @@ -25,8 +25,8 @@ export const sqlxRule: LibraryRule = { label: 'sqlx 查询', languageIds: ['rust'], libraries: ['sqlx'], - claim: (block, context) => valueCard(block, context, (value) => { - const query = findQuery(value) + claim: (block, context) => valueCard(block, context, (value, own) => { + const query = findQuery(value, own) return query && card(query, context) }), attributeNames: () => ({ attributes: {}, values: { kind: { 'sqlx.query': '查询' } } }), @@ -51,13 +51,15 @@ function sqlText(sql: Node | undefined, context: RuleContext): Inline[] { return [{ text: text ?? '', role: 'string', ...(sql && { offset: sql.startIndex }) }] } -/** 外层先认,找到的第一个查询。 */ -function findQuery(node: Node): Query | undefined { +/** 外层先认,找到的第一个查询;拆成子块的那几段(`own` 为假)不看。 */ +function findQuery(node: Node, own: (node: Node) => boolean): Query | undefined { + if (!own(node)) + return undefined const query = macroQuery(node) ?? callQuery(node) if (query) return query for (const child of named(node)) { - const found = findQuery(child) + const found = findQuery(child, own) if (found) return found }