✨ feat(vscode): 代码阅读加异常卡片,Python、TypeScript 的异常类画成同一张表(属性、构造参数),方法照常是子块

This commit is contained in:
cruldra committed 2026-09-27 10:42:10 +08:00
1 parent d5a092dd6f
commit 0afd012c94
21 files changed
+1118 -66

No files matched your search

+126 -1
View File
@@ -1,4 +1,4 @@
import type { BlockShape, CodeBlock, DocumentedBlock, Node, SourceSpan, TextKind } from '../contract'
import type { BaseFacts, BlockShape, ClassBlock, ClassFacts, CodeBlock, DocumentedBlock, ExceptionBlock, FieldFacts, Node, ParameterFacts, SourceSpan, TextKind } from '../contract'
/** 语言插件共用的建树工具:取子节点、算头部、把一串语句切成块。 */
@@ -164,3 +164,128 @@ export function listBlocks(
export function sameNode(a: Node | null | undefined, b: Node): boolean {
return !!a && a.startIndex === b.startIndex && a.endIndex === b.endIndex && a.type === b.type
}
// ─── 异常类 ───────────────────────────────────────────────────────────────────
/** 语言怎么认异常类、类主体里哪些子块是类属性、哪个是构造函数。 */
export interface ExceptionRules {
/** 父类一项写的类名(点分写法取最后一节);关键字参数、展开、调用这类不是类名的项没有。 */
baseName: (base: BaseFacts) => string | undefined
/** 光看名字就认得是异常类:语言自带的异常类,或按命名习惯以 Error 这类后缀结尾。 */
isExceptionName: (name: string) => boolean
/** 类主体里的子块是类属性时它的名字、类型、值;不是时没有。 */
field: (block: CodeBlock) => Omit<FieldFacts, 'docs'> | undefined
/** 子块是构造函数时它的参数(Python 去掉 self);不是时没有。 */
constructorParameters: (block: CodeBlock) => ParameterFacts[] | undefined
}
function classBlocks(blocks: readonly CodeBlock[]): ClassBlock[] {
return blocks.flatMap(block => [...(block.kind === 'class' ? [block] : []), ...classBlocks(block.children)])
}
/** 紧挨在 child 前面的注释块(连着几块都算,中间没隔别的子块),照源码顺序:讲的是它下面的这个 child。 */
function leadingComments(children: readonly CodeBlock[], child: CodeBlock): CodeBlock[] {
const at = children.indexOf(child)
let from = at
while (from > 0 && children[from - 1].kind === 'comment')
from--
return children.slice(from, at)
}
/** 注释块里的各条注释(几条行注释连成的一块是好几个节点);Python 当注释用的字符串是那个字符串。 */
function commentNodes(block: CodeBlock): Node[] {
if (block.node.type !== 'comment')
return syntaxChildren(block.node).slice(0, 1)
const nodes: Node[] = []
for (let node: Node | null = block.node; node?.type === 'comment' && node.endIndex <= block.header.endIndex; node = node.nextSibling)
nodes.push(node)
return nodes
}
/** 块自己的说明节点:属性表块的 facts.doc,一行译文块的 doc(Python 字段的属性 docstring)。 */
function ownDoc(block: CodeBlock): Node | undefined {
if (!('facts' in block))
return block.doc
return block.kind === 'import' ? undefined : block.facts.doc
}
/** 类属性的说明,照源码顺序:紧挨在它上面的注释块、它这一行行尾的注释、Python 紧跟在后面的属性 docstring。 */
function fieldDocs(children: readonly CodeBlock[], block: CodeBlock): Node[] {
const trailing = block.node.nextNamedSibling
const doc = ownDoc(block)
return [
...leadingComments(children, block).flatMap(commentNodes),
...(trailing?.type === 'comment' && trailing.startPosition.row === block.node.endPosition.row ? [trailing] : []),
...(doc ? [doc] : []),
]
}
/**
* 类块换成异常块:类属性连同紧挨在它前面的注释块并进属性表,构造函数(写了几个取最后一个,重载的实现写在最后)的参数进属性表,
* 留下的子块(方法、构造函数本身、别的语句)不管写在哪都照源码顺序留作子块;头部是整个类(到它连同各子块的最后一行),
* 「编辑」时整段一起改,webview 在它编辑时不画留下的子块。
*/
function exceptionBlock(block: ClassBlock, rules: ExceptionRules): ExceptionBlock {
const { children, facts } = block
const fields: FieldFacts[] = []
const members: CodeBlock[] = []
let parameters: ParameterFacts[] | undefined
for (const child of children) {
const field = rules.field(child)
if (field) {
fields.push({ ...field, docs: fieldDocs(children, child) })
members.push(child)
}
parameters = rules.constructorParameters(child) ?? parameters
}
const absorbed = new Set(members.flatMap(member => [...leadingComments(children, member), member]))
const ends = [block.header, nodeSpan(block.node), ...children.flatMap(child => [child.header, nodeSpan(child.node)])]
const last = ends.reduce((end, span) => span.endIndex > end.endIndex ? span : end)
return {
kind: 'exception',
node: block.node,
header: { ...block.header, endIndex: last.endIndex, endRow: last.endRow },
children: children.filter(child => !absorbed.has(child)),
...(block.attributes && { attributes: block.attributes }),
facts: {
name: facts.name,
doc: facts.doc,
access: facts.access,
decorators: facts.decorators,
bases: facts.bases,
fields,
...(parameters && { parameters }),
},
}
}
/**
* 认出异常类,把它们的类块换成异常块(见 exceptionBlock)。父类里有一项是异常类就是异常类:光看名字认得出(isExceptionName),
* 或是同一文件里(不论写在前面还是后面、嵌套在哪)认出来的异常类,一层层往下传。父类从别的文件导入、名字又不合命名习惯的认不出来。
*/
export function withExceptions(blocks: CodeBlock[], rules: ExceptionRules): CodeBlock[] {
const classes = classBlocks(blocks)
const local = new Set<string>()
const isException = (facts: ClassFacts): boolean => facts.bases.some((base) => {
const name = rules.baseName(base)
return name !== undefined && (rules.isExceptionName(name) || local.has(name))
})
let grew = true
while (grew) {
grew = false
for (const { facts } of classes) {
const name = facts.name?.text
if (name !== undefined && !local.has(name) && isException(facts)) {
local.add(name)
grew = true
}
}
}
const convert = (list: CodeBlock[]): CodeBlock[] => list.map((block) => {
const children = convert(block.children)
const same = children.every((child, i) => child === block.children[i])
const next: CodeBlock = same ? block : { ...block, children }
return next.kind === 'class' && isException(next.facts) ? exceptionBlock(next, rules) : next
})
return convert(blocks)
}