✨ feat(vscode): 代码阅读加异常卡片,Python、TypeScript 的异常类画成同一张表(属性、构造参数),方法照常是子块
This commit is contained in:
1 parent
d5a092dd6f
commit
0afd012c94
21 files changed
+1118
-66
No files matched your search
@@ -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)
|
||||
}
|
||||
Reference in new issue
Block a user