-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
/
Copy pathannotateWithScopes.ts
103 lines (88 loc) · 2.45 KB
/
annotateWithScopes.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
import { walk } from 'estree-walker';
import { Node } from '../interfaces';
export default function annotateWithScopes(expression: Node) {
let scope = new Scope(null, false);
walk(expression, {
enter(node: Node) {
if (/Function/.test(node.type)) {
if (node.type === 'FunctionDeclaration') {
scope.declarations.add(node.id.name);
} else {
node._scope = scope = new Scope(scope, false);
if (node.id) scope.declarations.add(node.id.name);
}
node.params.forEach((param: Node) => {
extractNames(param).forEach(name => {
scope.declarations.add(name);
});
});
} else if (/For(?:In|Of)Statement/.test(node.type)) {
node._scope = scope = new Scope(scope, true);
} else if (node.type === 'BlockStatement') {
node._scope = scope = new Scope(scope, true);
} else if (/(Function|Class|Variable)Declaration/.test(node.type)) {
scope.addDeclaration(node);
}
},
leave(node: Node) {
if (node._scope) {
scope = scope.parent;
}
},
});
return scope;
}
class Scope {
parent: Scope;
block: boolean;
declarations: Set<string>;
constructor(parent: Scope, block: boolean) {
this.parent = parent;
this.block = block;
this.declarations = new Set();
}
addDeclaration(node: Node) {
if (node.kind === 'var' && !this.block && this.parent) {
this.parent.addDeclaration(node);
} else if (node.type === 'VariableDeclaration') {
node.declarations.forEach((declarator: Node) => {
extractNames(declarator.id).forEach(name => {
this.declarations.add(name);
});
});
} else {
this.declarations.add(node.id.name);
}
}
has(name: string): boolean {
return (
this.declarations.has(name) || (this.parent && this.parent.has(name))
);
}
}
function extractNames(param: Node) {
const names: string[] = [];
extractors[param.type](names, param);
return names;
}
const extractors = {
Identifier(names: string[], param: Node) {
names.push(param.name);
},
ObjectPattern(names: string[], param: Node) {
param.properties.forEach((prop: Node) => {
extractors[prop.value.type](names, prop.value);
});
},
ArrayPattern(names: string[], param: Node) {
param.elements.forEach((element: Node) => {
if (element) extractors[element.type](names, element);
});
},
RestElement(names: string[], param: Node) {
extractors[param.argument.type](names, param.argument);
},
AssignmentPattern(names: string[], param: Node) {
extractors[param.left.type](names, param.left);
},
};