✨ feat(vscode): 代码阅读函数、导入、常量、变量块预览改成属性表

This commit is contained in:
cruldra committed 2026-09-25 04:56:22 +08:00
1 parent 0c4c9b0fba
commit dd9b39ac5d
19 files changed
+1458 -213

No files matched your search

+127 -23
View File
@@ -1,11 +1,13 @@
import type { BlockKind, CodeBlock, LanguageAdapter, Node, TokenRole } from '../contract'
import type { Access, BlockShape, CodeBlock, FunctionFacts, ImportFacts, ImportItem, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
import * as path from 'node:path'
import { compoundBlock, leafBlock, listBlocks, named, sameNode } from './shared'
import { children, compoundShape, leafBlock, leafShape, listBlocks, named, sameNode, syntaxChildren } from './shared'
/** 常量、类字段只认直接写在模块 / 类主体里的赋值。 */
/** 常量、类字段只认直接写在模块 / 类主体里的赋值;这两处的声明也才有访问级别。 */
type Scope = 'module' | 'class' | 'other'
const CONSTANT_NAME = /^[A-Z][A-Z0-9_]*$/
const PARAMETER_SEPARATORS = new Set(['keyword_separator', 'positional_separator'])
const PATTERNS = new Set(['pattern_list', 'tuple_pattern', 'list_pattern'])
function statements(items: Node[], scope: Scope, afterRow: number, docstring: boolean): CodeBlock[] {
return listBlocks(items, afterRow, (item, first) => statementBlocks(item, scope, docstring && first), () => true)
@@ -20,10 +22,14 @@ function bodyOf(node: Node): Node | null {
return blocks.at(-1) ?? null
}
function bodyBlock(kind: BlockKind, outer: Node, body: Node | null, scope: Scope, docstring = false): CodeBlock {
function bodyShape(outer: Node, body: Node | null, scope: Scope, docstring = false): BlockShape {
if (!body)
return leafBlock(kind, outer)
return compoundBlock(kind, outer, body.startIndex, named(body), (list, afterRow) => statements(list, scope, afterRow, docstring))
return leafShape(outer)
return compoundShape(outer, body.startIndex, named(body), (list, afterRow) => statements(list, scope, afterRow, docstring))
}
function bodyBlock(kind: TextKind, outer: Node, body: Node | null, scope: Scope): CodeBlock {
return { kind, ...bodyShape(outer, body, scope) }
}
function statementBlocks(node: Node, scope: Scope, docstring: boolean): CodeBlock[] {
@@ -31,13 +37,13 @@ function statementBlocks(node: Node, scope: Scope, docstring: boolean): CodeBloc
case 'import_statement':
case 'import_from_statement':
case 'future_import_statement':
return [leafBlock('import', node)]
return [{ kind: 'import', ...leafShape(node), facts: importFacts(node) }]
case 'function_definition':
case 'class_definition':
return [definitionBlock(node, node)]
return [definitionBlock(node, node, scope)]
case 'decorated_definition': {
const definition = node.childForFieldName('definition')
return [definition ? definitionBlock(definition, node) : leafBlock('statement', node)]
return [definition ? definitionBlock(definition, node, scope) : leafBlock('statement', node)]
}
case 'if_statement':
return ifBlocks(node)
@@ -55,16 +61,106 @@ function statementBlocks(node: Node, scope: Scope, docstring: boolean): CodeBloc
case 'return_statement':
return [leafBlock('return', node)]
case 'expression_statement':
return [leafBlock(expressionKind(node, scope, docstring), node)]
return [expressionBlock(node, scope, docstring)]
default:
return [leafBlock('statement', node)]
}
}
/** function_definition / class_definition;outer 是带装饰器的整体时头部从装饰器开始。 */
function definitionBlock(definition: Node, outer: Node): CodeBlock {
const isClass = definition.type === 'class_definition'
return bodyBlock(isClass ? 'class' : 'function', outer, definition.childForFieldName('body'), isClass ? 'class' : 'other', true)
function definitionBlock(definition: Node, outer: Node, scope: Scope): CodeBlock {
if (definition.type === 'class_definition')
return { kind: 'class', ...bodyShape(outer, definition.childForFieldName('body'), 'class', true) }
return {
kind: 'function',
...bodyShape(outer, definition.childForFieldName('body'), 'other', true),
facts: functionFacts(definition, outer, scope),
}
}
/** 单下划线开头的名字是私有(`_x`、`__x`),前后双下划线的特殊方法(`__init__`)是公共。 */
function nameAccess(name: string): Access {
const dunder = name.length > 4 && name.startsWith('__') && name.endsWith('__')
return name.startsWith('_') && !dunder ? 'private' : 'public'
}
/** 赋值目标里的名字;目标是属性、下标(`self.x = …`、`d[k] = …`)时没有。 */
function targetNames(target: Node): Node[] | undefined {
if (target.type === 'identifier')
return [target]
if (!PATTERNS.has(target.type) && target.type !== 'list_splat_pattern')
return undefined
const names: Node[] = []
for (const part of syntaxChildren(target)) {
const found = targetNames(part)
if (!found)
return undefined
names.push(...found)
}
return names
}
/** 模块、类主体里的声明才有访问级别;解构赋值里的名字全是私有才算私有。 */
function scopeAccess(target: Node, scope: Scope): Access | undefined {
const names = scope === 'other' ? undefined : targetNames(target)
if (!names?.length)
return undefined
return names.every(name => nameAccess(name.text) === 'private') ? 'private' : 'public'
}
function functionFacts(definition: Node, outer: Node, scope: Scope): FunctionFacts {
const name = definition.childForFieldName('name')
return {
name: name ?? undefined,
access: name ? scopeAccess(name, scope) : undefined,
async: children(definition).some(child => child.type === 'async'),
decorators: named(outer).filter(child => child.type === 'decorator'),
parameters: syntaxChildren(definition.childForFieldName('parameters'))
.filter(param => !PARAMETER_SEPARATORS.has(param.type))
.map(parameterFacts),
returnType: definition.childForFieldName('return_type') ?? undefined,
}
}
/** 带注解的参数(typed_parameter)语法没给名字起字段名,名字是第一个具名子节点(可能是 `*args`)。 */
function parameterFacts(param: Node): ParameterFacts {
switch (param.type) {
case 'typed_parameter':
return { name: syntaxChildren(param)[0] ?? param, optional: false, type: param.childForFieldName('type') ?? undefined }
case 'default_parameter':
case 'typed_default_parameter':
return {
name: param.childForFieldName('name') ?? param,
optional: false,
type: param.childForFieldName('type') ?? undefined,
default: param.childForFieldName('value') ?? undefined,
}
default:
return { name: param, optional: false }
}
}
/**
* import a.b as c, d:每个模块一个来源,导入项是整个模块;
* from m import a as b / from m import *:来源是 m(可以是相对的 `.models`),future 导入的来源是 `__future__` 这个词本身。
*/
function importFacts(node: Node): ImportFacts {
const names = node.childrenForFieldName('name').filter((name): name is Node => name !== null)
const alias = (name: Node): Node | undefined => name.type === 'aliased_import' ? name.childForFieldName('alias') ?? undefined : undefined
const original = (name: Node): Node => name.type === 'aliased_import' ? name.childForFieldName('name') ?? name : name
if (node.type === 'import_statement') {
return {
sources: names.map(original),
items: names.map(name => ({ kind: 'module', module: original(name), alias: alias(name) })),
}
}
const source = node.type === 'future_import_statement'
? children(node).find(child => child.type === '__future__')
: node.childForFieldName('module_name')
const items: ImportItem[] = names.map(name => ({ kind: 'named', name: original(name), alias: alias(name) }))
if (named(node).some(child => child.type === 'wildcard_import'))
items.push({ kind: 'namespace' })
return { sources: source ? [source] : [], items }
}
function isConstantTarget(target: Node | null): boolean {
@@ -79,17 +175,25 @@ function isConstantTarget(target: Node | null): boolean {
return false
}
function expressionKind(node: Node, scope: Scope, docstring: boolean): BlockKind {
/** 赋值 → 字段 / 常量 / 变量;主体开头的字符串是 docstring;其余表达式是语句。 */
function expressionBlock(node: Node, scope: Scope, docstring: boolean): CodeBlock {
const [expression, ...rest] = named(node)
if (docstring && rest.length === 0 && (expression?.type === 'string' || expression?.type === 'concatenated_string'))
return 'comment'
if (expression?.type !== 'assignment')
return 'statement'
if (scope === 'class' && expression.childForFieldName('type'))
return 'field'
if (scope === 'module' && isConstantTarget(expression.childForFieldName('left')))
return 'const'
return 'variable'
return leafBlock('comment', node)
const left = expression?.type === 'assignment' ? expression.childForFieldName('left') : null
if (!expression || !left)
return leafBlock('statement', node)
const type = expression.childForFieldName('type')
if (scope === 'class' && type)
return leafBlock('field', node)
return {
kind: scope === 'module' && isConstantTarget(left) ? 'const' : 'variable',
...leafShape(node),
facts: {
access: scopeAccess(left, scope),
declarators: [{ name: left, type: type ?? undefined, value: expression.childForFieldName('right') ?? undefined }],
},
}
}
/** elif / else 展平成紧跟 if 的兄弟块。 */
@@ -111,7 +215,7 @@ function loopBlocks(node: Node): CodeBlock[] {
return blocks
}
const TRY_CLAUSES: Record<string, BlockKind> = {
const TRY_CLAUSES: Record<string, TextKind> = {
except_clause: 'catch',
except_group_clause: 'catch',
else_clause: 'else',