Files
superwork/vscode/src/codeReader/libraryRules.ts
T
cruldra 582ac4c987 ✨ feat(vscode): 代码阅读的注释挂到它讲的代码块上成「注释」一行,重写注释改用技能一句话加结构化结果
- 普通注释不再单独成卡片:同一层挂到后面最近的块(隔空行也算),后面没有挂到前面那块,整层没代码挂到父块;只有文件开头隔着空行的注释、模块 docstring、//!、@file 单独成「文件开头的说明」
- 各语言(Python、TypeScript、Rust、Shell、Dockerfile)建好树后统一调 attachComments;块的编辑范围连着挂上的注释,差异、跳转、钉住卡片都按它
- 宿主统一给每张卡片(语言卡片、翻译插件的属性表、库规则卡片、一行译文的语句卡片)插「注释」一行,排在名称、说明后面
- TypeScript 的 JSDoc 也能当常量、变量的说明;sqlx、React 不再单独处理前面的注释
- 「重写注释」每块都有:提示只发 /ecc:writing-code-comments 路径:起-止,模型只读、按 --json-schema 交 body,没交结构化正文就失败不写;扩展换掉块前面的注释或在块上面新写一段
2026-10-08 07:51:21 +08:00

145 lines
6.7 KiB
TypeScript

import type { AttributeNames, CodeBlock, ImportFacts, Inline, LibraryRule, Node, PropertyItem, PropertyRow, RuleBlock, RuleContext, RuleLocation, RuleReference, RuleWorkspace, Segment } 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}/`) || module.startsWith(`${library}::`)
}
/** 这份源码启用的规则,保持注册顺序:导入了规则关心的某个库,或规则不看导入(libraries 是空的)。 */
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.length === 0 || 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 ('omit' in claimed)
return block.children.length > 0 ? '要略去的块还有子块' : block.comments ? '要略去的块挂着注释' : undefined
if (claimed.absorbs.some(child => !block.children.includes(child)))
return '要并进来的块不是这块的子块'
if ('as' in claimed)
return undefined
if (!claimed.kind.startsWith(`${rule.id}.`))
return `种类 id「${claimed.kind}」没以「${rule.id}.」开头`
const slots = claimed.slots ?? []
if (slots.some(slot => !block.children.includes(slot.block) || claimed.absorbs.includes(slot.block)))
return '挂在子行上的块不是留下的子块'
if (new Set(slots.map(slot => slot.block)).size < slots.length)
return '同一块挂在了卡片里的几处'
for (const { row, item } of slots) {
if (!claimed.rows[row])
return `挂在卡片第 ${row} 行的块,卡片没有这一行`
if (!claimed.rows[row].items?.[item])
return `挂在卡片第 ${row} 行第 ${item} 个子行的块,这一行没有这个子行`
}
for (const { reference, offset } of referencedSegments(claimed.rows)) {
if (reference?.rule !== rule.id)
return `引用「${reference?.key}」标的规则是「${reference?.rule}」,不是自己`
if (!rule.resolve)
return `标了引用「${reference.key}」却没给 resolve`
if (offset === undefined)
return `引用「${reference.key}」没带源码下标`
}
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
}
/** 各规则给自己的筛选属性起的名字(LibraryRule.attributeNames),按注册顺序;规则抛错时交给 report(记日志),当它没给。 */
export function ruleAttributeNames(rules: readonly LibraryRule[], displayLanguage: string | undefined, report: (message: string) => void): AttributeNames[] {
return rules.flatMap((rule) => {
try {
return rule.attributeNames ? [rule.attributeNames(displayLanguage)] : []
}
catch (err) {
report(`库规则 ${rule.id} 给筛选属性起名时出错,筛选栏照写 id:${err instanceof Error ? err.message : String(err)}`)
return []
}
})
}
/** 文字里(连框里)带引用的段。 */
function referencesInText(pieces: readonly Inline[]): Segment[] {
return pieces.flatMap(piece => 'role' in piece ? (piece.reference ? [piece] : []) : referencesInText(piece.segments))
}
function referencesInItems(items: readonly PropertyItem[] | undefined): Segment[] {
return (items ?? []).flatMap(item => [...item.cells.flatMap(cell => 'markdown' in cell ? [] : referencesInText(cell)), ...referencesInItems(item.items)])
}
/** 属性表里(值、各层子行的格子)带引用的段。 */
export function referencedSegments(rows: readonly PropertyRow[]): Segment[] {
return rows.flatMap(row => [...('markdown' in row.value ? [] : referencesInText(row.value)), ...referencesInItems(row.items)])
}
/**
* 查规则引用的定义:交给标它的那个规则的 resolve。规则已经注销、没给 resolve、查的时候出错,都记日志、当作没有定义,
* 预览照常不动。
*/
export async function resolveReference(reference: RuleReference, rules: readonly LibraryRule[], workspace: RuleWorkspace, report: (message: string) => void): Promise<RuleLocation[]> {
const rule = rules.find(candidate => candidate.id === reference.rule)
if (!rule?.resolve) {
report(`没有能查引用「${reference.key}」的库规则 ${reference.rule}`)
return []
}
try {
return await rule.resolve(reference.key, workspace)
}
catch (err) {
report(`库规则 ${rule.id} 查「${reference.key}」的定义时出错:${err instanceof Error ? err.message : String(err)}`)
return []
}
}