You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
649 lines
18 KiB
649 lines
18 KiB
"use strict"; |
|
|
|
exports.__esModule = true; |
|
|
|
var _symbol = require("babel-runtime/core-js/symbol"); |
|
|
|
var _symbol2 = _interopRequireDefault(_symbol); |
|
|
|
var _create = require("babel-runtime/core-js/object/create"); |
|
|
|
var _create2 = _interopRequireDefault(_create); |
|
|
|
var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck"); |
|
|
|
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); |
|
|
|
exports.default = function () { |
|
return { |
|
visitor: { |
|
VariableDeclaration: function VariableDeclaration(path, file) { |
|
var node = path.node, |
|
parent = path.parent, |
|
scope = path.scope; |
|
|
|
if (!isBlockScoped(node)) return; |
|
convertBlockScopedToVar(path, null, parent, scope, true); |
|
|
|
if (node._tdzThis) { |
|
var nodes = [node]; |
|
|
|
for (var i = 0; i < node.declarations.length; i++) { |
|
var decl = node.declarations[i]; |
|
if (decl.init) { |
|
var assign = t.assignmentExpression("=", decl.id, decl.init); |
|
assign._ignoreBlockScopingTDZ = true; |
|
nodes.push(t.expressionStatement(assign)); |
|
} |
|
decl.init = file.addHelper("temporalUndefined"); |
|
} |
|
|
|
node._blockHoist = 2; |
|
|
|
if (path.isCompletionRecord()) { |
|
nodes.push(t.expressionStatement(scope.buildUndefinedNode())); |
|
} |
|
|
|
path.replaceWithMultiple(nodes); |
|
} |
|
}, |
|
Loop: function Loop(path, file) { |
|
var node = path.node, |
|
parent = path.parent, |
|
scope = path.scope; |
|
|
|
t.ensureBlock(node); |
|
var blockScoping = new BlockScoping(path, path.get("body"), parent, scope, file); |
|
var replace = blockScoping.run(); |
|
if (replace) path.replaceWith(replace); |
|
}, |
|
CatchClause: function CatchClause(path, file) { |
|
var parent = path.parent, |
|
scope = path.scope; |
|
|
|
var blockScoping = new BlockScoping(null, path.get("body"), parent, scope, file); |
|
blockScoping.run(); |
|
}, |
|
"BlockStatement|SwitchStatement|Program": function BlockStatementSwitchStatementProgram(path, file) { |
|
if (!ignoreBlock(path)) { |
|
var blockScoping = new BlockScoping(null, path, path.parent, path.scope, file); |
|
blockScoping.run(); |
|
} |
|
} |
|
} |
|
}; |
|
}; |
|
|
|
var _babelTraverse = require("babel-traverse"); |
|
|
|
var _babelTraverse2 = _interopRequireDefault(_babelTraverse); |
|
|
|
var _tdz = require("./tdz"); |
|
|
|
var _babelTypes = require("babel-types"); |
|
|
|
var t = _interopRequireWildcard(_babelTypes); |
|
|
|
var _values = require("lodash/values"); |
|
|
|
var _values2 = _interopRequireDefault(_values); |
|
|
|
var _extend = require("lodash/extend"); |
|
|
|
var _extend2 = _interopRequireDefault(_extend); |
|
|
|
var _babelTemplate = require("babel-template"); |
|
|
|
var _babelTemplate2 = _interopRequireDefault(_babelTemplate); |
|
|
|
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } |
|
|
|
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
|
|
|
function ignoreBlock(path) { |
|
return t.isLoop(path.parent) || t.isCatchClause(path.parent); |
|
} |
|
|
|
var buildRetCheck = (0, _babelTemplate2.default)("\n if (typeof RETURN === \"object\") return RETURN.v;\n"); |
|
|
|
function isBlockScoped(node) { |
|
if (!t.isVariableDeclaration(node)) return false; |
|
if (node[t.BLOCK_SCOPED_SYMBOL]) return true; |
|
if (node.kind !== "let" && node.kind !== "const") return false; |
|
return true; |
|
} |
|
|
|
function convertBlockScopedToVar(path, node, parent, scope) { |
|
var moveBindingsToParent = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false; |
|
|
|
if (!node) { |
|
node = path.node; |
|
} |
|
|
|
if (!t.isFor(parent)) { |
|
for (var i = 0; i < node.declarations.length; i++) { |
|
var declar = node.declarations[i]; |
|
declar.init = declar.init || scope.buildUndefinedNode(); |
|
} |
|
} |
|
|
|
node[t.BLOCK_SCOPED_SYMBOL] = true; |
|
node.kind = "var"; |
|
|
|
if (moveBindingsToParent) { |
|
var parentScope = scope.getFunctionParent(); |
|
var ids = path.getBindingIdentifiers(); |
|
for (var name in ids) { |
|
var binding = scope.getOwnBinding(name); |
|
if (binding) binding.kind = "var"; |
|
scope.moveBindingTo(name, parentScope); |
|
} |
|
} |
|
} |
|
|
|
function isVar(node) { |
|
return t.isVariableDeclaration(node, { kind: "var" }) && !isBlockScoped(node); |
|
} |
|
|
|
var letReferenceBlockVisitor = _babelTraverse2.default.visitors.merge([{ |
|
Loop: { |
|
enter: function enter(path, state) { |
|
state.loopDepth++; |
|
}, |
|
exit: function exit(path, state) { |
|
state.loopDepth--; |
|
} |
|
}, |
|
Function: function Function(path, state) { |
|
if (state.loopDepth > 0) { |
|
path.traverse(letReferenceFunctionVisitor, state); |
|
} |
|
return path.skip(); |
|
} |
|
}, _tdz.visitor]); |
|
|
|
var letReferenceFunctionVisitor = _babelTraverse2.default.visitors.merge([{ |
|
ReferencedIdentifier: function ReferencedIdentifier(path, state) { |
|
var ref = state.letReferences[path.node.name]; |
|
|
|
if (!ref) return; |
|
|
|
var localBinding = path.scope.getBindingIdentifier(path.node.name); |
|
if (localBinding && localBinding !== ref) return; |
|
|
|
state.closurify = true; |
|
} |
|
}, _tdz.visitor]); |
|
|
|
var hoistVarDeclarationsVisitor = { |
|
enter: function enter(path, self) { |
|
var node = path.node, |
|
parent = path.parent; |
|
|
|
|
|
if (path.isForStatement()) { |
|
if (isVar(node.init, node)) { |
|
var nodes = self.pushDeclar(node.init); |
|
if (nodes.length === 1) { |
|
node.init = nodes[0]; |
|
} else { |
|
node.init = t.sequenceExpression(nodes); |
|
} |
|
} |
|
} else if (path.isFor()) { |
|
if (isVar(node.left, node)) { |
|
self.pushDeclar(node.left); |
|
node.left = node.left.declarations[0].id; |
|
} |
|
} else if (isVar(node, parent)) { |
|
path.replaceWithMultiple(self.pushDeclar(node).map(function (expr) { |
|
return t.expressionStatement(expr); |
|
})); |
|
} else if (path.isFunction()) { |
|
return path.skip(); |
|
} |
|
} |
|
}; |
|
|
|
var loopLabelVisitor = { |
|
LabeledStatement: function LabeledStatement(_ref, state) { |
|
var node = _ref.node; |
|
|
|
state.innerLabels.push(node.label.name); |
|
} |
|
}; |
|
|
|
var continuationVisitor = { |
|
enter: function enter(path, state) { |
|
if (path.isAssignmentExpression() || path.isUpdateExpression()) { |
|
var bindings = path.getBindingIdentifiers(); |
|
for (var name in bindings) { |
|
if (state.outsideReferences[name] !== path.scope.getBindingIdentifier(name)) continue; |
|
state.reassignments[name] = true; |
|
} |
|
} |
|
} |
|
}; |
|
|
|
function loopNodeTo(node) { |
|
if (t.isBreakStatement(node)) { |
|
return "break"; |
|
} else if (t.isContinueStatement(node)) { |
|
return "continue"; |
|
} |
|
} |
|
|
|
var loopVisitor = { |
|
Loop: function Loop(path, state) { |
|
var oldIgnoreLabeless = state.ignoreLabeless; |
|
state.ignoreLabeless = true; |
|
path.traverse(loopVisitor, state); |
|
state.ignoreLabeless = oldIgnoreLabeless; |
|
path.skip(); |
|
}, |
|
Function: function Function(path) { |
|
path.skip(); |
|
}, |
|
SwitchCase: function SwitchCase(path, state) { |
|
var oldInSwitchCase = state.inSwitchCase; |
|
state.inSwitchCase = true; |
|
path.traverse(loopVisitor, state); |
|
state.inSwitchCase = oldInSwitchCase; |
|
path.skip(); |
|
}, |
|
"BreakStatement|ContinueStatement|ReturnStatement": function BreakStatementContinueStatementReturnStatement(path, state) { |
|
var node = path.node, |
|
parent = path.parent, |
|
scope = path.scope; |
|
|
|
if (node[this.LOOP_IGNORE]) return; |
|
|
|
var replace = void 0; |
|
var loopText = loopNodeTo(node); |
|
|
|
if (loopText) { |
|
if (node.label) { |
|
if (state.innerLabels.indexOf(node.label.name) >= 0) { |
|
return; |
|
} |
|
|
|
loopText = loopText + "|" + node.label.name; |
|
} else { |
|
if (state.ignoreLabeless) return; |
|
|
|
if (state.inSwitchCase) return; |
|
|
|
if (t.isBreakStatement(node) && t.isSwitchCase(parent)) return; |
|
} |
|
|
|
state.hasBreakContinue = true; |
|
state.map[loopText] = node; |
|
replace = t.stringLiteral(loopText); |
|
} |
|
|
|
if (path.isReturnStatement()) { |
|
state.hasReturn = true; |
|
replace = t.objectExpression([t.objectProperty(t.identifier("v"), node.argument || scope.buildUndefinedNode())]); |
|
} |
|
|
|
if (replace) { |
|
replace = t.returnStatement(replace); |
|
replace[this.LOOP_IGNORE] = true; |
|
path.skip(); |
|
path.replaceWith(t.inherits(replace, node)); |
|
} |
|
} |
|
}; |
|
|
|
var BlockScoping = function () { |
|
function BlockScoping(loopPath, blockPath, parent, scope, file) { |
|
(0, _classCallCheck3.default)(this, BlockScoping); |
|
|
|
this.parent = parent; |
|
this.scope = scope; |
|
this.file = file; |
|
|
|
this.blockPath = blockPath; |
|
this.block = blockPath.node; |
|
|
|
this.outsideLetReferences = (0, _create2.default)(null); |
|
this.hasLetReferences = false; |
|
this.letReferences = (0, _create2.default)(null); |
|
this.body = []; |
|
|
|
if (loopPath) { |
|
this.loopParent = loopPath.parent; |
|
this.loopLabel = t.isLabeledStatement(this.loopParent) && this.loopParent.label; |
|
this.loopPath = loopPath; |
|
this.loop = loopPath.node; |
|
} |
|
} |
|
|
|
BlockScoping.prototype.run = function run() { |
|
var block = this.block; |
|
if (block._letDone) return; |
|
block._letDone = true; |
|
|
|
var needsClosure = this.getLetReferences(); |
|
|
|
if (t.isFunction(this.parent) || t.isProgram(this.block)) { |
|
this.updateScopeInfo(); |
|
return; |
|
} |
|
|
|
if (!this.hasLetReferences) return; |
|
|
|
if (needsClosure) { |
|
this.wrapClosure(); |
|
} else { |
|
this.remap(); |
|
} |
|
|
|
this.updateScopeInfo(needsClosure); |
|
|
|
if (this.loopLabel && !t.isLabeledStatement(this.loopParent)) { |
|
return t.labeledStatement(this.loopLabel, this.loop); |
|
} |
|
}; |
|
|
|
BlockScoping.prototype.updateScopeInfo = function updateScopeInfo(wrappedInClosure) { |
|
var scope = this.scope; |
|
var parentScope = scope.getFunctionParent(); |
|
var letRefs = this.letReferences; |
|
|
|
for (var key in letRefs) { |
|
var ref = letRefs[key]; |
|
var binding = scope.getBinding(ref.name); |
|
if (!binding) continue; |
|
if (binding.kind === "let" || binding.kind === "const") { |
|
binding.kind = "var"; |
|
|
|
if (wrappedInClosure) { |
|
scope.removeBinding(ref.name); |
|
} else { |
|
scope.moveBindingTo(ref.name, parentScope); |
|
} |
|
} |
|
} |
|
}; |
|
|
|
BlockScoping.prototype.remap = function remap() { |
|
var letRefs = this.letReferences; |
|
var scope = this.scope; |
|
|
|
for (var key in letRefs) { |
|
var ref = letRefs[key]; |
|
|
|
if (scope.parentHasBinding(key) || scope.hasGlobal(key)) { |
|
if (scope.hasOwnBinding(key)) scope.rename(ref.name); |
|
|
|
if (this.blockPath.scope.hasOwnBinding(key)) this.blockPath.scope.rename(ref.name); |
|
} |
|
} |
|
}; |
|
|
|
BlockScoping.prototype.wrapClosure = function wrapClosure() { |
|
if (this.file.opts.throwIfClosureRequired) { |
|
throw this.blockPath.buildCodeFrameError("Compiling let/const in this block would add a closure " + "(throwIfClosureRequired)."); |
|
} |
|
var block = this.block; |
|
|
|
var outsideRefs = this.outsideLetReferences; |
|
|
|
if (this.loop) { |
|
for (var name in outsideRefs) { |
|
var id = outsideRefs[name]; |
|
|
|
if (this.scope.hasGlobal(id.name) || this.scope.parentHasBinding(id.name)) { |
|
delete outsideRefs[id.name]; |
|
delete this.letReferences[id.name]; |
|
|
|
this.scope.rename(id.name); |
|
|
|
this.letReferences[id.name] = id; |
|
outsideRefs[id.name] = id; |
|
} |
|
} |
|
} |
|
|
|
this.has = this.checkLoop(); |
|
|
|
this.hoistVarDeclarations(); |
|
|
|
var params = (0, _values2.default)(outsideRefs); |
|
var args = (0, _values2.default)(outsideRefs); |
|
|
|
var isSwitch = this.blockPath.isSwitchStatement(); |
|
|
|
var fn = t.functionExpression(null, params, t.blockStatement(isSwitch ? [block] : block.body)); |
|
fn.shadow = true; |
|
|
|
this.addContinuations(fn); |
|
|
|
var ref = fn; |
|
|
|
if (this.loop) { |
|
ref = this.scope.generateUidIdentifier("loop"); |
|
this.loopPath.insertBefore(t.variableDeclaration("var", [t.variableDeclarator(ref, fn)])); |
|
} |
|
|
|
var call = t.callExpression(ref, args); |
|
var ret = this.scope.generateUidIdentifier("ret"); |
|
|
|
var hasYield = _babelTraverse2.default.hasType(fn.body, this.scope, "YieldExpression", t.FUNCTION_TYPES); |
|
if (hasYield) { |
|
fn.generator = true; |
|
call = t.yieldExpression(call, true); |
|
} |
|
|
|
var hasAsync = _babelTraverse2.default.hasType(fn.body, this.scope, "AwaitExpression", t.FUNCTION_TYPES); |
|
if (hasAsync) { |
|
fn.async = true; |
|
call = t.awaitExpression(call); |
|
} |
|
|
|
this.buildClosure(ret, call); |
|
|
|
if (isSwitch) this.blockPath.replaceWithMultiple(this.body);else block.body = this.body; |
|
}; |
|
|
|
BlockScoping.prototype.buildClosure = function buildClosure(ret, call) { |
|
var has = this.has; |
|
if (has.hasReturn || has.hasBreakContinue) { |
|
this.buildHas(ret, call); |
|
} else { |
|
this.body.push(t.expressionStatement(call)); |
|
} |
|
}; |
|
|
|
BlockScoping.prototype.addContinuations = function addContinuations(fn) { |
|
var state = { |
|
reassignments: {}, |
|
outsideReferences: this.outsideLetReferences |
|
}; |
|
|
|
this.scope.traverse(fn, continuationVisitor, state); |
|
|
|
for (var i = 0; i < fn.params.length; i++) { |
|
var param = fn.params[i]; |
|
if (!state.reassignments[param.name]) continue; |
|
|
|
var newParam = this.scope.generateUidIdentifier(param.name); |
|
fn.params[i] = newParam; |
|
|
|
this.scope.rename(param.name, newParam.name, fn); |
|
|
|
fn.body.body.push(t.expressionStatement(t.assignmentExpression("=", param, newParam))); |
|
} |
|
}; |
|
|
|
BlockScoping.prototype.getLetReferences = function getLetReferences() { |
|
var _this = this; |
|
|
|
var block = this.block; |
|
|
|
var declarators = []; |
|
|
|
if (this.loop) { |
|
var init = this.loop.left || this.loop.init; |
|
if (isBlockScoped(init)) { |
|
declarators.push(init); |
|
(0, _extend2.default)(this.outsideLetReferences, t.getBindingIdentifiers(init)); |
|
} |
|
} |
|
|
|
var addDeclarationsFromChild = function addDeclarationsFromChild(path, node) { |
|
node = node || path.node; |
|
if (t.isClassDeclaration(node) || t.isFunctionDeclaration(node) || isBlockScoped(node)) { |
|
if (isBlockScoped(node)) { |
|
convertBlockScopedToVar(path, node, block, _this.scope); |
|
} |
|
declarators = declarators.concat(node.declarations || node); |
|
} |
|
if (t.isLabeledStatement(node)) { |
|
addDeclarationsFromChild(path.get("body"), node.body); |
|
} |
|
}; |
|
|
|
if (block.body) { |
|
for (var i = 0; i < block.body.length; i++) { |
|
var declarPath = this.blockPath.get("body")[i]; |
|
addDeclarationsFromChild(declarPath); |
|
} |
|
} |
|
|
|
if (block.cases) { |
|
for (var _i = 0; _i < block.cases.length; _i++) { |
|
var consequents = block.cases[_i].consequent; |
|
|
|
for (var j = 0; j < consequents.length; j++) { |
|
var _declarPath = this.blockPath.get("cases")[_i]; |
|
var declar = consequents[j]; |
|
addDeclarationsFromChild(_declarPath, declar); |
|
} |
|
} |
|
} |
|
|
|
for (var _i2 = 0; _i2 < declarators.length; _i2++) { |
|
var _declar = declarators[_i2]; |
|
|
|
var keys = t.getBindingIdentifiers(_declar, false, true); |
|
(0, _extend2.default)(this.letReferences, keys); |
|
this.hasLetReferences = true; |
|
} |
|
|
|
if (!this.hasLetReferences) return; |
|
|
|
var state = { |
|
letReferences: this.letReferences, |
|
closurify: false, |
|
file: this.file, |
|
loopDepth: 0 |
|
}; |
|
|
|
var loopOrFunctionParent = this.blockPath.find(function (path) { |
|
return path.isLoop() || path.isFunction(); |
|
}); |
|
if (loopOrFunctionParent && loopOrFunctionParent.isLoop()) { |
|
state.loopDepth++; |
|
} |
|
|
|
this.blockPath.traverse(letReferenceBlockVisitor, state); |
|
|
|
return state.closurify; |
|
}; |
|
|
|
BlockScoping.prototype.checkLoop = function checkLoop() { |
|
var state = { |
|
hasBreakContinue: false, |
|
ignoreLabeless: false, |
|
inSwitchCase: false, |
|
innerLabels: [], |
|
hasReturn: false, |
|
isLoop: !!this.loop, |
|
map: {}, |
|
LOOP_IGNORE: (0, _symbol2.default)() |
|
}; |
|
|
|
this.blockPath.traverse(loopLabelVisitor, state); |
|
this.blockPath.traverse(loopVisitor, state); |
|
|
|
return state; |
|
}; |
|
|
|
BlockScoping.prototype.hoistVarDeclarations = function hoistVarDeclarations() { |
|
this.blockPath.traverse(hoistVarDeclarationsVisitor, this); |
|
}; |
|
|
|
BlockScoping.prototype.pushDeclar = function pushDeclar(node) { |
|
var declars = []; |
|
var names = t.getBindingIdentifiers(node); |
|
for (var name in names) { |
|
declars.push(t.variableDeclarator(names[name])); |
|
} |
|
|
|
this.body.push(t.variableDeclaration(node.kind, declars)); |
|
|
|
var replace = []; |
|
|
|
for (var i = 0; i < node.declarations.length; i++) { |
|
var declar = node.declarations[i]; |
|
if (!declar.init) continue; |
|
|
|
var expr = t.assignmentExpression("=", declar.id, declar.init); |
|
replace.push(t.inherits(expr, declar)); |
|
} |
|
|
|
return replace; |
|
}; |
|
|
|
BlockScoping.prototype.buildHas = function buildHas(ret, call) { |
|
var body = this.body; |
|
|
|
body.push(t.variableDeclaration("var", [t.variableDeclarator(ret, call)])); |
|
|
|
var retCheck = void 0; |
|
var has = this.has; |
|
var cases = []; |
|
|
|
if (has.hasReturn) { |
|
retCheck = buildRetCheck({ |
|
RETURN: ret |
|
}); |
|
} |
|
|
|
if (has.hasBreakContinue) { |
|
for (var key in has.map) { |
|
cases.push(t.switchCase(t.stringLiteral(key), [has.map[key]])); |
|
} |
|
|
|
if (has.hasReturn) { |
|
cases.push(t.switchCase(null, [retCheck])); |
|
} |
|
|
|
if (cases.length === 1) { |
|
var single = cases[0]; |
|
body.push(t.ifStatement(t.binaryExpression("===", ret, single.test), single.consequent[0])); |
|
} else { |
|
if (this.loop) { |
|
for (var i = 0; i < cases.length; i++) { |
|
var caseConsequent = cases[i].consequent[0]; |
|
if (t.isBreakStatement(caseConsequent) && !caseConsequent.label) { |
|
caseConsequent.label = this.loopLabel = this.loopLabel || this.scope.generateUidIdentifier("loop"); |
|
} |
|
} |
|
} |
|
|
|
body.push(t.switchStatement(ret, cases)); |
|
} |
|
} else { |
|
if (has.hasReturn) { |
|
body.push(retCheck); |
|
} |
|
} |
|
}; |
|
|
|
return BlockScoping; |
|
}(); |
|
|
|
module.exports = exports["default"]; |