⚡ perf(vscode): 语言插件按作用域规则认出名字的绑定,找别的文件起的中文名时同一个绑定只问一处、本文件定义的不问
This commit is contained in:
1 parent
12ad047b4d
commit
0070933599
14 files changed
+1159
-36
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import type { NameBinding, Node, SourceRange } from '../contract'
|
||||
|
||||
/** 语法树里的名字节点(Python 只有 identifier;TypeScript 的类名是 type_identifier,解构出来的变量是 shorthand_property_identifier_pattern)。 */
|
||||
const NAME_TYPES = new Set(['identifier', 'type_identifier', 'shorthand_property_identifier_pattern'])
|
||||
|
||||
/** 名字在一层作用域里是怎么绑定的:这份源码自己定义的(参数、赋值、def……),还是导入的。 */
|
||||
export type BindKind = 'local' | 'import'
|
||||
|
||||
/** 一层作用域:绑定了哪些名字(各是怎么绑定的),哪些名字拿不准(解析到这层的不报)。 */
|
||||
export class Scope {
|
||||
readonly binds = new Map<string, BindKind[]>()
|
||||
readonly unsure = new Set<string>()
|
||||
|
||||
constructor(readonly kind: string, readonly parent: Scope | undefined, readonly id: number) {}
|
||||
|
||||
bind(name: string, kind: BindKind): void {
|
||||
listOf(this.binds, name).push(kind)
|
||||
}
|
||||
}
|
||||
|
||||
/** 一处出现:名字在哪层作用域里出现、范围。 */
|
||||
interface Seen {
|
||||
scope: Scope
|
||||
name: string
|
||||
range: SourceRange
|
||||
}
|
||||
|
||||
/**
|
||||
* 认绑定的语言插件共用的:边走语法树边记下各层作用域、各处出现,走完按语言的规则(resolve)把每处出现落到一个绑定上,同一个绑定的归到一起。
|
||||
* resolve 交回绑定的键(同一个键是同一个绑定),拿不准的交回 undefined、不报。
|
||||
*/
|
||||
export class BindingCollector {
|
||||
private readonly seen: Seen[] = []
|
||||
private readonly scopes: Scope[] = []
|
||||
|
||||
scope(kind: string, parent: Scope | undefined): Scope {
|
||||
const scope = new Scope(kind, parent, this.scopes.length)
|
||||
this.scopes.push(scope)
|
||||
return scope
|
||||
}
|
||||
|
||||
/** 名字节点 node 出现在 scope 里(用到它,或者它的定义处)。 */
|
||||
see(scope: Scope, node: Node): void {
|
||||
this.seen.push({ scope, name: node.text, range: { startIndex: node.startIndex, endIndex: node.endIndex } })
|
||||
}
|
||||
|
||||
/** 拿不准 node 里的名字:scope 里这些名字都不报。 */
|
||||
unsure(scope: Scope, node: Node): void {
|
||||
if (NAME_TYPES.has(node.type))
|
||||
scope.unsure.add(node.text)
|
||||
for (const child of node.namedChildren) {
|
||||
if (child)
|
||||
this.unsure(scope, child)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 resolve 归到各个绑定:绑定只有本文件定义的是 local;只有一处导入的不是 local;哪儿都没绑定的(内置的)不是 local;
|
||||
* 既导入又本文件定义、导入了几次的拿不准,不报。
|
||||
*/
|
||||
bindings(resolve: (scope: Scope, name: string) => string | undefined): NameBinding[] {
|
||||
const kinds = new Map<string, BindKind[]>()
|
||||
for (const scope of this.scopes) {
|
||||
for (const [name, list] of scope.binds) {
|
||||
const key = resolve(scope, name)
|
||||
if (key !== undefined)
|
||||
listOf(kinds, key).push(...list)
|
||||
}
|
||||
}
|
||||
const occurrences = new Map<string, SourceRange[]>()
|
||||
for (const { scope, name, range } of this.seen) {
|
||||
const key = resolve(scope, name)
|
||||
if (key !== undefined)
|
||||
listOf(occurrences, key).push(range)
|
||||
}
|
||||
return [...occurrences].flatMap(([key, ranges]) => {
|
||||
const list = kinds.get(key) ?? []
|
||||
const imports = list.filter(kind => kind === 'import').length
|
||||
if (imports > 1 || (imports > 0 && imports < list.length))
|
||||
return []
|
||||
return [{ occurrences: ranges, local: list.length > 0 && imports === 0 }]
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function listOf<T>(map: Map<string, T[]>, key: string): T[] {
|
||||
let list = map.get(key)
|
||||
if (!list) {
|
||||
list = []
|
||||
map.set(key, list)
|
||||
}
|
||||
return list
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Access, BlockShape, CallArgument, CallFacts, ClassFacts, CodeBlock, CollectionFacts, CommentBlock, ElementFacts, EntryFacts, FunctionFacts, ImportFacts, ImportItem, IterationClause, IterationFacts, IterationResult, LanguageAdapter, Node, ParameterFacts, TextKind, TokenRole } from '../contract'
|
||||
import type { CommentRules, ExceptionRules } from './shared'
|
||||
import * as path from 'node:path'
|
||||
import { pythonBindings } from './pythonBindings'
|
||||
import { pythonNameReader } from './pythonNames'
|
||||
import { attachComments, children, compoundShape, insideOf, leafBlock, leafShape, listBlocks, named, sameNode, syntaxChildren, withExceptions } from './shared'
|
||||
|
||||
@@ -670,6 +671,7 @@ export function createPythonAdapter(wasmDir: string): LanguageAdapter {
|
||||
iteration: pythonIteration,
|
||||
call: pythonCall,
|
||||
collection: pythonCollection,
|
||||
bindings: root => pythonBindings(root),
|
||||
displayNames: pythonNameReader,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import type { NameBinding } from '../contract'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { runtime, WASM_DIR } from '../testSupport'
|
||||
import { createPythonAdapter } from './python'
|
||||
|
||||
const py = createPythonAdapter(WASM_DIR)
|
||||
|
||||
/** 读一份源码的绑定,按名字第几次出现(0 起,按整词数)问它落在哪个绑定上。 */
|
||||
async function bindingsOf(source: string): Promise<{ of: (name: string, n: number) => NameBinding | undefined, same: (name: string, ...ns: number[]) => boolean }> {
|
||||
const tree = await runtime.parse(py, 'python', source)
|
||||
let list: NameBinding[]
|
||||
try {
|
||||
list = py.bindings!(tree.rootNode, source, 'python')
|
||||
}
|
||||
finally {
|
||||
tree.delete()
|
||||
}
|
||||
const at = (name: string, n: number): number => [...source.matchAll(new RegExp(`\\b${name}\\b`, 'g'))][n].index!
|
||||
const of = (name: string, n: number): NameBinding | undefined => list.find(binding => binding.occurrences.some(range => range.startIndex === at(name, n) && range.endIndex === at(name, n) + name.length))
|
||||
return { of, same: (name, ...ns) => ns.every(n => of(name, n) !== undefined && of(name, n) === of(name, ns[0])) }
|
||||
}
|
||||
|
||||
describe('python 的名字绑定', () => {
|
||||
it('同一个函数里参数的各处是一个绑定、本文件定义;内层函数同名参数是另一个;内层没重新绑定的用的是外层的;没定义过的不是本文件定义', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'def outer(session, other):',
|
||||
' use(session)',
|
||||
' def inner(session):',
|
||||
' return session',
|
||||
' def closure():',
|
||||
' return other',
|
||||
' return session',
|
||||
].join('\n'))
|
||||
expect(same('session', 0, 1, 4)).toBe(true)
|
||||
expect(same('session', 2, 3)).toBe(true)
|
||||
expect(of('session', 2)).not.toBe(of('session', 0))
|
||||
expect(of('session', 0)?.local).toBe(true)
|
||||
expect(same('other', 0, 1)).toBe(true)
|
||||
expect(of('use', 0)?.local).toBe(false)
|
||||
})
|
||||
|
||||
it('推导式变量是推导式自己的,第一个 for 的可迭代对象在外层、后面的在推导式里', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'def f(items, x):',
|
||||
' a = [x for x in items if x]',
|
||||
' b = {k: v for k, v in x.items()}',
|
||||
' c = [y for xs in items for y in xs]',
|
||||
' return x',
|
||||
].join('\n'))
|
||||
expect(same('x', 1, 2, 3)).toBe(true)
|
||||
expect(same('x', 0, 4, 5)).toBe(true)
|
||||
expect(of('x', 1)).not.toBe(of('x', 0))
|
||||
expect(same('items', 0, 1, 3)).toBe(true)
|
||||
// x.items 的属性名不是名字的绑定
|
||||
expect(of('items', 2)).toBeUndefined()
|
||||
expect(same('xs', 0, 1)).toBe(true)
|
||||
expect(same('k', 0, 1)).toBe(true)
|
||||
})
|
||||
|
||||
it('global、nonlocal 照声明找到外层那个;没声明的同名赋值是函数自己的', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'counter = 0',
|
||||
'def bump():',
|
||||
' global counter',
|
||||
' counter += 1',
|
||||
'def make():',
|
||||
' total = 0',
|
||||
' def add(n):',
|
||||
' nonlocal total',
|
||||
' total += n',
|
||||
' return total',
|
||||
'def shadow():',
|
||||
' counter = 5',
|
||||
' return counter',
|
||||
].join('\n'))
|
||||
expect(same('counter', 0, 1, 2)).toBe(true)
|
||||
expect(same('counter', 3, 4)).toBe(true)
|
||||
expect(of('counter', 3)).not.toBe(of('counter', 0))
|
||||
expect(same('total', 0, 1, 2, 3)).toBe(true)
|
||||
})
|
||||
|
||||
it('类主体里绑定的名字:类主体里直接用的、推导式第一个可迭代对象是类的,方法里、推导式里面的是模块的', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'limit = 10',
|
||||
'class Config:',
|
||||
' limit = 5',
|
||||
' doubled = limit * 2',
|
||||
' values = [limit for _ in range(limit)]',
|
||||
' def get(self):',
|
||||
' return limit',
|
||||
].join('\n'))
|
||||
expect(same('limit', 1, 2, 4)).toBe(true)
|
||||
expect(same('limit', 0, 3, 5)).toBe(true)
|
||||
expect(of('limit', 1)).not.toBe(of('limit', 0))
|
||||
})
|
||||
|
||||
it('lambda 的参数是它自己的,默认值在外层;海象运算符在推导式里绑定的是外面那个函数的;with、except 的 as 是本文件定义的', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'def f(x, data, path):',
|
||||
' g = lambda x, y=x: x + y',
|
||||
' if any((hit := d) for d in data):',
|
||||
' return hit',
|
||||
' with open(path) as fh:',
|
||||
' fh.read()',
|
||||
' try:',
|
||||
' pass',
|
||||
' except ValueError as err:',
|
||||
' log(err)',
|
||||
].join('\n'))
|
||||
expect(same('x', 0, 2)).toBe(true)
|
||||
expect(same('x', 1, 3)).toBe(true)
|
||||
expect(of('x', 1)).not.toBe(of('x', 0))
|
||||
expect(same('hit', 0, 1)).toBe(true)
|
||||
expect(same('fh', 0, 1)).toBe(true)
|
||||
expect(same('err', 0, 1)).toBe(true)
|
||||
expect(of('err', 0)?.local).toBe(true)
|
||||
expect(of('ValueError', 0)?.local).toBe(false)
|
||||
})
|
||||
|
||||
it('导入的名字不是本文件定义的;import a.b 绑定 a,as 前面的原名、模块路径不报;同一个名字导入两次、又导入又赋值的拿不准不报', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'import os.path',
|
||||
'from app.models import User, Role as R',
|
||||
'from x import dup',
|
||||
'from y import dup',
|
||||
'from z import mixed',
|
||||
'mixed = wrap(mixed)',
|
||||
'def f():',
|
||||
' return os, User, R, dup, mixed, path',
|
||||
].join('\n'))
|
||||
expect(same('os', 0, 1)).toBe(true)
|
||||
expect(of('os', 0)?.local).toBe(false)
|
||||
expect(same('User', 0, 1)).toBe(true)
|
||||
expect(same('R', 0, 1)).toBe(true)
|
||||
expect(of('Role', 0)).toBeUndefined()
|
||||
expect(of('models', 0)).toBeUndefined()
|
||||
expect(of('path', 0)).toBeUndefined()
|
||||
expect(of('dup', 2)).toBeUndefined()
|
||||
expect(of('mixed', 3)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('拿不准的不报:模式匹配捕获的名字、类型参数;有 from m import * 时模块级的名字', async () => {
|
||||
const matched = await bindingsOf([
|
||||
'def f(value):',
|
||||
' match value:',
|
||||
' case Point(x=px):',
|
||||
' return px',
|
||||
' return value',
|
||||
'def g[T](item: T) -> T:',
|
||||
' return item',
|
||||
].join('\n'))
|
||||
expect(matched.of('px', 0)).toBeUndefined()
|
||||
expect(matched.of('px', 1)).toBeUndefined()
|
||||
expect(matched.same('value', 0, 1, 2)).toBe(true)
|
||||
expect(matched.of('T', 1)).toBeUndefined()
|
||||
expect(matched.same('item', 0, 1)).toBe(true)
|
||||
const starred = await bindingsOf('from m import *\nx = 1\ndef f():\n y = 2\n return x, y, print\n')
|
||||
expect(starred.of('x', 0)).toBeUndefined()
|
||||
expect(starred.of('print', 0)).toBeUndefined()
|
||||
expect(starred.same('y', 0, 1)).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,327 @@
|
||||
import type { NameBinding, Node } from '../contract'
|
||||
import type { BindKind, Scope } from './bindings'
|
||||
import { BindingCollector } from './bindings'
|
||||
import { named } from './shared'
|
||||
|
||||
/** 推导式、生成器表达式:自成一层作用域,第一个 for 的可迭代对象在外层求值。 */
|
||||
const COMPREHENSIONS = new Set(['list_comprehension', 'set_comprehension', 'dictionary_comprehension', 'generator_expression'])
|
||||
|
||||
/** 赋值目标里一层层往里拆的写法。 */
|
||||
const TARGET_LISTS = new Set(['pattern_list', 'tuple_pattern', 'list_pattern', 'tuple', 'list', 'expression_list', 'parenthesized_expression', 'list_splat_pattern', 'list_splat', 'as_pattern_target'])
|
||||
|
||||
/**
|
||||
* Python 的作用域规则:模块、函数(def、lambda)、类、推导式各是一层。函数里赋过值、当参数的名字是函数自己的,
|
||||
* 用到的名字从里往外找(往外找时跳过类主体),global / nonlocal 声明的照声明找。参数的注解和默认值、装饰器、父类在外层求值。
|
||||
* 模式匹配捕获的、类型参数(PEP 695)、`type X = …` 拿不准;模块里有 `from m import *` 时模块级的名字都拿不准。
|
||||
*/
|
||||
class PythonScopes {
|
||||
readonly collector = new BindingCollector()
|
||||
readonly module: Scope
|
||||
/** 各层用 global / nonlocal 声明的名字 */
|
||||
private readonly declared = new Map<Scope, Map<string, 'global' | 'nonlocal'>>()
|
||||
private star = false
|
||||
|
||||
constructor() {
|
||||
this.module = this.collector.scope('module', undefined)
|
||||
}
|
||||
|
||||
visit(node: Node, scope: Scope): void {
|
||||
switch (node.type) {
|
||||
case 'identifier':
|
||||
this.collector.see(scope, node)
|
||||
return
|
||||
case 'function_definition':
|
||||
this.function(node, scope)
|
||||
return
|
||||
case 'lambda': {
|
||||
const inner = this.collector.scope('function', scope)
|
||||
this.parameters(node.childForFieldName('parameters'), scope, inner)
|
||||
this.visitField(node, 'body', inner)
|
||||
return
|
||||
}
|
||||
case 'class_definition':
|
||||
this.class(node, scope)
|
||||
return
|
||||
case 'assignment':
|
||||
case 'augmented_assignment':
|
||||
case 'for_statement':
|
||||
this.withTarget(node, 'left', scope)
|
||||
return
|
||||
case 'as_pattern':
|
||||
this.withTarget(node, 'alias', scope)
|
||||
return
|
||||
case 'named_expression': {
|
||||
const name = node.childForFieldName('name')
|
||||
if (name)
|
||||
this.declare(name, this.functionScope(scope), 'local', scope)
|
||||
this.visitField(node, 'value', scope)
|
||||
return
|
||||
}
|
||||
case 'delete_statement':
|
||||
for (const target of named(node))
|
||||
this.target(target, scope)
|
||||
return
|
||||
case 'import_statement':
|
||||
case 'import_from_statement':
|
||||
this.import(node, scope)
|
||||
return
|
||||
case 'future_import_statement':
|
||||
return
|
||||
case 'global_statement':
|
||||
case 'nonlocal_statement':
|
||||
for (const name of named(node)) {
|
||||
this.declarations(scope).set(name.text, node.type === 'global_statement' ? 'global' : 'nonlocal')
|
||||
this.collector.see(scope, name)
|
||||
}
|
||||
return
|
||||
case 'keyword_argument':
|
||||
this.visitField(node, 'value', scope)
|
||||
return
|
||||
case 'attribute':
|
||||
this.visitField(node, 'object', scope)
|
||||
return
|
||||
case 'match_statement':
|
||||
this.match(node, scope)
|
||||
return
|
||||
case 'type_alias_statement':
|
||||
this.collector.unsure(scope, node)
|
||||
return
|
||||
}
|
||||
if (COMPREHENSIONS.has(node.type)) {
|
||||
this.comprehension(node, scope)
|
||||
return
|
||||
}
|
||||
for (const child of named(node))
|
||||
this.visit(child, scope)
|
||||
}
|
||||
|
||||
bindings(): NameBinding[] {
|
||||
return this.collector.bindings((scope, name) => this.resolve(scope, name))
|
||||
}
|
||||
|
||||
private function(node: Node, scope: Scope): void {
|
||||
const name = node.childForFieldName('name')
|
||||
if (name)
|
||||
this.declare(name, scope, 'local', scope)
|
||||
const inner = this.collector.scope('function', scope)
|
||||
const types = node.childForFieldName('type_parameters')
|
||||
if (types) {
|
||||
this.collector.unsure(scope, types)
|
||||
this.collector.unsure(inner, types)
|
||||
}
|
||||
this.parameters(node.childForFieldName('parameters'), scope, inner)
|
||||
this.visitField(node, 'return_type', scope)
|
||||
this.visitField(node, 'body', inner)
|
||||
}
|
||||
|
||||
private class(node: Node, scope: Scope): void {
|
||||
const name = node.childForFieldName('name')
|
||||
if (name)
|
||||
this.declare(name, scope, 'local', scope)
|
||||
const inner = this.collector.scope('class', scope)
|
||||
const types = node.childForFieldName('type_parameters')
|
||||
if (types) {
|
||||
this.collector.unsure(scope, types)
|
||||
this.collector.unsure(inner, types)
|
||||
}
|
||||
this.visitField(node, 'superclasses', scope)
|
||||
this.visitField(node, 'body', inner)
|
||||
}
|
||||
|
||||
/** 参数名是函数这一层的;注解、默认值在外层求值。 */
|
||||
private parameters(list: Node | null, outer: Scope, inner: Scope): void {
|
||||
for (const parameter of named(list)) {
|
||||
switch (parameter.type) {
|
||||
case 'identifier':
|
||||
case 'list_splat_pattern':
|
||||
case 'dictionary_splat_pattern':
|
||||
this.parameter(parameter, inner)
|
||||
break
|
||||
case 'typed_parameter':
|
||||
this.parameter(named(parameter)[0], inner)
|
||||
this.visitField(parameter, 'type', outer)
|
||||
break
|
||||
case 'default_parameter':
|
||||
case 'typed_default_parameter':
|
||||
this.parameter(parameter.childForFieldName('name'), inner)
|
||||
this.visitField(parameter, 'type', outer)
|
||||
this.visitField(parameter, 'value', outer)
|
||||
break
|
||||
case 'keyword_separator':
|
||||
case 'positional_separator':
|
||||
break
|
||||
default:
|
||||
this.collector.unsure(inner, parameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private parameter(node: Node | null | undefined, scope: Scope): void {
|
||||
const name = node?.type === 'list_splat_pattern' || node?.type === 'dictionary_splat_pattern' ? named(node)[0] : node
|
||||
if (name?.type === 'identifier')
|
||||
this.declare(name, scope, 'local', scope)
|
||||
else if (node)
|
||||
this.collector.unsure(scope, node)
|
||||
}
|
||||
|
||||
/** 推导式自成一层:变量是它的,第一个 for 的可迭代对象在外层求值,后面的在里面。 */
|
||||
private comprehension(node: Node, scope: Scope): void {
|
||||
const inner = this.collector.scope('comprehension', scope)
|
||||
let first = true
|
||||
for (const child of named(node)) {
|
||||
if (child.type !== 'for_in_clause') {
|
||||
this.visit(child, inner)
|
||||
continue
|
||||
}
|
||||
for (let i = 0; i < child.childCount; i++) {
|
||||
const part = child.child(i)
|
||||
if (!part?.isNamed)
|
||||
continue
|
||||
const field = child.fieldNameForChild(i)
|
||||
if (field === 'left')
|
||||
this.target(part, inner)
|
||||
else
|
||||
this.visit(part, field === 'right' && first ? scope : inner)
|
||||
}
|
||||
first = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 模式匹配:被匹配的照常算;case 里的模式(捕获的名字、值)拿不准,守卫和分支照常算。 */
|
||||
private match(node: Node, scope: Scope): void {
|
||||
for (const child of named(node)) {
|
||||
if (child.type !== 'block') {
|
||||
this.visit(child, scope)
|
||||
continue
|
||||
}
|
||||
for (const clause of named(child)) {
|
||||
for (const part of named(clause)) {
|
||||
if (part.type === 'case_pattern')
|
||||
this.collector.unsure(scope, part)
|
||||
else
|
||||
this.visit(part, scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 导入的名字:`import a.b` 绑定 a,`as` 后面的名字、`from m import x` 的 x;模块路径、`as` 前面的原名不算。 */
|
||||
private import(node: Node, scope: Scope): void {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i)
|
||||
if (!child)
|
||||
continue
|
||||
if (child.type === 'wildcard_import')
|
||||
this.star = true
|
||||
if (node.fieldNameForChild(i) !== 'name')
|
||||
continue
|
||||
const name = child.type === 'aliased_import' ? child.childForFieldName('alias') : named(child)[0]
|
||||
if (name?.type === 'identifier')
|
||||
this.declare(name, scope, 'import', scope)
|
||||
}
|
||||
}
|
||||
|
||||
/** field 那个子节点是赋值目标,别的照常算。 */
|
||||
private withTarget(node: Node, field: string, scope: Scope): void {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i)
|
||||
if (!child?.isNamed)
|
||||
continue
|
||||
if (node.fieldNameForChild(i) === field)
|
||||
this.target(child, scope)
|
||||
else
|
||||
this.visit(child, scope)
|
||||
}
|
||||
}
|
||||
|
||||
/** 赋值目标:名字在这一层绑定;属性、下标是用到对象;认不出的写法拿不准。 */
|
||||
private target(node: Node, scope: Scope): void {
|
||||
if (node.type === 'identifier')
|
||||
this.declare(node, scope, 'local', scope)
|
||||
else if (TARGET_LISTS.has(node.type))
|
||||
named(node).forEach(child => this.target(child, scope))
|
||||
else if (node.type === 'attribute' || node.type === 'subscript')
|
||||
this.visit(node, scope)
|
||||
else
|
||||
this.collector.unsure(scope, node)
|
||||
}
|
||||
|
||||
/** name 在 owner 这一层绑定,出现在 scope 里。 */
|
||||
private declare(name: Node, owner: Scope, kind: BindKind, scope: Scope): void {
|
||||
owner.bind(name.text, kind)
|
||||
this.collector.see(scope, name)
|
||||
}
|
||||
|
||||
private visitField(node: Node, field: string, scope: Scope): void {
|
||||
for (const child of node.childrenForFieldName(field)) {
|
||||
if (child?.isNamed)
|
||||
this.visit(child, scope)
|
||||
}
|
||||
}
|
||||
|
||||
private declarations(scope: Scope): Map<string, 'global' | 'nonlocal'> {
|
||||
let found = this.declared.get(scope)
|
||||
if (!found) {
|
||||
found = new Map()
|
||||
this.declared.set(scope, found)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
/** 海象运算符绑定的那一层:往外第一层不是推导式的。 */
|
||||
private functionScope(scope: Scope): Scope {
|
||||
let found = scope
|
||||
while (found.kind === 'comprehension' && found.parent)
|
||||
found = found.parent
|
||||
return found
|
||||
}
|
||||
|
||||
/** scope 里用到的 name 落在哪个绑定上;拿不准的没有。 */
|
||||
private resolve(scope: Scope, name: string): string | undefined {
|
||||
for (let current: Scope | undefined = scope; current; current = current.parent) {
|
||||
if (current !== scope && current.kind === 'class')
|
||||
continue
|
||||
if (current.unsure.has(name))
|
||||
return undefined
|
||||
const declared = this.declared.get(current)?.get(name)
|
||||
if (declared === 'global')
|
||||
return this.global(name)
|
||||
if (declared === 'nonlocal')
|
||||
return this.nonlocal(current, name)
|
||||
if (current.binds.has(name))
|
||||
return current === this.module ? this.global(name) : `${current.id}:${name}`
|
||||
}
|
||||
return this.global(name)
|
||||
}
|
||||
|
||||
/** scope 里 nonlocal 声明的 name:往外找最近一层绑定它的函数,找不到拿不准。 */
|
||||
private nonlocal(scope: Scope, name: string): string | undefined {
|
||||
for (let current = scope.parent; current && current !== this.module; current = current.parent) {
|
||||
if (current.kind === 'class')
|
||||
continue
|
||||
if (current.unsure.has(name))
|
||||
return undefined
|
||||
const declared = this.declared.get(current)?.get(name)
|
||||
if (declared === 'nonlocal')
|
||||
return this.nonlocal(current, name)
|
||||
if (declared === undefined && current.binds.has(name))
|
||||
return `${current.id}:${name}`
|
||||
if (declared !== undefined)
|
||||
return undefined
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
/** 模块级的名字(含哪儿都没绑定的内置名字);有 `from m import *` 时拿不准。 */
|
||||
private global(name: string): string | undefined {
|
||||
return this.star ? undefined : `:${name}`
|
||||
}
|
||||
}
|
||||
|
||||
/** 一份 Python 源码里各个名字的绑定(见 NameBinding)。 */
|
||||
export function pythonBindings(root: Node): NameBinding[] {
|
||||
const scopes = new PythonScopes()
|
||||
for (const child of named(root))
|
||||
scopes.visit(child, scopes.module)
|
||||
return scopes.bindings()
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import type { Access, BaseFacts, BlockShape, CallArgument, CallFacts, ClassFacts
|
||||
import type { CommentRules, ExceptionRules } from './shared'
|
||||
import * as path from 'node:path'
|
||||
import { attachComments, children, compoundShape, extendHeader, headerSpan, insideOf, leafBlock, leafShape, listBlocks, named, nodeSpan, sameNode, syntaxChildren, withExceptions } from './shared'
|
||||
import { typescriptBindings } from './typescriptBindings'
|
||||
import { typescriptNameReader } from './typescriptNames'
|
||||
|
||||
const GRAMMARS: Record<string, string> = {
|
||||
@@ -662,6 +663,7 @@ export function createTypeScriptAdapter(wasmDir: string): LanguageAdapter {
|
||||
freshDoc,
|
||||
call: typescriptCall,
|
||||
collection: typescriptCollection,
|
||||
bindings: root => typescriptBindings(root),
|
||||
displayNames: typescriptNameReader,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { NameBinding } from '../contract'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { runtime, WASM_DIR } from '../testSupport'
|
||||
import { createTypeScriptAdapter } from './typescript'
|
||||
|
||||
const ts = createTypeScriptAdapter(WASM_DIR)
|
||||
|
||||
/** 读一份源码的绑定,按名字第几次出现(0 起,按整词数)问它落在哪个绑定上。 */
|
||||
async function bindingsOf(source: string, languageId = 'typescript'): Promise<{ of: (name: string, n: number) => NameBinding | undefined, same: (name: string, ...ns: number[]) => boolean }> {
|
||||
const tree = await runtime.parse(ts, languageId, source)
|
||||
let list: NameBinding[]
|
||||
try {
|
||||
list = ts.bindings!(tree.rootNode, source, languageId)
|
||||
}
|
||||
finally {
|
||||
tree.delete()
|
||||
}
|
||||
const at = (name: string, n: number): number => [...source.matchAll(new RegExp(`(?<![\\w$])${name}(?![\\w$])`, 'g'))][n].index!
|
||||
const of = (name: string, n: number): NameBinding | undefined => list.find(binding => binding.occurrences.some(range => range.startIndex === at(name, n) && range.endIndex === at(name, n) + name.length))
|
||||
return { of, same: (name, ...ns) => ns.every(n => of(name, n) !== undefined && of(name, n) === of(name, ns[0])) }
|
||||
}
|
||||
|
||||
describe('typescript 的名字绑定', () => {
|
||||
it('同一个函数里参数的各处是一个绑定、本文件定义;内层箭头函数同名参数是另一个;内层没重新绑定的用的是外层的', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'function outer(session: Session, other: number) {',
|
||||
' use(session)',
|
||||
' const inner = (session: string) => session',
|
||||
' function closure() { return other }',
|
||||
' return session',
|
||||
'}',
|
||||
].join('\n'))
|
||||
expect(same('session', 0, 1, 4)).toBe(true)
|
||||
expect(same('session', 2, 3)).toBe(true)
|
||||
expect(of('session', 2)).not.toBe(of('session', 0))
|
||||
expect(of('session', 0)?.local).toBe(true)
|
||||
expect(same('other', 0, 1)).toBe(true)
|
||||
expect(of('use', 0)?.local).toBe(false)
|
||||
expect(of('Session', 0)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('块里的 let 遮住外层;var 提到函数体;for…of、catch 的变量各是一层', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'function f(items) {',
|
||||
' let x = 1',
|
||||
' if (items) {',
|
||||
' let x = 2',
|
||||
' use(x)',
|
||||
' var y = x',
|
||||
' }',
|
||||
' for (const x of items) use(x)',
|
||||
' try { run() } catch (x) { use(x) }',
|
||||
' return [x, y]',
|
||||
'}',
|
||||
].join('\n'))
|
||||
expect(same('x', 0, 8)).toBe(true)
|
||||
expect(same('x', 1, 2, 3)).toBe(true)
|
||||
expect(same('x', 4, 5)).toBe(true)
|
||||
expect(same('x', 6, 7)).toBe(true)
|
||||
expect(new Set([0, 1, 4, 6].map(n => of('x', n))).size).toBe(4)
|
||||
expect(same('y', 0, 1)).toBe(true)
|
||||
})
|
||||
|
||||
it('导入的(连 require 进来的)不是本文件定义的,as 前面的原名不报;解构出来的、默认值里用到的照常认', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'import Default, { a, b as c } from \'m\'',
|
||||
'import * as ns from \'n\'',
|
||||
'const { p, q: r = a, ...rest } = ns.obj',
|
||||
'const { helper } = require(\'./h\')',
|
||||
'export function g({ p: inner }: Props = fallback) { return [p, r, rest, c, Default, inner, helper] }',
|
||||
].join('\n'))
|
||||
// require 进来的按导入算:跳到定义会跳进那个模块
|
||||
expect(same('helper', 0, 1)).toBe(true)
|
||||
expect(of('helper', 0)?.local).toBe(false)
|
||||
expect(same('a', 0, 1)).toBe(true)
|
||||
expect(of('a', 0)?.local).toBe(false)
|
||||
expect(of('b', 0)).toBeUndefined()
|
||||
expect(same('c', 0, 1)).toBe(true)
|
||||
expect(same('Default', 0, 1)).toBe(true)
|
||||
expect(same('ns', 0, 1)).toBe(true)
|
||||
expect(same('p', 0, 2)).toBe(true)
|
||||
expect(of('p', 0)?.local).toBe(true)
|
||||
expect(of('q', 0)).toBeUndefined()
|
||||
expect(same('r', 0, 1)).toBe(true)
|
||||
expect(same('rest', 0, 1)).toBe(true)
|
||||
expect(same('inner', 0, 1)).toBe(true)
|
||||
})
|
||||
|
||||
it('类:类声明的名字和方法里 new 它是同一个;类表达式、函数表达式自己的名字只在里面', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'class Box { static make() { return new Box(limit) } size = limit }',
|
||||
'const limit = 3',
|
||||
'const Named = class Inner { clone() { return new Inner() } }',
|
||||
'const fn = function self() { return self }',
|
||||
'const self = 1',
|
||||
].join('\n'))
|
||||
expect(same('Box', 0, 1)).toBe(true)
|
||||
expect(same('limit', 0, 1, 2)).toBe(true)
|
||||
expect(same('Inner', 0, 1)).toBe(true)
|
||||
expect(same('self', 0, 1)).toBe(true)
|
||||
expect(of('self', 2)).not.toBe(of('self', 0))
|
||||
})
|
||||
|
||||
it('拿不准的不报:对象字面量的简写、JSX 标签名、块里的函数声明、命名空间的名字、declare 的', async () => {
|
||||
const { of, same } = await bindingsOf([
|
||||
'const value = 1',
|
||||
'const obj = { value }',
|
||||
'const el = <Value prop={value} />',
|
||||
'if (obj) { function hoisted() {} hoisted() }',
|
||||
'namespace Space { export const z = 1 }',
|
||||
'declare const ambient: number',
|
||||
'use(Space, ambient)',
|
||||
].join('\n'), 'typescriptreact')
|
||||
expect(of('value', 1)).toBeUndefined()
|
||||
expect(same('value', 0, 2)).toBe(true)
|
||||
expect(of('Value', 0)).toBeUndefined()
|
||||
expect(of('hoisted', 0)).toBeUndefined()
|
||||
expect(of('hoisted', 1)).toBeUndefined()
|
||||
expect(of('Space', 1)).toBeUndefined()
|
||||
expect(of('ambient', 1)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,339 @@
|
||||
import type { NameBinding, Node } from '../contract'
|
||||
import type { BindKind, Scope } from './bindings'
|
||||
import { BindingCollector } from './bindings'
|
||||
import { named } from './shared'
|
||||
|
||||
/** 只写类型的地方:名字是类型那一套的,不看。 */
|
||||
const TYPE_NODES = new Set([
|
||||
'type_annotation',
|
||||
'type_arguments',
|
||||
'type_parameters',
|
||||
'type_alias_declaration',
|
||||
'interface_declaration',
|
||||
'implements_clause',
|
||||
'opting_type_annotation',
|
||||
'omitting_type_annotation',
|
||||
'asserts_annotation',
|
||||
'type_predicate_annotation',
|
||||
'abstract_method_signature',
|
||||
'method_signature',
|
||||
'property_signature',
|
||||
'index_signature',
|
||||
])
|
||||
|
||||
/** 函数:自己一层放参数,函数体再一层。 */
|
||||
const FUNCTIONS = new Set(['function_declaration', 'generator_function_declaration', 'function_expression', 'function', 'generator_function', 'arrow_function', 'method_definition'])
|
||||
|
||||
/** 自成一层块作用域(let、const、class 在里面)。 */
|
||||
const BLOCKS = new Set(['statement_block', 'switch_body', 'for_statement', 'for_in_statement'])
|
||||
|
||||
/** 声明里的名字怎么记:declare 是在 owner 这一层绑定,assign 是给已有的名字赋值(解构赋值)。 */
|
||||
type PatternMode = { declare: Scope, kind: BindKind } | 'assign'
|
||||
|
||||
/**
|
||||
* TypeScript / JavaScript 的作用域规则:模块、函数(参数一层、函数体一层)、块各是一层。let、const、class、函数声明在所在的块,
|
||||
* var 提到所在的函数体;用到的名字从里往外找。只认值那一套名字:写在类型里的、对象字面量里的简写属性、JSX 标签名不报;
|
||||
* 命名空间、declare 声明、块里的函数声明、`import a = b.c` 拿不准。
|
||||
*/
|
||||
class TypeScriptScopes {
|
||||
readonly collector = new BindingCollector()
|
||||
readonly module: Scope
|
||||
|
||||
constructor() {
|
||||
this.module = this.collector.scope('function', undefined)
|
||||
}
|
||||
|
||||
visit(node: Node, scope: Scope): void {
|
||||
if (TYPE_NODES.has(node.type))
|
||||
return
|
||||
if (FUNCTIONS.has(node.type)) {
|
||||
this.function(node, scope)
|
||||
return
|
||||
}
|
||||
switch (node.type) {
|
||||
case 'identifier':
|
||||
this.collector.see(scope, node)
|
||||
return
|
||||
case 'lexical_declaration':
|
||||
case 'variable_declaration':
|
||||
this.declaration(node, scope, node.type === 'variable_declaration' ? this.varScope(scope) : scope)
|
||||
return
|
||||
case 'class_declaration':
|
||||
case 'abstract_class_declaration':
|
||||
case 'class':
|
||||
this.class(node, scope)
|
||||
return
|
||||
case 'enum_declaration':
|
||||
case 'function_signature': {
|
||||
const name = node.childForFieldName('name')
|
||||
if (name)
|
||||
this.declare(name, scope, 'local', scope)
|
||||
return
|
||||
}
|
||||
case 'import_statement':
|
||||
this.import(node, scope)
|
||||
return
|
||||
case 'ambient_declaration':
|
||||
this.collector.unsure(scope, node)
|
||||
return
|
||||
case 'internal_module':
|
||||
case 'module':
|
||||
case 'import_alias': {
|
||||
// 里面不看,只是它在这一层绑定的名字拿不准
|
||||
const name = node.childForFieldName('name') ?? named(node)[0]
|
||||
if (name)
|
||||
this.collector.unsure(scope, name)
|
||||
return
|
||||
}
|
||||
case 'export_clause':
|
||||
case 'namespace_export':
|
||||
case 'shorthand_property_identifier':
|
||||
return
|
||||
case 'catch_clause':
|
||||
this.catch(node, scope)
|
||||
return
|
||||
case 'assignment_expression':
|
||||
this.assignment(node, scope)
|
||||
return
|
||||
case 'as_expression':
|
||||
case 'satisfies_expression': {
|
||||
const [value] = named(node)
|
||||
if (value)
|
||||
this.visit(value, scope)
|
||||
return
|
||||
}
|
||||
case 'jsx_opening_element':
|
||||
case 'jsx_closing_element':
|
||||
case 'jsx_self_closing_element':
|
||||
for (const attribute of node.childrenForFieldName('attribute')) {
|
||||
if (attribute)
|
||||
this.visit(attribute, scope)
|
||||
}
|
||||
return
|
||||
}
|
||||
if (BLOCKS.has(node.type)) {
|
||||
this.block(node, scope)
|
||||
return
|
||||
}
|
||||
for (const child of named(node))
|
||||
this.visit(child, scope)
|
||||
}
|
||||
|
||||
bindings(): NameBinding[] {
|
||||
return this.collector.bindings((scope, name) => {
|
||||
for (let current: Scope | undefined = scope; current; current = current.parent) {
|
||||
if (current.unsure.has(name))
|
||||
return undefined
|
||||
if (current.binds.has(name))
|
||||
return `${current.id}:${name}`
|
||||
}
|
||||
return `:${name}`
|
||||
})
|
||||
}
|
||||
|
||||
/** 块:let、const、class、for 里声明的变量在这一层;`for (… of …)` 的可迭代对象也在这一层(按规范它看得见循环变量)。 */
|
||||
private block(node: Node, scope: Scope): void {
|
||||
const inner = this.collector.scope('block', scope)
|
||||
if (node.type === 'for_in_statement') {
|
||||
const kind = node.childForFieldName('kind')?.type
|
||||
const left = node.childForFieldName('left')
|
||||
if (left)
|
||||
this.pattern(left, kind === undefined ? 'assign' : { declare: kind === 'var' ? this.varScope(scope) : inner, kind: 'local' }, inner)
|
||||
for (const field of ['right', 'body']) {
|
||||
const child = node.childForFieldName(field)
|
||||
if (child)
|
||||
this.visit(child, inner)
|
||||
}
|
||||
return
|
||||
}
|
||||
for (const child of named(node))
|
||||
this.visit(child, inner)
|
||||
}
|
||||
|
||||
/** 函数:参数一层(默认值在这一层求值),函数体再一层;函数表达式自己的名字只在它自己里面。 */
|
||||
private function(node: Node, scope: Scope): void {
|
||||
const name = node.childForFieldName('name')
|
||||
let outer = scope
|
||||
if (name?.type === 'identifier') {
|
||||
if (node.type === 'function_declaration' || node.type === 'generator_function_declaration') {
|
||||
// 块里的函数声明在非严格模式下会提到外面,拿不准
|
||||
if (scope.kind === 'block')
|
||||
scope.unsure.add(name.text)
|
||||
this.declare(name, scope, 'local', scope)
|
||||
}
|
||||
else {
|
||||
outer = this.collector.scope('name', scope)
|
||||
this.declare(name, outer, 'local', outer)
|
||||
}
|
||||
}
|
||||
else if (name) {
|
||||
this.visit(name, scope)
|
||||
}
|
||||
const parameters = this.collector.scope('parameters', outer)
|
||||
const single = node.childForFieldName('parameter')
|
||||
if (single)
|
||||
this.pattern(single, { declare: parameters, kind: 'local' }, parameters)
|
||||
for (const parameter of named(node.childForFieldName('parameters'))) {
|
||||
const pattern = parameter.childForFieldName('pattern')
|
||||
if (pattern && pattern.type !== 'this')
|
||||
this.pattern(pattern, { declare: parameters, kind: 'local' }, parameters)
|
||||
const value = parameter.childForFieldName('value')
|
||||
if (value)
|
||||
this.visit(value, parameters)
|
||||
for (const decorator of named(parameter).filter(child => child.type === 'decorator'))
|
||||
this.visit(decorator, scope)
|
||||
}
|
||||
for (const decorator of named(node).filter(child => child.type === 'decorator'))
|
||||
this.visit(decorator, scope)
|
||||
const body = node.childForFieldName('body')
|
||||
if (body?.type === 'statement_block') {
|
||||
const inner = this.collector.scope('function', parameters)
|
||||
for (const child of named(body))
|
||||
this.visit(child, inner)
|
||||
}
|
||||
else if (body) {
|
||||
this.visit(body, parameters)
|
||||
}
|
||||
}
|
||||
|
||||
/** 类:类声明的名字在所在的块,类表达式的名字只在类里面;父类、装饰器、成员照常算。 */
|
||||
private class(node: Node, scope: Scope): void {
|
||||
const name = node.childForFieldName('name')
|
||||
let inner = scope
|
||||
if (name && node.type === 'class') {
|
||||
inner = this.collector.scope('name', scope)
|
||||
this.declare(name, inner, 'local', inner)
|
||||
}
|
||||
else if (name) {
|
||||
this.declare(name, scope, 'local', scope)
|
||||
}
|
||||
for (const child of named(node)) {
|
||||
if (child.type !== name?.type || child.startIndex !== name.startIndex)
|
||||
this.visit(child, inner)
|
||||
}
|
||||
}
|
||||
|
||||
/** 变量声明;`const x = require('m')` 这种在 JavaScript 里按导入算(跳到定义会跳进 m)。 */
|
||||
private declaration(node: Node, scope: Scope, owner: Scope): void {
|
||||
for (const declarator of named(node)) {
|
||||
if (declarator.type !== 'variable_declarator') {
|
||||
this.visit(declarator, scope)
|
||||
continue
|
||||
}
|
||||
const value = declarator.childForFieldName('value')
|
||||
const required = value?.type === 'call_expression' && value.childForFieldName('function')?.text === 'require'
|
||||
const name = declarator.childForFieldName('name')
|
||||
if (name)
|
||||
this.pattern(name, { declare: owner, kind: required ? 'import' : 'local' }, scope)
|
||||
if (value)
|
||||
this.visit(value, scope)
|
||||
}
|
||||
}
|
||||
|
||||
/** 导入的名字:默认导入、`* as ns`、`{ a, b as c }` 里 as 后面的(没有 as 就是它自己)。 */
|
||||
private import(node: Node, scope: Scope): void {
|
||||
for (const clause of named(node)) {
|
||||
if (clause.type !== 'import_clause' && clause.type !== 'import_require_clause')
|
||||
continue
|
||||
for (const part of named(clause)) {
|
||||
if (part.type === 'identifier') {
|
||||
this.declare(part, scope, 'import', scope)
|
||||
}
|
||||
else if (part.type === 'namespace_import') {
|
||||
const [name] = named(part)
|
||||
if (name)
|
||||
this.declare(name, scope, 'import', scope)
|
||||
}
|
||||
else if (part.type === 'named_imports') {
|
||||
for (const specifier of named(part)) {
|
||||
const name = specifier.childForFieldName('alias') ?? specifier.childForFieldName('name')
|
||||
if (name?.type === 'identifier')
|
||||
this.declare(name, scope, 'import', scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** catch 的参数和它的块是同一层。 */
|
||||
private catch(node: Node, scope: Scope): void {
|
||||
const inner = this.collector.scope('block', scope)
|
||||
const parameter = node.childForFieldName('parameter')
|
||||
if (parameter)
|
||||
this.pattern(parameter, { declare: inner, kind: 'local' }, inner)
|
||||
for (const child of named(node.childForFieldName('body')))
|
||||
this.visit(child, inner)
|
||||
}
|
||||
|
||||
/** 赋值:左边是名字、解构是给已有的名字赋值,是成员就是用到对象。 */
|
||||
private assignment(node: Node, scope: Scope): void {
|
||||
const left = node.childForFieldName('left')
|
||||
if (left)
|
||||
this.pattern(left, 'assign', scope)
|
||||
const right = node.childForFieldName('right')
|
||||
if (right)
|
||||
this.visit(right, scope)
|
||||
}
|
||||
|
||||
/** 声明、参数、解构里的名字:照 mode 在一层绑定或者当用到;默认值、计算出来的属性名照常算;声明里认不出的写法拿不准。 */
|
||||
private pattern(node: Node, mode: PatternMode, scope: Scope): void {
|
||||
switch (node.type) {
|
||||
case 'identifier':
|
||||
case 'shorthand_property_identifier_pattern':
|
||||
if (mode === 'assign')
|
||||
this.collector.see(scope, node)
|
||||
else
|
||||
this.declare(node, mode.declare, mode.kind, scope)
|
||||
return
|
||||
case 'object_pattern':
|
||||
case 'array_pattern':
|
||||
case 'rest_pattern':
|
||||
named(node).forEach(child => this.pattern(child, mode, scope))
|
||||
return
|
||||
case 'pair_pattern': {
|
||||
const key = node.childForFieldName('key')
|
||||
if (key?.type === 'computed_property_name')
|
||||
this.visit(key, scope)
|
||||
const value = node.childForFieldName('value')
|
||||
if (value)
|
||||
this.pattern(value, mode, scope)
|
||||
return
|
||||
}
|
||||
case 'assignment_pattern':
|
||||
case 'object_assignment_pattern': {
|
||||
const left = node.childForFieldName('left')
|
||||
if (left)
|
||||
this.pattern(left, mode, scope)
|
||||
const right = node.childForFieldName('right')
|
||||
if (right)
|
||||
this.visit(right, scope)
|
||||
return
|
||||
}
|
||||
}
|
||||
if (mode === 'assign')
|
||||
this.visit(node, scope)
|
||||
else
|
||||
this.collector.unsure(mode.declare, node)
|
||||
}
|
||||
|
||||
private declare(name: Node, owner: Scope, kind: BindKind, scope: Scope): void {
|
||||
owner.bind(name.text, kind)
|
||||
this.collector.see(scope, name)
|
||||
}
|
||||
|
||||
/** var 提到的那一层:往外第一层函数体(或模块)。 */
|
||||
private varScope(scope: Scope): Scope {
|
||||
let found = scope
|
||||
while (found.kind !== 'function' && found.parent)
|
||||
found = found.parent
|
||||
return found
|
||||
}
|
||||
}
|
||||
|
||||
/** 一份 TypeScript / JavaScript 源码里各个名字的绑定(见 NameBinding)。 */
|
||||
export function typescriptBindings(root: Node): NameBinding[] {
|
||||
const scopes = new TypeScriptScopes()
|
||||
for (const child of named(root))
|
||||
scopes.visit(child, scopes.module)
|
||||
return scopes.bindings()
|
||||
}
|
||||
Reference in new issue
Block a user