add better support for labels in the block scoping transformer and add more let scoping tests - fixes #644 and closes #608

This commit is contained in:
Sebastian McKenzie
2015-01-30 20:51:20 +11:00
parent 503a3f2e3a
commit 3f3cd2bb3a
16 changed files with 115 additions and 35 deletions

View File

@@ -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) {