🐛 fix(vscode): 实参里的闭包、async 块写了查询或路由时不再外面那句、子块各出一张卡

valueCard 交给规则一个 own:拆成子块的那几段不归这一块,sqlx 找查询、axum 找路由时跳过;顺带去掉 rustRows 里 nameRow 没用的参数(页面那边的类型检查报它)
This commit is contained in:
cruldra committed 2026-10-15 21:06:58 +11:00
1 parent 1956879aa1
commit 6ad2a5b27a
4 files changed
+53 -20

No files matched your search

+7 -4
View File
@@ -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
}
@@ -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']],
])
})
})
+14 -11
View File
@@ -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)
+7 -5
View File
@@ -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
}