🐛 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) const route = attributeRoute(block)
return route ? card(block, route, context) : undefined return route ? card(block, route, context) : undefined
} }
return valueCard(block, context, (value) => { return valueCard(block, context, (value, own) => {
const [route] = routesOn(value).filter(item => !handled(value, item.handler)) const [route] = routesOn(value, own).filter(item => !handled(value, item.handler))
return route ? routerCard(value, route, context) : undefined 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') { if (node.type === 'call_expression') {
const callee = node.childForFieldName('function') const callee = node.childForFieldName('function')
const field = callee?.type === 'field_expression' ? callee.childForFieldName('field')?.text : undefined 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)) for (const child of named(node))
routesOn(child, found) routesOn(child, own, found)
return 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 docs = paramDocs(doc, fields.map(field => field.name.text))
const described = fields.some(field => docs.of(field.name.text)) const described = fields.some(field => docs.of(field.name.text))
return [ return [
nameRow(block.facts.name, say, 'type'), nameRow(block.facts.name, 'type'),
...docRow(doc), ...docRow(doc),
...unmatchedParamRows(docs.unmatched), ...unmatchedParamRows(docs.unmatched),
visibilityRow(block), visibilityRow(block),
@@ -73,7 +73,7 @@ export function structRows(block: ClassBlock, say: RustSay): PropertyRow[] {
export function moduleRows(block: ModuleBlock, say: RustSay): PropertyRow[] { export function moduleRows(block: ModuleBlock, say: RustSay): PropertyRow[] {
return [ return [
nameRow(block.facts.name, say, 'variable'), nameRow(block.facts.name, 'variable'),
...docRow(block.facts.doc && say.doc(block.facts.doc)), ...docRow(block.facts.doc && say.doc(block.facts.doc)),
visibilityRow(block), visibilityRow(block),
] ]
@@ -172,7 +172,7 @@ function cfgText(predicate: CfgPredicate, nested: boolean): Inline[] {
export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] { export function enumRows(block: EnumBlock, say: RustSay): PropertyRow[] {
return [ return [
nameRow(block.facts.name, say, 'type'), nameRow(block.facts.name, 'type'),
...docRow(block.facts.doc && say.doc(block.facts.doc)), ...docRow(block.facts.doc && say.doc(block.facts.doc)),
...tableRow('变体', ['变体', '携带的数据'], block.facts.members.map(member => ({ ...tableRow('变体', ['变体', '携带的数据'], block.facts.members.map(member => ({
cells: [[sourceName(member.name, 'type')], payloadValue(member.value, say)], 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 docs = paramDocs(doc, parameters.map(param => param.name.text))
const described = parameters.some(param => docs.of(param.name.text)) const described = parameters.some(param => docs.of(param.name.text))
return [ return [
nameRow(block.facts.name, say, 'function'), nameRow(block.facts.name, 'function'),
...docRow(doc), ...docRow(doc),
...unmatchedParamRows(docs.unmatched), ...unmatchedParamRows(docs.unmatched),
...(block.facts.async ? [{ label: '异步', value: [keyword('是')] }] : []), ...(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[] { export function traitRows(block: TraitBlock, say: RustSay): PropertyRow[] {
return [ return [
nameRow(block.facts.name, say, 'type'), nameRow(block.facts.name, 'type'),
...docRow(block.facts.doc && say.doc(block.facts.doc)), ...docRow(block.facts.doc && say.doc(block.facts.doc)),
...methodRows(block, say), ...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 { return {
label: '名称', label: '名称',
value: name ? [sourceName(name, role)] : [hint('匿名')], value: name ? [sourceName(name, role)] : [hint('匿名')],
@@ -296,22 +296,25 @@ export function payloadValue(body: Node | undefined, say: RustSay): Inline[] {
* Rust 库规则认函数体里的一块时看它算出的值:语句看整句,变量看初始值,返回块看返回的值,交给 `card` 认成卡片。 * Rust 库规则认函数体里的一块时看它算出的值:语句看整句,变量看初始值,返回块看返回的值,交给 `card` 认成卡片。
* *
* 认领变量时变量名排在卡片第一行「变量」,卡片和这一行的定义范围都是它,同没认领时的变量块; * 认领变量时变量名排在卡片第一行「变量」,卡片和这一行的定义范围都是它,同没认领时的变量块;
* `let … else` 的 else、实参里的闭包这些子块不并进卡片,照常是它的子块。 * `let … else` 的 else、实参里的闭包这些子块不并进卡片,照常是它的子块。拆成子块的那几段讲在子块自己的卡片上,
* `card` 在值里找东西时跳过它们(`own` 为假的节点),同一句查询、路由不会外面那句、子块各出一张卡。
* *
* @param block 要认的那一块 * @param block 要认的那一块
* @param context 把变量名翻成一行用 * @param context 把变量名翻成一行用
* @param card 从值里认出卡片,认不出是 `undefined` * @param card 从值里认出卡片,认不出是 `undefined`;`own` 说一个节点是不是这一块自己的(不在哪个子块里)
* @return 认出的卡片;别的种类的块、没写初始值的变量是 `undefined` * @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') if (block.kind === 'statement' || block.kind === 'return')
return card(block.node) return card(block.node, own)
if (block.kind !== 'variable') if (block.kind !== 'variable')
return undefined return undefined
const [declarator, ...rest] = block.facts.declarators const [declarator, ...rest] = block.facts.declarators
if (!declarator?.value || rest.length > 0) if (!declarator?.value || rest.length > 0)
return undefined return undefined
const found = card(declarator.value) const found = card(declarator.value, own)
if (!found) if (!found)
return undefined return undefined
const defines = range(declarator.name) const defines = range(declarator.name)
+7 -5
View File
@@ -25,8 +25,8 @@ export const sqlxRule: LibraryRule = {
label: 'sqlx 查询', label: 'sqlx 查询',
languageIds: ['rust'], languageIds: ['rust'],
libraries: ['sqlx'], libraries: ['sqlx'],
claim: (block, context) => valueCard(block, context, (value) => { claim: (block, context) => valueCard(block, context, (value, own) => {
const query = findQuery(value) const query = findQuery(value, own)
return query && card(query, context) return query && card(query, context)
}), }),
attributeNames: () => ({ attributes: {}, values: { kind: { 'sqlx.query': '查询' } } }), 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 }) }] return [{ text: text ?? '', role: 'string', ...(sql && { offset: sql.startIndex }) }]
} }
/** 外层先认,找到的第一个查询。 */ /** 外层先认,找到的第一个查询;拆成子块的那几段(`own` 为假)不看。 */
function findQuery(node: Node): Query | undefined { function findQuery(node: Node, own: (node: Node) => boolean): Query | undefined {
if (!own(node))
return undefined
const query = macroQuery(node) ?? callQuery(node) const query = macroQuery(node) ?? callQuery(node)
if (query) if (query)
return query return query
for (const child of named(node)) { for (const child of named(node)) {
const found = findQuery(child) const found = findQuery(child, own)
if (found) if (found)
return found return found
} }