fix let variable declaration hoisting bug. All let variable declarators now have a default initializer of undefined unless they're the left of a ForIn or ForOf

This commit is contained in:
Sebastian McKenzie
2014-12-16 08:07:46 +11:00
parent 01bdb7efdc
commit 49578fe223

View File

@@ -3,18 +3,25 @@ var util = require("../../util");
var t = require("../../types");
var _ = require("lodash");
var isLet = function (node) {
var isLet = function (node, parent) {
if (!t.isVariableDeclaration(node)) return false;
if (node._let) return true;
if (node.kind !== "let") return false;
// https://github.com/6to5/6to5/issues/255
if (!t.isFor(parent) || t.isFor(parent) && parent.left !== node) {
_.each(node.declarations, function (declar) {
declar.init = declar.init || t.identifier("undefined");
});
}
node._let = true;
node.kind = "var";
return true;
};
var isVar = function (node) {
return t.isVariableDeclaration(node, { kind: "var" }) && !isLet(node);
var isVar = function (node, parent) {
return t.isVariableDeclaration(node, { kind: "var" }) && !isLet(node, parent);
};
var standardiseLets = function (declars) {
@@ -23,13 +30,13 @@ var standardiseLets = function (declars) {
}
};
exports.VariableDeclaration = function (node) {
isLet(node);
exports.VariableDeclaration = function (node, parent) {
isLet(node, parent);
};
exports.Loop = function (node, parent, file, scope) {
var init = node.left || node.init;
if (isLet(init)) {
if (isLet(init, node)) {
t.ensureBlock(node);
node.body._letDeclars = [init];
}
@@ -91,9 +98,6 @@ LetScoping.prototype.run = function () {
// remap all let references that exist in upper scopes to their uid
this.remap();
// add default initializer to let variables in loop bodys
this.initialiseLoopLets();
// this is a block within a `Function` so we can safely leave it be
if (t.isFunction(this.parent)) return this.noClosure();
@@ -242,7 +246,7 @@ LetScoping.prototype.getInfo = function () {
for (var i in block.body) {
var declar = block.body[i];
if (!isLet(declar)) continue;
if (!isLet(declar, block)) continue;
_.each(t.getIds(declar, true), function (id, key) {
duplicates(id, key);
@@ -253,31 +257,6 @@ LetScoping.prototype.getInfo = function () {
return opts;
};
/**
* Any let variable declared within a loop body need to have an initializer or
* else they'll be hoisted and subsequent iterations of the loop will have a
* previous state. This function adds a default initializer of `undefined` to
* those variables.
*/
LetScoping.prototype.initialiseLoopLets = function () {
var loopParent = this.loopParent;
if (!loopParent) return;
traverse(this.block, function (node) {
if (t.isFunction(node) || t.isLoop(node)) {
return false;
}
if (isLet(node)) {
for (var i in node.declarations) {
var declar = node.declarations[i];
declar.init = declar.init || t.identifier("undefined");
}
}
});
};
/**
* If we're inside of a loop then traverse it and check if it has one of
* the following node types `ReturnStatement`, `BreakStatement`,
@@ -333,16 +312,16 @@ LetScoping.prototype.checkLoop = function () {
LetScoping.prototype.hoistVarDeclarations = function () {
var self = this;
traverse(this.block, function (node) {
traverse(this.block, function (node, parent) {
if (t.isForStatement(node)) {
if (isVar(node.init)) {
if (isVar(node.init, node)) {
node.init = t.sequenceExpression(self.pushDeclar(node.init));
}
} else if (t.isFor(node)) {
if (isVar(node.left)) {
if (isVar(node.left, node)) {
node.left = node.left.declarations[0].id;
}
} else if (isVar(node)) {
} else if (isVar(node, parent)) {
return self.pushDeclar(node).map(t.expressionStatement);
} else if (t.isFunction(node)) {
return false;