✨ feat(vscode): 代码阅读支持库规则,内置 SQLAlchemy 模型类显示成数据表卡片

This commit is contained in:
cruldra committed 2026-09-26 18:29:40 +08:00
1 parent 32be7d54b6
commit 5ece24cef9
26 files changed
+1768 -81

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
import type { CodeBlock, ImportFacts, LibraryRule, Node, RuleBlock, RuleContext } from './contract'
/** 宿主这边怎么用库规则:什么时候启用、问哪个规则、认领了之后哪些子块并进去。规则本身只经 contract 拿信息。 */
/** 这份源码里各处导入的 facts,照源码顺序(先父块、再子块)。 */
export function importsIn(blocks: readonly CodeBlock[]): ImportFacts[] {
const found: ImportFacts[] = []
const visit = (list: readonly CodeBlock[]): void => {
for (const block of list) {
if (block.kind === 'import')
found.push(block.facts)
visit(block.children)
}
}
visit(blocks)
return found
}
/** 来源模块的写法去掉首尾引号(TypeScript 的来源是字符串字面量)。 */
function moduleName(source: Node): string {
return source.text.replace(/^(['"`])([\s\S]*)\1$/, '$2')
}
/** 模块是这个库或它的子模块:等于库名,或以「库名.」(Python 的点分模块名)「库名/」(JavaScript 的子路径)开头。 */
export function fromLibrary(module: string, library: string): boolean {
return module === library || module.startsWith(`${library}.`) || module.startsWith(`${library}/`)
}
/** 这份源码启用的规则,保持注册顺序:导入了规则关心的某个库。 */
export function activeRules(rules: readonly LibraryRule[], imports: readonly ImportFacts[]): LibraryRule[] {
const modules = imports.flatMap(facts => facts.sources.map(moduleName))
return rules.filter(rule => rule.libraries.some(library => modules.some(module => fromLibrary(module, library))))
}
/** 一块被哪个规则认领、替身是什么。 */
export interface Claim {
rule: LibraryRule
block: RuleBlock
}
/** 替身不合 RuleBlock 的约定时说哪儿不对。 */
function problemOf(rule: LibraryRule, block: CodeBlock, claimed: RuleBlock): string | undefined {
if (!claimed.kind.startsWith(`${rule.id}.`))
return `种类 id「${claimed.kind}」没以「${rule.id}.」开头`
if (claimed.absorbs.some(child => !block.children.includes(child)))
return '要并进来的块不是这块的子块'
return undefined
}
/**
* 按注册顺序问启用的规则,第一个认领的算。规则抛错、给的替身不合约定时交给 report(记日志),当它没认、接着问下一个;
* 都没认领就是 undefined,这块照常显示。
*/
export function claimBlock(block: CodeBlock, rules: readonly LibraryRule[], context: RuleContext, report: (message: string) => void): Claim | undefined {
const where = `第 ${block.header.startRow + 1} 行的 ${block.kind} 块`
for (const rule of rules) {
let claimed: RuleBlock | undefined
try {
claimed = rule.claim(block, context)
}
catch (err) {
report(`库规则 ${rule.id} 看${where}时出错,这块照常显示:${err instanceof Error ? err.message : String(err)}`)
continue
}
if (!claimed)
continue
const problem = problemOf(rule, block, claimed)
if (problem) {
report(`库规则 ${rule.id} 给${where}的替身不合约定,这块照常显示:${problem}`)
continue
}
return { rule, block: claimed }
}
return undefined
}
/**
* 替身并进了哪几行、留下哪些子块:编辑范围从原块头部开始,到最后一个并进来的子块(整条语句)结束;
* 起始行落在这段里的子块都并进来(包括夹在中间、规则没点名的),之后的留下。
*/
export function absorbedRange(block: CodeBlock, absorbs: readonly CodeBlock[]): { endRow: number, kept: CodeBlock[] } {
const endRow = Math.max(block.header.endRow, ...absorbs.map(child => Math.max(child.header.endRow, child.node.endPosition.row)))
return { endRow, kept: block.children.filter(child => child.header.startRow > endRow) }
}