This commit is contained in:
@@ -6,24 +6,23 @@ var _ = require("lodash");
|
||||
|
||||
function Scope(parent, block) {
|
||||
this.parent = parent;
|
||||
this.block = block;
|
||||
this.ids = block._scopeIds = block._scopeIds || Scope.getIds(block);
|
||||
}
|
||||
|
||||
Scope.getIds = function (block) {
|
||||
var ids = [];
|
||||
var ids = {};
|
||||
|
||||
if (t.isBlockStatement(block)) {
|
||||
_.each(block.body, function (node) {
|
||||
if (t.isVariableDeclaration(node) && node.kind !== "var") {
|
||||
ids = ids.concat(t.getIds(node));
|
||||
ids = t.getIds(node, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (t.isProgram(block) || t.isFunction(block)) {
|
||||
} else if (t.isProgram(block) || t.isFunction(block)) {
|
||||
traverse(block, function (node) {
|
||||
if (t.isVariableDeclaration(node) && node.kind === "var") {
|
||||
ids = ids.concat(t.getIds(node));
|
||||
ids = t.getIds(node, true);
|
||||
} else if (t.isFunction(node)) {
|
||||
return false;
|
||||
}
|
||||
@@ -32,16 +31,33 @@ Scope.getIds = function (block) {
|
||||
|
||||
if (t.isFunction(block)) {
|
||||
_.each(block.params, function (param) {
|
||||
ids = ids.concat(t.getIds(param));
|
||||
_.merge(ids, t.getIds(param, true));
|
||||
});
|
||||
}
|
||||
|
||||
return ids;
|
||||
};
|
||||
|
||||
Scope.prototype.has = function (id, noParent) {
|
||||
if (!id) return false;
|
||||
if (_.contains(this.ids, id)) return true;
|
||||
if (noParent !== false && this.parent) return this.parent.has(id);
|
||||
return false;
|
||||
Scope.prototype.get = function (id) {
|
||||
return id && (this.getOwn(id) || this.parentGet(id));
|
||||
};
|
||||
|
||||
Scope.prototype.getOwn = function (id) {
|
||||
return _.has(this.ids, id) && this.ids[id];
|
||||
};
|
||||
|
||||
Scope.prototype.parentGet = function (id) {
|
||||
return this.parent && this.parent.get(id);
|
||||
};
|
||||
|
||||
Scope.prototype.has = function (id) {
|
||||
return id && (this.hasOwn(id) || this.parentHas(id));
|
||||
};
|
||||
|
||||
Scope.prototype.hasOwn = function (id) {
|
||||
return !!this.getOwn(id);
|
||||
};
|
||||
|
||||
Scope.prototype.parentHas = function (id) {
|
||||
return this.parent && this.parent.has(id);
|
||||
};
|
||||
|
||||
@@ -3,58 +3,276 @@ var util = require("../util");
|
||||
var t = require("../types");
|
||||
var _ = require("lodash");
|
||||
|
||||
exports.VariableDeclaration = function (node, parent, file) {
|
||||
if (node.kind !== "let") return;
|
||||
var isLet = function (node) {
|
||||
if (!t.isVariableDeclaration(node)) return false;
|
||||
if (node._let) return true;
|
||||
if (node.kind !== "let") return false;
|
||||
|
||||
node._let = true;
|
||||
node.kind = "var";
|
||||
return true;
|
||||
};
|
||||
|
||||
var ids = {};
|
||||
var isVar = function (node) {
|
||||
return t.isVariableDeclaration(node) && node.kind === "var" && !isLet(node);
|
||||
};
|
||||
|
||||
_.each(node.declarations, function (declar) {
|
||||
_.each(util.getIds(declar.id), function (id) {
|
||||
ids[id] = t.identifier(file.generateUid(id));
|
||||
exports.VariableDeclaration = function (node) {
|
||||
isLet(node);
|
||||
};
|
||||
|
||||
exports.For = function (node, parent, file, scope) {
|
||||
var init = node.left || node.init;
|
||||
if (isLet(init)) {
|
||||
t.ensureBlock(node);
|
||||
node.body._letDeclars = [init];
|
||||
}
|
||||
|
||||
if (t.isLabeledStatement(parent)) {
|
||||
// set label so `run` has access to it
|
||||
node.label = parent.label;
|
||||
}
|
||||
|
||||
run(node, node.body, parent, file, scope);
|
||||
|
||||
if (node.label && !t.isLabeledStatement(parent)) {
|
||||
// we've been given a label so let's wrap ourself
|
||||
return t.labeledStatement(node.label, node);
|
||||
}
|
||||
};
|
||||
|
||||
exports.BlockStatement = function (block, parent, file, scope) {
|
||||
if (!t.isFor(parent)) {
|
||||
run(false, block, parent, file, scope);
|
||||
}
|
||||
};
|
||||
|
||||
var noClosure = function (letDeclars, block, replacements) {
|
||||
standardiseLets(letDeclars);
|
||||
|
||||
if (_.isEmpty(replacements)) return;
|
||||
|
||||
traverse(block, function (node, parent) {
|
||||
if (!t.isIdentifier(node)) return;
|
||||
if (!t.isReferenced(node, parent)) return;
|
||||
node.name = replacements[node.name] || node.name;
|
||||
});
|
||||
};
|
||||
|
||||
var standardiseLets = function (declars) {
|
||||
_.each(declars, function (declar) {
|
||||
delete declar._let;
|
||||
});
|
||||
};
|
||||
|
||||
var getInfo = function (block, file, scope) {
|
||||
var opts = {
|
||||
outsideKeys: [],
|
||||
declarators: block._letDeclars || [],
|
||||
duplicates: {},
|
||||
keys: []
|
||||
};
|
||||
|
||||
_.each(opts.declarators, function (declar) {
|
||||
opts.declarators.push(declar);
|
||||
|
||||
var keys = t.getIds(declar);
|
||||
opts.outsideKeys = opts.outsideKeys.concat(keys);
|
||||
opts.keys = opts.keys.concat(keys);
|
||||
});
|
||||
|
||||
_.each(block.body, function (declar) {
|
||||
if (!isLet(declar)) return;
|
||||
|
||||
_.each(t.getIds(declar, true), function (id, key) {
|
||||
var has = scope.parentGet(key);
|
||||
if (has && has !== id) {
|
||||
opts.duplicates[key] = id.name = file.generateUid(key, scope);
|
||||
}
|
||||
|
||||
opts.keys.push(key);
|
||||
});
|
||||
});
|
||||
|
||||
var replaceId = function (node, parent) {
|
||||
// not an identifier so we have no use for this node
|
||||
if (node.type !== "Identifier") return;
|
||||
return opts;
|
||||
};
|
||||
|
||||
// not a let reference
|
||||
var id = ids[node.name];
|
||||
if (!id) return;
|
||||
var run = function (forParent, block, parent, file, scope) {
|
||||
if (block._letDone) return;
|
||||
block._letDone = true;
|
||||
|
||||
if (t.isReferenced(node, parent)) return id;
|
||||
var info = getInfo(block, file, scope);
|
||||
var declarators = info.declarators;
|
||||
var letKeys = info.keys;
|
||||
|
||||
//
|
||||
|
||||
if (!letKeys.length) return noClosure(declarators, block, info.duplicates);
|
||||
|
||||
//
|
||||
|
||||
var letReferences = {};
|
||||
var closurify = false;
|
||||
|
||||
traverse(block, function (node, parent, opts) {
|
||||
if (t.isFunction(node)) {
|
||||
traverse(node, function (node, parent, opts2) {
|
||||
if (!t.isIdentifier(node)) return;
|
||||
if (!t.isReferenced(node, parent)) return;
|
||||
if (opts.scope.hasOwn(node.name)) return;
|
||||
|
||||
closurify = true;
|
||||
if (!_.contains(info.outsideKeys, node.name)) return;
|
||||
if (_.has(letReferences, node.name)) return;
|
||||
|
||||
letReferences[node.name] = node;
|
||||
});
|
||||
return false;
|
||||
} else if (t.isFor(node)) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
letReferences = _.values(letReferences);
|
||||
|
||||
if (!closurify) return noClosure(declarators, block, info.duplicates);
|
||||
|
||||
//
|
||||
|
||||
var has = {
|
||||
hasContinue: false,
|
||||
hasReturn: false,
|
||||
hasBreak: false,
|
||||
};
|
||||
|
||||
var isProgram = t.isProgram(parent);
|
||||
if (forParent) {
|
||||
traverse(block, function (node) {
|
||||
var replace;
|
||||
|
||||
var replace = function (node, parent) {
|
||||
if (!isProgram && _.contains(t.FUNCTION_TYPES, node.type)) {
|
||||
var letReferences = [];
|
||||
|
||||
traverse(node, function (node, parent) {
|
||||
var id = replaceId(node, parent);
|
||||
if (id && !_.contains(letReferences, id)) letReferences.push(id);
|
||||
return id;
|
||||
});
|
||||
|
||||
if (letReferences.length) {
|
||||
if (t.isFunctionDeclaration(node)) {
|
||||
throw new Error("`FunctionDeclaration`s that use `let` and `constant` references aren't allowed outside of the root scope");
|
||||
} else {
|
||||
var func = t.functionExpression(null, letReferences, t.blockStatement([
|
||||
t.returnStatement(node)
|
||||
]));
|
||||
func._aliasFunction = true;
|
||||
return t.callExpression(func, letReferences);
|
||||
}
|
||||
} else {
|
||||
if (t.isFunction(node) || t.isFor(node)) {
|
||||
return false;
|
||||
} else if (t.isBreakStatement(node) && !node.label) {
|
||||
has.hasBreak = true;
|
||||
replace = t.returnStatement(t.literal("break"));
|
||||
} else if (t.isContinueStatement(node) && !node.label) {
|
||||
has.hasContinue = true;
|
||||
replace = t.returnStatement(t.literal("continue"));
|
||||
} else if (t.isReturnStatement(node)) {
|
||||
has.hasReturn = true;
|
||||
replace = t.returnStatement(t.objectExpression([
|
||||
t.property("init", t.identifier("v"), node.argument)
|
||||
]));
|
||||
}
|
||||
|
||||
if (replace) return t.inherits(replace, node);
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
var body = [];
|
||||
|
||||
var pushDeclar = function (node) {
|
||||
body.push(t.variableDeclaration("var", node.declarations.map(function (declar) {
|
||||
return t.variableDeclarator(declar.id);
|
||||
})));
|
||||
|
||||
var replace = [];
|
||||
|
||||
_.each(node.declarations, function (declar) {
|
||||
if (declar.init) replace.push(buildDeclarAssign(declar));
|
||||
});
|
||||
|
||||
return replace;
|
||||
};
|
||||
|
||||
var buildDeclarAssign = function (declar) {
|
||||
var expr = t.assignmentExpression("=", declar.id, declar.init);
|
||||
return t.inherits(expr, declar);
|
||||
};
|
||||
|
||||
// hoist `var` declarations
|
||||
traverse(block, function (node, parent) {
|
||||
if (t.isForStatement(node)) {
|
||||
if (isVar(node.init)) {
|
||||
node.init = t.sequenceExpression(pushDeclar(node.init));
|
||||
}
|
||||
} else if (t.isFor(node)) {
|
||||
if (isVar(node.left)) {
|
||||
body.push()
|
||||
node.left = node.left.declarations[0].id;
|
||||
}
|
||||
} else if (isVar(node)) {
|
||||
return pushDeclar(node).map(t.expressionStatement);
|
||||
} else if (t.isFunction(node)) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
standardiseLets(declarators);
|
||||
|
||||
//
|
||||
|
||||
if (t.isFunction(parent)) return;
|
||||
|
||||
//
|
||||
|
||||
var fn = t.functionExpression(null, letReferences, t.blockStatement(block.body));
|
||||
fn._aliasFunction = true;
|
||||
|
||||
var params = _.cloneDeep(letReferences);
|
||||
|
||||
_.each(params, function (param) {
|
||||
param.name = info.duplicates[param.name] || param.name;
|
||||
});
|
||||
|
||||
var call = t.callExpression(fn, params);
|
||||
|
||||
block.body = body;
|
||||
|
||||
//
|
||||
|
||||
var ret = t.identifier(file.generateUid("ret", scope));
|
||||
|
||||
var retCheck;
|
||||
|
||||
if (has.hasReturn || has.hasBreak || has.hasContinue) {
|
||||
body.push(t.variableDeclaration("var", [
|
||||
t.variableDeclarator(ret, call)
|
||||
]));
|
||||
|
||||
if (has.hasReturn) {
|
||||
retCheck = t.ifStatement(
|
||||
t.binaryExpression("===", t.unaryExpression("typeof", ret, true), t.literal("object")),
|
||||
t.returnStatement(t.memberExpression(ret, t.identifier("v")))
|
||||
);
|
||||
|
||||
if (!has.hasBreak && !has.hasContinue) {
|
||||
body.push(retCheck);
|
||||
}
|
||||
}
|
||||
|
||||
return replaceId(node, parent);
|
||||
};
|
||||
if (has.hasBreak || has.hasContinue) {
|
||||
var label = forParent.label = forParent.label || t.identifier(file.generateUid("loop", scope));
|
||||
|
||||
traverse(parent, replace);
|
||||
var cases = [];
|
||||
|
||||
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)]));
|
||||
}
|
||||
|
||||
if (has.hasReturn) {
|
||||
cases.push(t.switchCase(null, [retCheck]));
|
||||
}
|
||||
|
||||
body.push(t.switchStatement(ret, cases));
|
||||
}
|
||||
|
||||
} else {
|
||||
body.push(t.expressionStatement(call));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,9 +48,13 @@ function traverse(parent, callbacks, opts) {
|
||||
if (result != null) node = obj[key] = result;
|
||||
};
|
||||
|
||||
//
|
||||
var opts2 = _.clone(opts);
|
||||
if (t.isScope(node)) opts2.scope = new Scope(opts.scope, node);
|
||||
|
||||
// enter
|
||||
if (callbacks.enter) {
|
||||
result = callbacks.enter(node, parent, opts);
|
||||
result = callbacks.enter(node, parent, opts2);
|
||||
|
||||
// stop iteration
|
||||
if (result === false) return;
|
||||
@@ -59,13 +63,11 @@ function traverse(parent, callbacks, opts) {
|
||||
}
|
||||
|
||||
// traverse node
|
||||
var opts2 = _.clone(opts);
|
||||
if (t.isScope(node)) opts2.scope = new Scope(opts.scope, node);
|
||||
traverse(node, callbacks, opts2);
|
||||
|
||||
// exit
|
||||
if (callbacks.exit) {
|
||||
maybeReplace(callbacks.exit(node, parent, opts));
|
||||
maybeReplace(callbacks.exit(node, parent, opts2));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -94,15 +94,15 @@ t.toBlock = function (node, parent) {
|
||||
return t.blockStatement(node);
|
||||
};
|
||||
|
||||
t.getIds = function (node) {
|
||||
t.getIds = function (node, map) {
|
||||
var search = [node];
|
||||
var ids = [];
|
||||
var ids = {};
|
||||
|
||||
while (search.length) {
|
||||
var id = search.shift();
|
||||
|
||||
if (t.isIdentifier(id)) {
|
||||
ids.push(id.name);
|
||||
ids[id.name] = id;
|
||||
} else if (t.isArrayPattern(id)) {
|
||||
_.each(id.elements, function (elem) {
|
||||
search.push(elem);
|
||||
@@ -122,6 +122,7 @@ t.getIds = function (node) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!map) ids = _.keys(ids);
|
||||
return ids;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user