✨ feat(vscode): 代码阅读加 Celery 库规则,定时任务表里的任务名能跳到任务函数

This commit is contained in:
cruldra committed 2026-09-26 19:41:07 +08:00
1 parent 905d7845b5
commit 68104e2cba
22 files changed
+2228 -139

No files matched your search

+5 -102
View File
@@ -1,4 +1,7 @@
import type { ClassBlock, CodeBlock, ImportFacts, Inline, LibraryRule, Node, PropertyItem, PropertyRow, RuleBlock, RuleContext, Segment, SourceRange, ThemeColor, TokenRole } from '../contract'
import type { ClassBlock, CodeBlock, Inline, LibraryRule, Node, PropertyItem, PropertyRow, RuleBlock, RuleContext, ThemeColor, TokenRole } from '../contract'
import type { Call } from './pythonTree'
import { aliasesOf, callOf, canonicalName, lastName, stringValue, syntaxChildren } from './pythonTree'
import { hint, joined, name, plain, range, word } from './segments'
/**
* SQLAlchemy 库规则:声明式模型类(有 `__tablename__`,或主体里用 `mapped_column(...)` / `Column(...)` 定义字段)整块换成「数据表」卡片:
@@ -116,13 +119,6 @@ interface Env {
aliases: Map<string, string>
}
/** 一个调用:函数名(最后一节,别名换回原名)、位置参数、关键字参数。 */
interface Call {
name: string | undefined
positional: Node[]
keywords: Map<string, Node>
}
/** 类主体里一条给名字赋值(或只写注解)的语句;doc 是它的属性 docstring(字段的 doc、变量的 facts.doc)。 */
interface Member {
block: CodeBlock
@@ -147,78 +143,10 @@ interface Model {
// ─── 读语法树 ─────────────────────────────────────────────────────────────────
/** 具名子节点,注释不算。 */
function syntaxChildren(node: Node | null | undefined): Node[] {
return node ? node.namedChildren.filter((child): child is Node => child !== null && !child.isExtra) : []
}
/** 名字的最后一节:`sa.Column` → Column。 */
function lastName(node: Node | null | undefined): string | undefined {
if (node?.type === 'identifier')
return node.text
if (node?.type === 'attribute')
return node.childForFieldName('attribute')?.text
return undefined
}
function isTrue(node: Node | undefined): boolean {
return node?.type === 'true'
}
/** 字符串字面量的内容;f-string、拼接的字符串不算。 */
function stringValue(node: Node | null | undefined): string | undefined {
if (node?.type !== 'string')
return undefined
const parts = syntaxChildren(node)
if (parts.some(part => part.type === 'interpolation'))
return undefined
return parts.filter(part => part.type === 'string_content').map(part => part.text).join('')
}
function fromLibrary(source: Node): boolean {
return LIBRARIES.some(library => source.text === library || source.text.startsWith(`${library}.`))
}
/** 从 SQLAlchemy 导入时起的别名 → 原名:`from sqlalchemy import Enum as SQLEnum`。 */
function aliasesOf(imports: ImportFacts[]): Map<string, string> {
const aliases = new Map<string, string>()
for (const facts of imports) {
if (!facts.sources.some(fromLibrary))
continue
for (const item of facts.items) {
if (item.kind === 'named' && item.alias)
aliases.set(item.alias.text, item.name.text)
}
}
return aliases
}
/** 类型、函数的原名:直接写的名字按别名表换回,`sa.Integer` 这种取最后一节。 */
function canonicalName(node: Node | null | undefined, aliases: Map<string, string>): string | undefined {
const name = lastName(node)
return node?.type === 'identifier' && name ? aliases.get(name) ?? name : name
}
function callOf(node: Node | null | undefined, aliases: Map<string, string>): Call | undefined {
if (node?.type !== 'call')
return undefined
const args = node.childForFieldName('arguments')
const positional: Node[] = []
const keywords = new Map<string, Node>()
for (const arg of args?.type === 'argument_list' ? syntaxChildren(args) : []) {
if (arg.type === 'keyword_argument') {
const key = arg.childForFieldName('name')
const value = arg.childForFieldName('value')
if (key && value)
keywords.set(key.text, value)
}
else if (arg.type !== 'list_splat' && arg.type !== 'dictionary_splat') {
positional.push(arg)
}
}
return { name: canonicalName(node.childForFieldName('function'), aliases), positional, keywords }
}
function memberOf(block: CodeBlock): Member | undefined {
if (block.kind !== 'field' && block.kind !== 'variable' && block.kind !== 'const')
return undefined
@@ -331,31 +259,6 @@ function dictValue(dictionary: Node | undefined, key: string): Node | undefined
// ─── 写成属性表 ───────────────────────────────────────────────────────────────
function range(node: Node): SourceRange {
return { startIndex: node.startIndex, endIndex: node.endIndex }
}
function plain(text: string): Segment {
return { text, role: 'plain' }
}
function hint(text: string): Segment {
return { text, role: 'hint' }
}
function word(text: string): Segment {
return { text, role: 'storage' }
}
/** 源码里的一个名字照抄,带源码下标(能跳到定义、高亮各处出现)。 */
function name(node: Node, role: TokenRole): Segment {
return { text: node.text, role, offset: node.startIndex }
}
function joined(parts: Inline[][], separator: Segment): Inline[] {
return parts.flatMap((part, i) => i > 0 ? [{ ...separator }, ...part] : part)
}
/** 字符串字面量写内容(不带引号)、按 role 着色;别的表达式交给翻译插件翻。 */
function textOr(node: Node, role: TokenRole, env: Env): Inline[] {
const text = stringValue(node)
@@ -558,7 +461,7 @@ export const sqlalchemyRule: LibraryRule = {
claim: (block, context) => {
if (block.kind !== 'class')
return undefined
const aliases = aliasesOf(context.imports)
const aliases = aliasesOf(context.imports, LIBRARIES)
const model = modelOf(block, aliases)
return model && tableBlock(block, model, { context, words: WORDS[context.displayLanguage ?? ''] ?? ZH, aliases })
},