✨ 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

+119 -3
View File
@@ -1,8 +1,8 @@
import type { Access, BlockShape, ClassFacts, CodeBlock, EntryFacts, FunctionFacts, ImportFacts, ImportItem, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
import type { CommentRules } from './shared'
import type { CommentRules, ExceptionRules } from './shared'
import * as path from 'node:path'
import { pythonNameReader } from './pythonNames'
import { children, compoundShape, leafBlock, leafShape, listBlocks, named, sameNode, syntaxChildren } from './shared'
import { children, compoundShape, leafBlock, leafShape, listBlocks, named, sameNode, syntaxChildren, withExceptions } from './shared'
/** 常量、类字段只认直接写在模块 / 类主体里的赋值;这两处的声明也才有访问级别。 */
type Scope = 'module' | 'class' | 'other'
@@ -398,6 +398,122 @@ function tryBlocks(node: Node): CodeBlock[] {
return blocks
}
// ─── 异常类 ───────────────────────────────────────────────────────────────────
/** Python 自带的异常类(builtins 里的,含各种 Warning)。 */
const BUILTIN_EXCEPTIONS = new Set([
'BaseException',
'BaseExceptionGroup',
'GeneratorExit',
'KeyboardInterrupt',
'SystemExit',
'Exception',
'ArithmeticError',
'FloatingPointError',
'OverflowError',
'ZeroDivisionError',
'AssertionError',
'AttributeError',
'BufferError',
'EOFError',
'ExceptionGroup',
'ImportError',
'ModuleNotFoundError',
'LookupError',
'IndexError',
'KeyError',
'MemoryError',
'NameError',
'UnboundLocalError',
'OSError',
'EnvironmentError',
'IOError',
'WindowsError',
'BlockingIOError',
'ChildProcessError',
'ConnectionError',
'BrokenPipeError',
'ConnectionAbortedError',
'ConnectionRefusedError',
'ConnectionResetError',
'FileExistsError',
'FileNotFoundError',
'InterruptedError',
'IsADirectoryError',
'NotADirectoryError',
'PermissionError',
'ProcessLookupError',
'TimeoutError',
'ReferenceError',
'RuntimeError',
'NotImplementedError',
'PythonFinalizationError',
'RecursionError',
'StopAsyncIteration',
'StopIteration',
'SyntaxError',
'IndentationError',
'TabError',
'SystemError',
'TypeError',
'ValueError',
'UnicodeError',
'UnicodeDecodeError',
'UnicodeEncodeError',
'UnicodeTranslateError',
'Warning',
'BytesWarning',
'DeprecationWarning',
'EncodingWarning',
'FutureWarning',
'ImportWarning',
'PendingDeprecationWarning',
'ResourceWarning',
'RuntimeWarning',
'SyntaxWarning',
'UnicodeWarning',
'UserWarning',
])
/** PEP 8 的命名习惯:异常类名以 Error 结尾(别的文件里的 `AppException`、`*Warning` 也常见)。 */
const EXCEPTION_NAME = /(?:Error|Exception|Warning)$/
/** 父类写的类名:`X`、`pkg.X` 取 X,泛型 `Base[T]` 取 Base 的;关键字参数、`*bases`、调用没有。 */
function className(node: Node | null): string | undefined {
switch (node?.type) {
case 'identifier':
return node.text
case 'attribute':
return node.childForFieldName('attribute')?.text
case 'subscript':
return className(node.childForFieldName('value'))
default:
return undefined
}
}
/**
* 类属性:类主体里给一个名字赋值的语句(`x = 1`、`x: int = 1`、只写注解的 `x: int`);连着赋值(`a = b = 1`)、解构赋值不算。
* 构造函数是 `__init__`,参数去掉第一个(self;`*args` 这类不是 self 的不去)。
*/
const PYTHON_EXCEPTIONS: ExceptionRules = {
baseName: base => className(base.value),
isExceptionName: name => BUILTIN_EXCEPTIONS.has(name) || EXCEPTION_NAME.test(name),
field: (block) => {
const assignment = block.kind === 'field' || block.kind === 'variable' ? assignmentIn(block.node) : undefined
const name = assignment?.childForFieldName('left')
const value = assignment?.childForFieldName('right')
if (!assignment || name?.type !== 'identifier' || value?.type === 'assignment')
return undefined
return { name, type: assignment.childForFieldName('type') ?? undefined, value: value ?? undefined }
},
constructorParameters: (block) => {
if (block.kind !== 'function' || block.facts.name?.text !== '__init__')
return undefined
const [receiver, ...rest] = block.facts.parameters
return receiver?.name.type === 'identifier' ? rest : block.facts.parameters
},
}
// ─── 源码着色 ─────────────────────────────────────────────────────────────────
const STORAGE_KEYWORDS = new Set(['def', 'class', 'async', 'lambda', 'global', 'nonlocal', 'del'])
@@ -484,7 +600,7 @@ export function createPythonAdapter(wasmDir: string): LanguageAdapter {
id: 'python',
languageIds: ['python'],
grammarPath: () => path.join(wasmDir, 'tree-sitter-python.wasm'),
toBlocks: root => statements(named(root), 'module', -1),
toBlocks: root => withExceptions(statements(named(root), 'module', -1), PYTHON_EXCEPTIONS),
tokenRole: pythonTokenRole,
iteration: pythonIteration,
displayNames: pythonNameReader,