diff --git a/lib/6to5/transformation/transformers/es6/block-scoping.js b/lib/6to5/transformation/transformers/es6/block-scoping.js index 9f32a185cd..a2d8144824 100644 --- a/lib/6to5/transformation/transformers/es6/block-scoping.js +++ b/lib/6to5/transformation/transformers/es6/block-scoping.js @@ -2,6 +2,7 @@ var traverse = require("../../../traverse"); var object = require("../../../helpers/object"); +var clone = require("lodash/lang/clone"); var util = require("../../../util"); var t = require("../../../types"); var values = require("lodash/object/values"); @@ -308,34 +309,57 @@ LetScoping.prototype.getLetReferences = function () { return state.closurify; }; +var loopNodeTo = function (node) { + if (t.isBreakStatement(node)) { + return "break"; + } else if (t.isContinueStatement(node)) { + return "continue"; + } +}; + var loopVisitor = { enter: function (node, parent, scope, context, state) { var replace; + if (t.isLoop(node)) { + state = clone(state); + state.ignoreLabeless = true; + traverse(node, loopVisitor, scope, state); + } + if (t.isFunction(node) || t.isLoop(node)) { return context.skip(); } - if (node && !node.label && state.isLoop) { - if (t.isBreakStatement(node)) { - if (t.isSwitchCase(parent)) return; + var loopText = loopNodeTo(node); - state.hasBreak = true; - replace = t.returnStatement(t.literal("break")); - } else if (t.isContinueStatement(node)) { - state.hasContinue = true; - replace = t.returnStatement(t.literal("continue")); + if (loopText) { + if (node.label) { + loopText = loopText + "|" + node.label.name; + } else { + // we shouldn't be dealing with this + if (state.ignoreLabeless) return; + + // break statements mean something different in this context + if (t.isBreakStatement(node) && t.isSwitchCase(parent)) return; } + + state.hasBreakContinue = true; + state.map[loopText] = node; + replace = t.literal(loopText); } if (t.isReturnStatement(node)) { state.hasReturn = true; - replace = t.returnStatement(t.objectExpression([ + replace = t.objectExpression([ t.property("init", t.identifier("v"), node.argument || t.identifier("undefined")) - ])); + ]); } - if (replace) return t.inherits(replace, node); + if (replace) { + replace = t.returnStatement(replace); + return t.inherits(replace, node); + } } }; @@ -350,10 +374,11 @@ var loopVisitor = { LetScoping.prototype.checkLoop = function () { var state = { - hasContinue: false, - hasReturn: false, - hasBreak: false, - isLoop: !!this.loopParent + hasBreakContinue: false, + ignoreLabeless: false, + hasReturn: false, + isLoop: !!this.loopParent, + map: {} }; traverse(this.block, loopVisitor, this.scope, state); @@ -422,7 +447,7 @@ LetScoping.prototype.pushDeclar = function (node) { LetScoping.prototype.build = function (ret, call) { var has = this.has; - if (has.hasReturn || has.hasBreak || has.hasContinue) { + if (has.hasReturn || has.hasBreakContinue) { this.buildHas(ret, call); } else { this.body.push(t.expressionStatement(call)); @@ -455,22 +480,14 @@ LetScoping.prototype.buildHas = function (ret, call) { }); } - if (has.hasBreak || has.hasContinue) { + if (has.hasBreakContinue) { if (!loopParent) { throw new Error("Has no loop parent but we're trying to reassign breaks " + "and continues, something is going wrong here."); } - // ensure that the parent has a label as we're building a switch and we - // need to be able to access it - var label = loopParent.label = loopParent.label || this.scope.generateUidIdentifier("loop"); - - if (has.hasBreak) { - cases.push(t.switchCase(t.literal("break"), [t.breakStatement(label)])); - } - - if (has.hasContinue) { - cases.push(t.switchCase(t.literal("continue"), [t.continueStatement(label)])); + for (var key in has.map) { + cases.push(t.switchCase(t.literal(key), [has.map[key]])); } if (has.hasReturn) { diff --git a/test/fixtures/transformation/es6-block-scoping/exec-block-scoped-2/exec.js b/test/fixtures/transformation/es6-block-scoping-exec/block-scoped-2.js similarity index 100% rename from test/fixtures/transformation/es6-block-scoping/exec-block-scoped-2/exec.js rename to test/fixtures/transformation/es6-block-scoping-exec/block-scoped-2.js diff --git a/test/fixtures/transformation/es6-block-scoping/exec-block-scoped/exec.js b/test/fixtures/transformation/es6-block-scoping-exec/block-scoped.js similarity index 100% rename from test/fixtures/transformation/es6-block-scoping/exec-block-scoped/exec.js rename to test/fixtures/transformation/es6-block-scoping-exec/block-scoped.js diff --git a/test/fixtures/transformation/es6-block-scoping/exec-collision-for/exec.js b/test/fixtures/transformation/es6-block-scoping-exec/collision-for.js similarity index 100% rename from test/fixtures/transformation/es6-block-scoping/exec-collision-for/exec.js rename to test/fixtures/transformation/es6-block-scoping-exec/collision-for.js diff --git a/test/fixtures/transformation/es6-block-scoping/exec-destructuring-defaults/exec.js b/test/fixtures/transformation/es6-block-scoping-exec/destructuring-defaults.js similarity index 100% rename from test/fixtures/transformation/es6-block-scoping/exec-destructuring-defaults/exec.js rename to test/fixtures/transformation/es6-block-scoping-exec/destructuring-defaults.js diff --git a/test/fixtures/transformation/es6-block-scoping/exec-duplicate-function-scope/exec.js b/test/fixtures/transformation/es6-block-scoping-exec/duplicate-function-scope.js similarity index 100% rename from test/fixtures/transformation/es6-block-scoping/exec-duplicate-function-scope/exec.js rename to test/fixtures/transformation/es6-block-scoping-exec/duplicate-function-scope.js diff --git a/test/fixtures/transformation/es6-block-scoping/exec-duplicate-in-upper-scope/exec.js b/test/fixtures/transformation/es6-block-scoping-exec/duplicate-in-upper-scope.js similarity index 100% rename from test/fixtures/transformation/es6-block-scoping/exec-duplicate-in-upper-scope/exec.js rename to test/fixtures/transformation/es6-block-scoping-exec/duplicate-in-upper-scope.js diff --git a/test/fixtures/transformation/es6-block-scoping/exec-for-loop-head/exec.js b/test/fixtures/transformation/es6-block-scoping-exec/for-loop-head.js similarity index 100% rename from test/fixtures/transformation/es6-block-scoping/exec-for-loop-head/exec.js rename to test/fixtures/transformation/es6-block-scoping-exec/for-loop-head.js diff --git a/test/fixtures/transformation/es6-block-scoping-exec/label.js b/test/fixtures/transformation/es6-block-scoping-exec/label.js new file mode 100644 index 0000000000..426c57a5a6 --- /dev/null +++ b/test/fixtures/transformation/es6-block-scoping-exec/label.js @@ -0,0 +1,15 @@ +var heh = []; +var nums = [1, 2, 3]; + +loop1: +for (let i in nums) { + let num = nums[i]; + heh.push(x => x * num); + if (num >= 2) { + break loop1; + } +} + +assert.equal(heh.length, 2); +assert.equal(heh[0](2), 2); +assert.equal(heh[1](4), 8); diff --git a/test/fixtures/transformation/es6-block-scoping-exec/multiple.js b/test/fixtures/transformation/es6-block-scoping-exec/multiple.js new file mode 100644 index 0000000000..34f8f1aefa --- /dev/null +++ b/test/fixtures/transformation/es6-block-scoping-exec/multiple.js @@ -0,0 +1,4 @@ +for (let i = 0, x = 2; i < 5; i++); + +assert.ok(typeof i === "undefined"); +assert.ok(typeof x === "undefined"); diff --git a/test/fixtures/transformation/es6-block-scoping-exec/nested-labels-2.js b/test/fixtures/transformation/es6-block-scoping-exec/nested-labels-2.js new file mode 100644 index 0000000000..0d9b2bde64 --- /dev/null +++ b/test/fixtures/transformation/es6-block-scoping-exec/nested-labels-2.js @@ -0,0 +1,22 @@ +(function () { + var stack = []; + + loop1: + for (let j = 0; j < 10; j++) { + for (let i = 0; i < 10; i++) { + stack.push(() => [i, j]); + break; + } + } + + assert.deepEqual(stack[0](), [0, 0]); + assert.deepEqual(stack[1](), [0, 1]); + assert.deepEqual(stack[2](), [0, 2]); + assert.deepEqual(stack[3](), [0, 3]); + assert.deepEqual(stack[4](), [0, 4]); + assert.deepEqual(stack[5](), [0, 5]); + assert.deepEqual(stack[6](), [0, 6]); + assert.deepEqual(stack[7](), [0, 7]); + assert.deepEqual(stack[8](), [0, 8]); + assert.deepEqual(stack[9](), [0, 9]); +})(); diff --git a/test/fixtures/transformation/es6-block-scoping-exec/nested-labels.js b/test/fixtures/transformation/es6-block-scoping-exec/nested-labels.js new file mode 100644 index 0000000000..d9cb24977e --- /dev/null +++ b/test/fixtures/transformation/es6-block-scoping-exec/nested-labels.js @@ -0,0 +1,22 @@ +(function () { + var stack = []; + + loop1: + for (let j = 0; j < 10; j++) { + for (let i = 0; i < 10; i++) { + stack.push(() => [i, j]); + continue loop1; + } + } + + assert.deepEqual(stack[0](), [0, 0]); + assert.deepEqual(stack[1](), [0, 1]); + assert.deepEqual(stack[2](), [0, 2]); + assert.deepEqual(stack[3](), [0, 3]); + assert.deepEqual(stack[4](), [0, 4]); + assert.deepEqual(stack[5](), [0, 5]); + assert.deepEqual(stack[6](), [0, 6]); + assert.deepEqual(stack[7](), [0, 7]); + assert.deepEqual(stack[8](), [0, 8]); + assert.deepEqual(stack[9](), [0, 9]); +})(); diff --git a/test/fixtures/transformation/es6-block-scoping/exec-switch-break/exec.js b/test/fixtures/transformation/es6-block-scoping-exec/switch-break.js similarity index 100% rename from test/fixtures/transformation/es6-block-scoping/exec-switch-break/exec.js rename to test/fixtures/transformation/es6-block-scoping-exec/switch-break.js diff --git a/test/fixtures/transformation/es6-block-scoping/for-break-continue-return/expected.js b/test/fixtures/transformation/es6-block-scoping/for-break-continue-return/expected.js index 29f844e7d1..2748ecf355 100644 --- a/test/fixtures/transformation/es6-block-scoping/for-break-continue-return/expected.js +++ b/test/fixtures/transformation/es6-block-scoping/for-break-continue-return/expected.js @@ -1,7 +1,7 @@ "use strict"; (function () { - _loop: for (var i in nums) { + for (var i in nums) { var _ret = (function (i) { fns.push(function () { return i; @@ -18,10 +18,10 @@ })(i); switch (_ret) { - case "break": - break _loop; case "continue": - continue _loop; + continue; + case "break": + break; default: if (typeof _ret === "object") return _ret.v; } diff --git a/test/fixtures/transformation/es6-block-scoping/for-break/expected.js b/test/fixtures/transformation/es6-block-scoping/for-break/expected.js index 96fc3616ef..48e8005602 100644 --- a/test/fixtures/transformation/es6-block-scoping/for-break/expected.js +++ b/test/fixtures/transformation/es6-block-scoping/for-break/expected.js @@ -1,6 +1,6 @@ "use strict"; -_loop: for (var i in nums) { +for (var i in nums) { var _ret = (function (i) { fns.push(function () { return i; @@ -8,5 +8,5 @@ _loop: for (var i in nums) { return "break"; })(i); - if (_ret === "break") break _loop; + if (_ret === "break") break; } diff --git a/test/fixtures/transformation/es6-block-scoping/for-continue/expected.js b/test/fixtures/transformation/es6-block-scoping/for-continue/expected.js index 658e48536b..1ad602d089 100644 --- a/test/fixtures/transformation/es6-block-scoping/for-continue/expected.js +++ b/test/fixtures/transformation/es6-block-scoping/for-continue/expected.js @@ -1,6 +1,6 @@ "use strict"; -_loop: for (var i in nums) { +for (var i in nums) { var _ret = (function (i) { fns.push(function () { return i; @@ -8,5 +8,5 @@ _loop: for (var i in nums) { return "continue"; })(i); - if (_ret === "continue") continue _loop; + if (_ret === "continue") continue; }