UNPKG

10 kBJavaScriptView Raw
1var types = require("./types");
2var Type = types.Type;
3var namedTypes = types.namedTypes;
4var Node = namedTypes.Node;
5var Expression = namedTypes.Expression;
6var isArray = types.builtInTypes.array;
7var hasOwn = Object.prototype.hasOwnProperty;
8var b = types.builders;
9
10function Scope(path, parentScope) {
11 if (!(this instanceof Scope)) {
12 throw new Error("Scope constructor cannot be invoked without 'new'");
13 }
14 if (!(path instanceof require("./node-path"))) {
15 throw new Error("");
16 }
17 ScopeType.assert(path.value);
18
19 var depth;
20
21 if (parentScope) {
22 if (!(parentScope instanceof Scope)) {
23 throw new Error("");
24 }
25 depth = parentScope.depth + 1;
26 } else {
27 parentScope = null;
28 depth = 0;
29 }
30
31 Object.defineProperties(this, {
32 path: { value: path },
33 node: { value: path.value },
34 isGlobal: { value: !parentScope, enumerable: true },
35 depth: { value: depth },
36 parent: { value: parentScope },
37 bindings: { value: {} },
38 types: { value: {} },
39 });
40}
41
42var scopeTypes = [
43 // Program nodes introduce global scopes.
44 namedTypes.Program,
45
46 // Function is the supertype of FunctionExpression,
47 // FunctionDeclaration, ArrowExpression, etc.
48 namedTypes.Function,
49
50 // In case you didn't know, the caught parameter shadows any variable
51 // of the same name in an outer scope.
52 namedTypes.CatchClause
53];
54
55var ScopeType = Type.or.apply(Type, scopeTypes);
56
57Scope.isEstablishedBy = function(node) {
58 return ScopeType.check(node);
59};
60
61var Sp = Scope.prototype;
62
63// Will be overridden after an instance lazily calls scanScope.
64Sp.didScan = false;
65
66Sp.declares = function(name) {
67 this.scan();
68 return hasOwn.call(this.bindings, name);
69};
70
71Sp.declaresType = function(name) {
72 this.scan();
73 return hasOwn.call(this.types, name);
74};
75
76Sp.declareTemporary = function(prefix) {
77 if (prefix) {
78 if (!/^[a-z$_]/i.test(prefix)) {
79 throw new Error("");
80 }
81 } else {
82 prefix = "t$";
83 }
84
85 // Include this.depth in the name to make sure the name does not
86 // collide with any variables in nested/enclosing scopes.
87 prefix += this.depth.toString(36) + "$";
88
89 this.scan();
90
91 var index = 0;
92 while (this.declares(prefix + index)) {
93 ++index;
94 }
95
96 var name = prefix + index;
97 return this.bindings[name] = types.builders.identifier(name);
98};
99
100Sp.injectTemporary = function(identifier, init) {
101 identifier || (identifier = this.declareTemporary());
102
103 var bodyPath = this.path.get("body");
104 if (namedTypes.BlockStatement.check(bodyPath.value)) {
105 bodyPath = bodyPath.get("body");
106 }
107
108 bodyPath.unshift(
109 b.variableDeclaration(
110 "var",
111 [b.variableDeclarator(identifier, init || null)]
112 )
113 );
114
115 return identifier;
116};
117
118Sp.scan = function(force) {
119 if (force || !this.didScan) {
120 for (var name in this.bindings) {
121 // Empty out this.bindings, just in cases.
122 delete this.bindings[name];
123 }
124 scanScope(this.path, this.bindings, this.types);
125 this.didScan = true;
126 }
127};
128
129Sp.getBindings = function () {
130 this.scan();
131 return this.bindings;
132};
133
134Sp.getTypes = function () {
135 this.scan();
136 return this.types;
137};
138
139function scanScope(path, bindings, scopeTypes) {
140 var node = path.value;
141 ScopeType.assert(node);
142
143 if (namedTypes.CatchClause.check(node)) {
144 // A catch clause establishes a new scope but the only variable
145 // bound in that scope is the catch parameter. Any other
146 // declarations create bindings in the outer scope.
147 addPattern(path.get("param"), bindings);
148
149 } else {
150 recursiveScanScope(path, bindings, scopeTypes);
151 }
152}
153
154function recursiveScanScope(path, bindings, scopeTypes) {
155 var node = path.value;
156
157 if (path.parent &&
158 namedTypes.FunctionExpression.check(path.parent.node) &&
159 path.parent.node.id) {
160 addPattern(path.parent.get("id"), bindings);
161 }
162
163 if (!node) {
164 // None of the remaining cases matter if node is falsy.
165
166 } else if (isArray.check(node)) {
167 path.each(function(childPath) {
168 recursiveScanChild(childPath, bindings, scopeTypes);
169 });
170
171 } else if (namedTypes.Function.check(node)) {
172 path.get("params").each(function(paramPath) {
173 addPattern(paramPath, bindings);
174 });
175
176 recursiveScanChild(path.get("body"), bindings, scopeTypes);
177
178 } else if (namedTypes.TypeAlias && namedTypes.TypeAlias.check(node)) {
179 addTypePattern(path.get("id"), scopeTypes);
180
181 } else if (namedTypes.VariableDeclarator.check(node)) {
182 addPattern(path.get("id"), bindings);
183 recursiveScanChild(path.get("init"), bindings, scopeTypes);
184
185 } else if (node.type === "ImportSpecifier" ||
186 node.type === "ImportNamespaceSpecifier" ||
187 node.type === "ImportDefaultSpecifier") {
188 addPattern(
189 // Esprima used to use the .name field to refer to the local
190 // binding identifier for ImportSpecifier nodes, but .id for
191 // ImportNamespaceSpecifier and ImportDefaultSpecifier nodes.
192 // ESTree/Acorn/ESpree use .local for all three node types.
193 path.get(node.local ? "local" :
194 node.name ? "name" : "id"),
195 bindings
196 );
197
198 } else if (Node.check(node) && !Expression.check(node)) {
199 types.eachField(node, function(name, child) {
200 var childPath = path.get(name);
201 if (childPath.value !== child) {
202 throw new Error("");
203 }
204 recursiveScanChild(childPath, bindings, scopeTypes);
205 });
206 }
207}
208
209function recursiveScanChild(path, bindings, scopeTypes) {
210 var node = path.value;
211
212 if (!node || Expression.check(node)) {
213 // Ignore falsy values and Expressions.
214
215 } else if (namedTypes.FunctionDeclaration.check(node)) {
216 addPattern(path.get("id"), bindings);
217
218 } else if (namedTypes.ClassDeclaration &&
219 namedTypes.ClassDeclaration.check(node)) {
220 addPattern(path.get("id"), bindings);
221
222 } else if (ScopeType.check(node)) {
223 if (namedTypes.CatchClause.check(node)) {
224 var catchParamName = node.param.name;
225 var hadBinding = hasOwn.call(bindings, catchParamName);
226
227 // Any declarations that occur inside the catch body that do
228 // not have the same name as the catch parameter should count
229 // as bindings in the outer scope.
230 recursiveScanScope(path.get("body"), bindings, scopeTypes);
231
232 // If a new binding matching the catch parameter name was
233 // created while scanning the catch body, ignore it because it
234 // actually refers to the catch parameter and not the outer
235 // scope that we're currently scanning.
236 if (!hadBinding) {
237 delete bindings[catchParamName];
238 }
239 }
240
241 } else {
242 recursiveScanScope(path, bindings, scopeTypes);
243 }
244}
245
246function addPattern(patternPath, bindings) {
247 var pattern = patternPath.value;
248 namedTypes.Pattern.assert(pattern);
249
250 if (namedTypes.Identifier.check(pattern)) {
251 if (hasOwn.call(bindings, pattern.name)) {
252 bindings[pattern.name].push(patternPath);
253 } else {
254 bindings[pattern.name] = [patternPath];
255 }
256
257 } else if (namedTypes.ObjectPattern &&
258 namedTypes.ObjectPattern.check(pattern)) {
259 patternPath.get('properties').each(function(propertyPath) {
260 var property = propertyPath.value;
261 if (namedTypes.Pattern.check(property)) {
262 addPattern(propertyPath, bindings);
263 } else if (namedTypes.Property.check(property)) {
264 addPattern(propertyPath.get('value'), bindings);
265 } else if (namedTypes.SpreadProperty &&
266 namedTypes.SpreadProperty.check(property)) {
267 addPattern(propertyPath.get('argument'), bindings);
268 }
269 });
270
271 } else if (namedTypes.ArrayPattern &&
272 namedTypes.ArrayPattern.check(pattern)) {
273 patternPath.get('elements').each(function(elementPath) {
274 var element = elementPath.value;
275 if (namedTypes.Pattern.check(element)) {
276 addPattern(elementPath, bindings);
277 } else if (namedTypes.SpreadElement &&
278 namedTypes.SpreadElement.check(element)) {
279 addPattern(elementPath.get("argument"), bindings);
280 }
281 });
282
283 } else if (namedTypes.PropertyPattern &&
284 namedTypes.PropertyPattern.check(pattern)) {
285 addPattern(patternPath.get('pattern'), bindings);
286
287 } else if ((namedTypes.SpreadElementPattern &&
288 namedTypes.SpreadElementPattern.check(pattern)) ||
289 (namedTypes.SpreadPropertyPattern &&
290 namedTypes.SpreadPropertyPattern.check(pattern))) {
291 addPattern(patternPath.get('argument'), bindings);
292 }
293}
294
295function addTypePattern(patternPath, types) {
296 var pattern = patternPath.value;
297 namedTypes.Pattern.assert(pattern);
298
299 if (namedTypes.Identifier.check(pattern)) {
300 if (hasOwn.call(types, pattern.name)) {
301 types[pattern.name].push(patternPath);
302 } else {
303 types[pattern.name] = [patternPath];
304 }
305
306 }
307}
308
309Sp.lookup = function(name) {
310 for (var scope = this; scope; scope = scope.parent)
311 if (scope.declares(name))
312 break;
313 return scope;
314};
315
316Sp.lookupType = function(name) {
317 for (var scope = this; scope; scope = scope.parent)
318 if (scope.declaresType(name))
319 break;
320 return scope;
321};
322
323Sp.getGlobalScope = function() {
324 var scope = this;
325 while (!scope.isGlobal)
326 scope = scope.parent;
327 return scope;
328};
329
330module.exports = Scope;