✨ feat(vscode): 代码阅读的解析与模板直译核心

This commit is contained in:
cruldra committed 2026-09-24 21:16:21 +08:00
1 parent af56985d9a
commit 4d34b8c715
24 files changed
+2294

No files matched your search

+217
View File
@@ -0,0 +1,217 @@
import type { BlockKind, CodeBlock, LanguageAdapter, Node, TokenRole } from '../contract'
import * as path from 'node:path'
import { compoundBlock, leafBlock, listBlocks, named, sameNode } from './shared'
/** 常量、类字段只认直接写在模块 / 类主体里的赋值。 */
type Scope = 'module' | 'class' | 'other'
const CONSTANT_NAME = /^[A-Z][A-Z0-9_]*$/
function statements(items: Node[], scope: Scope, afterRow: number, docstring: boolean): CodeBlock[] {
return listBlocks(items, afterRow, (item, first) => statementBlocks(item, scope, docstring && first), () => true)
}
/** 冒号后面的主体:body / consequence 字段,except / finally 子句里是不带字段名的 block。 */
function bodyOf(node: Node): Node | null {
const body = node.childForFieldName('body') ?? node.childForFieldName('consequence')
if (body)
return body
const blocks = named(node).filter(child => child.type === 'block')
return blocks.at(-1) ?? null
}
function bodyBlock(kind: BlockKind, outer: Node, body: Node | null, scope: Scope, docstring = false): CodeBlock {
if (!body)
return leafBlock(kind, outer)
return compoundBlock(kind, outer, body.startIndex, named(body), (list, afterRow) => statements(list, scope, afterRow, docstring))
}
function statementBlocks(node: Node, scope: Scope, docstring: boolean): CodeBlock[] {
switch (node.type) {
case 'import_statement':
case 'import_from_statement':
case 'future_import_statement':
return [leafBlock('import', node)]
case 'function_definition':
case 'class_definition':
return [definitionBlock(node, node)]
case 'decorated_definition': {
const definition = node.childForFieldName('definition')
return [definition ? definitionBlock(definition, node) : leafBlock('statement', node)]
}
case 'if_statement':
return ifBlocks(node)
case 'for_statement':
case 'while_statement':
return loopBlocks(node)
case 'try_statement':
return tryBlocks(node)
case 'with_statement':
return [bodyBlock('statement', node, bodyOf(node), 'other')]
case 'match_statement':
return [bodyBlock('if', node, bodyOf(node), 'other')]
case 'case_clause':
return [bodyBlock('if', node, bodyOf(node), 'other')]
case 'return_statement':
return [leafBlock('return', node)]
case 'expression_statement':
return [leafBlock(expressionKind(node, scope, docstring), node)]
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 isConstantTarget(target: Node | null): boolean {
if (!target)
return false
if (target.type === 'identifier')
return CONSTANT_NAME.test(target.text)
if (target.type === 'pattern_list' || target.type === 'tuple_pattern') {
const names = named(target)
return names.length > 0 && names.every(isConstantTarget)
}
return false
}
function expressionKind(node: Node, scope: Scope, docstring: boolean): BlockKind {
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'
}
/** elif / else 展平成紧跟 if 的兄弟块。 */
function ifBlocks(node: Node): CodeBlock[] {
const blocks = [bodyBlock('if', node, bodyOf(node), 'other')]
for (const alternative of node.childrenForFieldName('alternative')) {
if (alternative)
blocks.push(bodyBlock('else', alternative, bodyOf(alternative), 'other'))
}
return blocks
}
/** 循环的 else 是兄弟块 else。 */
function loopBlocks(node: Node): CodeBlock[] {
const blocks = [bodyBlock('loop', node, bodyOf(node), 'other')]
const alternative = node.childForFieldName('alternative')
if (alternative)
blocks.push(bodyBlock('else', alternative, bodyOf(alternative), 'other'))
return blocks
}
const TRY_CLAUSES: Record<string, BlockKind> = {
except_clause: 'catch',
except_group_clause: 'catch',
else_clause: 'else',
finally_clause: 'finally',
}
function tryBlocks(node: Node): CodeBlock[] {
const blocks = [bodyBlock('try', node, node.childForFieldName('body'), 'other')]
for (const clause of named(node)) {
const kind = TRY_CLAUSES[clause.type]
if (kind)
blocks.push(bodyBlock(kind, clause, bodyOf(clause), 'other'))
}
return blocks
}
// ─── 源码着色 ─────────────────────────────────────────────────────────────────
const STORAGE_KEYWORDS = new Set(['def', 'class', 'async', 'lambda', 'global', 'nonlocal', 'del'])
const OPERATOR_KEYWORDS = new Set(['and', 'or', 'not', 'is'])
const COMPARISON = new Set(['comparison_operator', 'not in', 'is not'])
const LITERAL_KEYWORDS = new Set(['none', 'true', 'false'])
function insideString(leaf: Node): boolean {
for (let node = leaf.parent; node; node = node.parent) {
if (node.type === 'interpolation')
return false
if (node.type === 'string')
return true
}
return false
}
/** 注解里的名字:typed_parameter / 返回值 / 带注解赋值的 type 节点之内。 */
export function insideAnnotation(leaf: Node): boolean {
for (let node = leaf.parent; node; node = node.parent) {
if (node.type === 'type')
return true
if (node.type === 'block' || node.type === 'module')
return false
}
return false
}
function isCallee(node: Node): boolean {
const parent = node.parent
return !!parent && parent.type === 'call' && sameNode(parent.childForFieldName('function'), node)
}
function identifierRole(leaf: Node): TokenRole {
const parent = leaf.parent
if (!parent)
return 'variable'
if (insideAnnotation(leaf))
return 'type'
if (parent.type === 'function_definition' && sameNode(parent.childForFieldName('name'), leaf))
return 'function'
if (parent.type === 'class_definition' && sameNode(parent.childForFieldName('name'), leaf))
return 'type'
if (parent.type === 'decorator' || isCallee(leaf))
return 'function'
if (parent.type === 'attribute' && sameNode(parent.childForFieldName('attribute'), leaf) && isCallee(parent))
return 'function'
return 'variable'
}
export function pythonTokenRole(leaf: Node): TokenRole {
if (leaf.type === 'comment')
return 'comment'
if (insideString(leaf))
return 'string'
if (!leaf.isNamed) {
if (!/^[a-z]+$/.test(leaf.type))
return 'plain'
if (STORAGE_KEYWORDS.has(leaf.type))
return 'storage'
if (OPERATOR_KEYWORDS.has(leaf.type) || (leaf.type === 'in' && COMPARISON.has(leaf.parent?.type ?? '')))
return 'operator'
return 'keyword'
}
if (LITERAL_KEYWORDS.has(leaf.type))
return 'storage'
switch (leaf.type) {
case 'integer':
case 'float':
return 'number'
case 'identifier':
return identifierRole(leaf)
default:
return 'plain'
}
}
/** Python 语言插件。@param wasmDir 语法包 .wasm 所在目录 */
export function createPythonAdapter(wasmDir: string): LanguageAdapter {
return {
id: 'python',
languageIds: ['python'],
grammarPath: () => path.join(wasmDir, 'tree-sitter-python.wasm'),
toBlocks: root => statements(named(root), 'module', -1, true),
tokenRole: pythonTokenRole,
}
}