✨ feat(vscode): 代码阅读函数、导入、常量、变量块预览改成属性表
This commit is contained in:
1 parent
0c4c9b0fba
commit
dd9b39ac5d
19 files changed
+1458
-213
No files matched your search
@@ -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',
|
||||
|
||||
Reference in new issue
Block a user