diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ec9d4db0a..93935fa179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +# 1.14.17 + + * Add default initializer to let variables within loop bodies. + * Fix excessive `break` replacement inside of switches in let scoping. + # 1.14.16 * Add object getter memos and this shorthand to playground. diff --git a/lib/6to5/transformation/transformers/es6-let-scoping.js b/lib/6to5/transformation/transformers/es6-let-scoping.js index a2e665abef..f0afe96c70 100644 --- a/lib/6to5/transformation/transformers/es6-let-scoping.js +++ b/lib/6to5/transformation/transformers/es6-let-scoping.js @@ -91,6 +91,9 @@ 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(); @@ -106,7 +109,7 @@ LetScoping.prototype.run = function () { // if we're inside of a for loop then we search to see if there are any // `break`s, `continue`s, `return`s etc - this.has = this.checkFor(); + this.has = this.checkLoop(); // hoist var references to retain scope this.hoistVarDeclarations(); @@ -244,6 +247,30 @@ 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)) { + _.each(node.declarations, function (declar) { + declar.init = declar.init || t.identifier("undefined"); + }); + } + }); +}; + /** * If we're inside of a `For*Statement` then traverse it and check if it has one * of the following node types `ReturnStatement`, `BreakStatement`, @@ -253,22 +280,26 @@ LetScoping.prototype.getInfo = function () { * @returns {Object} */ -LetScoping.prototype.checkFor = function () { +LetScoping.prototype.checkLoop = function () { var has = { hasContinue: false, hasReturn: false, hasBreak: false, }; - traverse(this.block, function (node) { + var loopParent = this.loopParent; + + traverse(this.block, function (node, parent) { var replace; - if (t.isFunction(node) || t.isLoop(node) || t.isSwitchStatement(node)) { + if (t.isFunction(node) || t.isLoop(node)) { return false; } if (node && !node.label) { if (t.isBreakStatement(node)) { + if (t.isSwitchCase(parent)) return; + has.hasBreak = true; replace = t.returnStatement(t.literal("break")); } else if (t.isContinueStatement(node)) { diff --git a/test/fixtures/transformation/es6-let-scoping/loop-initializer-default/actual.js b/test/fixtures/transformation/es6-let-scoping/loop-initializer-default/actual.js new file mode 100644 index 0000000000..1a02be8b2e --- /dev/null +++ b/test/fixtures/transformation/es6-let-scoping/loop-initializer-default/actual.js @@ -0,0 +1,11 @@ +while (value) { + let foo; + + if (bar) { + foo = []; + } + + if (foo) { + doIt(); + } +} diff --git a/test/fixtures/transformation/es6-let-scoping/loop-initializer-default/expected.js b/test/fixtures/transformation/es6-let-scoping/loop-initializer-default/expected.js new file mode 100644 index 0000000000..5397773365 --- /dev/null +++ b/test/fixtures/transformation/es6-let-scoping/loop-initializer-default/expected.js @@ -0,0 +1,13 @@ +"use strict"; + +while (value) { + var foo = undefined; + + if (bar) { + foo = []; + } + + if (foo) { + doIt(); + } +}