5
* LICENSE file in the root directory of this source tree.
6
*/
7
8
-import { Binding, NodePath, Scope } from "@babel/traverse";
8
+import { NodePath, Scope } from "@babel/traverse";
9
import * as t from "@babel/types";
10
import { Expression } from "@babel/types";
11
import invariant from "invariant";
326
case "BlockStatement": {
327
const stmt = stmtPath as NodePath<t.BlockStatement>;
328
const statements = stmt.get("body");
329
- const hoistableBindings: Set<Binding> = new Set();
330
-
331
- const recordDeclaration = (lval: NodePath<t.LVal>): void => {
332
- // TODO: support other kinds of declarations that might need to be hoisted
333
- switch (lval.type) {
334
- case "Identifier": {
335
- const lv = lval as NodePath<t.Identifier>;
336
- const binding = stmt.scope.getBinding(lv.node.name);
337
- if (binding != null) {
338
- hoistableBindings.delete(binding);
339
- }
340
- break;
341
- }
342
- }
343
- };
329
+ /**
330
+ * Hoistable identifier bindings defined for this precise block
331
+ * scope (excluding bindings from parent or child block scopes).
332
+ */
333
+ const hoistableIdentifiers: Set<t.Identifier> = new Set();
334
335
for (const [, binding] of Object.entries(stmt.scope.bindings)) {
336
// TODO: support other kinds of bindings
339
binding.path.isVariableDeclarator() &&
340
binding.path.get("id").isIdentifier()
341
) {
352
- hoistableBindings.add(binding);
342
+ hoistableIdentifiers.add(binding.identifier);
343
}
344
}
345
}
346
347
for (const s of statements) {
358
- const hoistableIdentifiers = new Set<NodePath<t.Identifier>>();
359
- /*
360
- * After visiting the declaration, hoisting is no longer required
361
- * TODO: support other kinds of declarations
362
- */
363
- if (s.isVariableDeclaration()) {
364
- for (const decl of s.get("declarations")) {
365
- recordDeclaration(decl.get("id"));
366
- }
367
- }
368
-
348
+ const willHoist = new Set<NodePath<t.Identifier>>();
349
/*
350
* If we see a hoistable identifier before its declaration, it should be hoisted just
371
- * before the statement that references it
351
+ * before the statement that references it.
352
+ * Identifier can only be hoisted if the reference occurs within an inner function
353
*/
354
+ let fnDepth = s.isFunctionDeclaration() ? 1 : 0;
355
+ const withFunctionContext = {
356
+ enter: (): void => {
357
+ fnDepth++;
358
+ },
359
+ exit: (): void => {
360
+ fnDepth--;
361
+ },
362
+ };
363
s.traverse({
364
+ FunctionExpression: withFunctionContext,
365
+ FunctionDeclaration: withFunctionContext,
366
+ ArrowFunctionExpression: withFunctionContext,
367
+ ObjectMethod: withFunctionContext,
368
Identifier(id: NodePath<t.Identifier>) {
375
- if (!id.isReferencedIdentifier()) {
369
+ if (!id.isReferencedIdentifier() || fnDepth === 0) {
370
return;
371
}
378
- const binding = id.scope.getBinding(id.node.name);
379
- if (binding != null && hoistableBindings.has(binding)) {
380
- if (
381
- id.parentPath.isVariableDeclarator() ||
382
- // don't hoist MemberExpr `property`s, only their `object`
383
- (id.parentPath.isMemberExpression() &&
384
- id.parentPath.get("property") === id &&
385
- id.parentPath.node.computed === false)
386
- ) {
387
- return;
388
- }
389
- hoistableIdentifiers.add(id);
372
+ const bindingIdentifier = id.scope.getBindingIdentifier(
373
+ id.node.name
374
+ );
375
+ if (
376
+ bindingIdentifier != null &&
377
+ hoistableIdentifiers.has(bindingIdentifier)
378
+ ) {
379
+ willHoist.add(id);
380
+ }
381
+ },
382
+ });
383
+ /*
384
+ * After visiting the declaration, hoisting is no longer required
385
+ */
386
+ s.traverse({
387
+ Identifier(path: NodePath<t.Identifier>) {
388
+ if (hoistableIdentifiers.has(path.node)) {
389
+ hoistableIdentifiers.delete(path.node);
390
}
391
},
392
});
393
394
// Hoist declarations that need it to the earliest point where they are needed
395
- for (const id of hoistableIdentifiers) {
395
+ for (const id of willHoist) {
396
const binding = stmt.scope.getBinding(id.node.name);
397
CompilerError.invariant(binding != null, {
398
reason: "Expected to find binding for hoisted identifier",
413
loc: id.parentPath.node.loc ?? GeneratedSource,
414
});
415
continue;
416
+ } else if (!binding.path.get("id").isIdentifier()) {
417
+ builder.errors.push({
418
+ severity: ErrorSeverity.Todo,
419
+ reason: "Unsupported variable declaration type for hoisting",
420
+ description: `${binding.path.get("id").type}`,
421
+ suggestions: null,
422
+ loc: id.parentPath.node.loc ?? GeneratedSource,
423
+ });
424
+ continue;
425
}
426
const identifier = builder.resolveIdentifier(id)!;
427
const place: Place = {