|
|
"use strict";
Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0;
var _helperPluginUtils = require("@babel/helper-plugin-utils");
var _tdz = require("./tdz");
var _core = require("@babel/core");
const DONE = new WeakSet();
var _default = (0, _helperPluginUtils.declare)((api, opts) => { api.assertVersion(7); const { throwIfClosureRequired = false, tdz: tdzEnabled = false } = opts;
if (typeof throwIfClosureRequired !== "boolean") { throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`); }
if (typeof tdzEnabled !== "boolean") { throw new Error(`.tdz must be a boolean, or undefined`); }
return { name: "transform-block-scoping", visitor: { VariableDeclaration(path) { const { node, parent, scope } = path; if (!isBlockScoped(node)) return; convertBlockScopedToVar(path, null, parent, scope, true);
if (node._tdzThis) { const nodes = [node];
for (let i = 0; i < node.declarations.length; i++) { const decl = node.declarations[i];
const assign = _core.types.assignmentExpression("=", _core.types.cloneNode(decl.id), decl.init || scope.buildUndefinedNode());
assign._ignoreBlockScopingTDZ = true; nodes.push(_core.types.expressionStatement(assign)); decl.init = this.addHelper("temporalUndefined"); }
node._blockHoist = 2;
if (path.isCompletionRecord()) { nodes.push(_core.types.expressionStatement(scope.buildUndefinedNode())); }
path.replaceWithMultiple(nodes); } },
Loop(path, state) { const { parent, scope } = path; path.ensureBlock(); const blockScoping = new BlockScoping(path, path.get("body"), parent, scope, throwIfClosureRequired, tdzEnabled, state); const replace = blockScoping.run(); if (replace) path.replaceWith(replace); },
CatchClause(path, state) { const { parent, scope } = path; const blockScoping = new BlockScoping(null, path.get("body"), parent, scope, throwIfClosureRequired, tdzEnabled, state); blockScoping.run(); },
"BlockStatement|SwitchStatement|Program"(path, state) { if (!ignoreBlock(path)) { const blockScoping = new BlockScoping(null, path, path.parent, path.scope, throwIfClosureRequired, tdzEnabled, state); blockScoping.run(); } }
} }; });
exports.default = _default;
function ignoreBlock(path) { return _core.types.isLoop(path.parent) || _core.types.isCatchClause(path.parent); }
const buildRetCheck = (0, _core.template)(`
if (typeof RETURN === "object") return RETURN.v; `);
function isBlockScoped(node) { if (!_core.types.isVariableDeclaration(node)) return false; if (node[_core.types.BLOCK_SCOPED_SYMBOL]) return true; if (node.kind !== "let" && node.kind !== "const") return false; return true; }
function isInLoop(path) { const loopOrFunctionParent = path.find(path => path.isLoop() || path.isFunction()); return loopOrFunctionParent == null ? void 0 : loopOrFunctionParent.isLoop(); }
function convertBlockScopedToVar(path, node, parent, scope, moveBindingsToParent = false) { if (!node) { node = path.node; }
if (isInLoop(path) && !_core.types.isFor(parent)) { for (let i = 0; i < node.declarations.length; i++) { const declar = node.declarations[i]; declar.init = declar.init || scope.buildUndefinedNode(); } }
node[_core.types.BLOCK_SCOPED_SYMBOL] = true; node.kind = "var";
if (moveBindingsToParent) { const parentScope = scope.getFunctionParent() || scope.getProgramParent();
for (const name of Object.keys(path.getBindingIdentifiers())) { const binding = scope.getOwnBinding(name); if (binding) binding.kind = "var"; scope.moveBindingTo(name, parentScope); } } }
function isVar(node) { return _core.types.isVariableDeclaration(node, { kind: "var" }) && !isBlockScoped(node); }
const letReferenceBlockVisitor = _core.traverse.visitors.merge([{ Loop: { enter(path, state) { state.loopDepth++; },
exit(path, state) { state.loopDepth--; }
},
Function(path, state) { if (state.loopDepth > 0) { path.traverse(letReferenceFunctionVisitor, state); } else { path.traverse(_tdz.visitor, state); }
return path.skip(); }
}, _tdz.visitor]);
const letReferenceFunctionVisitor = _core.traverse.visitors.merge([{ ReferencedIdentifier(path, state) { const ref = state.letReferences.get(path.node.name); if (!ref) return; const localBinding = path.scope.getBindingIdentifier(path.node.name); if (localBinding && localBinding !== ref) return; state.closurify = true; }
}, _tdz.visitor]);
const hoistVarDeclarationsVisitor = { enter(path, self) { const { node, parent } = path;
if (path.isForStatement()) { if (isVar(node.init, node)) { const nodes = self.pushDeclar(node.init);
if (nodes.length === 1) { node.init = nodes[0]; } else { node.init = _core.types.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(expr => _core.types.expressionStatement(expr))); } else if (path.isFunction()) { return path.skip(); } }
}; const loopLabelVisitor = { LabeledStatement({ node }, state) { state.innerLabels.push(node.label.name); }
}; const continuationVisitor = { enter(path, state) { if (path.isAssignmentExpression() || path.isUpdateExpression()) { for (const name of Object.keys(path.getBindingIdentifiers())) { if (state.outsideReferences.get(name) !== path.scope.getBindingIdentifier(name)) { continue; }
state.reassignments[name] = true; } } else if (path.isReturnStatement()) { state.returnStatements.push(path); } }
};
function loopNodeTo(node) { if (_core.types.isBreakStatement(node)) { return "break"; } else if (_core.types.isContinueStatement(node)) { return "continue"; } }
const loopVisitor = { Loop(path, state) { const oldIgnoreLabeless = state.ignoreLabeless; state.ignoreLabeless = true; path.traverse(loopVisitor, state); state.ignoreLabeless = oldIgnoreLabeless; path.skip(); },
Function(path) { path.skip(); },
SwitchCase(path, state) { const oldInSwitchCase = state.inSwitchCase; state.inSwitchCase = true; path.traverse(loopVisitor, state); state.inSwitchCase = oldInSwitchCase; path.skip(); },
"BreakStatement|ContinueStatement|ReturnStatement"(path, state) { const { node, scope } = path; if (node[this.LOOP_IGNORE]) return; let replace; let 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 (_core.types.isBreakStatement(node) && state.inSwitchCase) return; }
state.hasBreakContinue = true; state.map[loopText] = node; replace = _core.types.stringLiteral(loopText); }
if (path.isReturnStatement()) { state.hasReturn = true; replace = _core.types.objectExpression([_core.types.objectProperty(_core.types.identifier("v"), node.argument || scope.buildUndefinedNode())]); }
if (replace) { replace = _core.types.returnStatement(replace); replace[this.LOOP_IGNORE] = true; path.skip(); path.replaceWith(_core.types.inherits(replace, node)); } }
};
function isStrict(path) { return !!path.find(({ node }) => { if (_core.types.isProgram(node)) { if (node.sourceType === "module") return true; } else if (!_core.types.isBlockStatement(node)) return false;
return node.directives.some(directive => directive.value.value === "use strict"); }); }
class BlockScoping { constructor(loopPath, blockPath, parent, scope, throwIfClosureRequired, tdzEnabled, state) { this.parent = parent; this.scope = scope; this.state = state; this.throwIfClosureRequired = throwIfClosureRequired; this.tdzEnabled = tdzEnabled; this.blockPath = blockPath; this.block = blockPath.node; this.outsideLetReferences = new Map(); this.hasLetReferences = false; this.letReferences = new Map(); this.body = [];
if (loopPath) { this.loopParent = loopPath.parent; this.loopLabel = _core.types.isLabeledStatement(this.loopParent) && this.loopParent.label; this.loopPath = loopPath; this.loop = loopPath.node; } }
run() { const block = this.block; if (DONE.has(block)) return; DONE.add(block); const needsClosure = this.getLetReferences(); this.checkConstants();
if (_core.types.isFunction(this.parent) || _core.types.isProgram(this.block)) { this.updateScopeInfo(); return; }
if (!this.hasLetReferences) return;
if (needsClosure) { this.wrapClosure(); } else { this.remap(); }
this.updateScopeInfo(needsClosure);
if (this.loopLabel && !_core.types.isLabeledStatement(this.loopParent)) { return _core.types.labeledStatement(this.loopLabel, this.loop); } }
checkConstants() { const scope = this.scope; const state = this.state;
for (const name of Object.keys(scope.bindings)) { const binding = scope.bindings[name]; if (binding.kind !== "const") continue;
for (const violation of binding.constantViolations) { const readOnlyError = state.addHelper("readOnlyError");
const throwNode = _core.types.callExpression(readOnlyError, [_core.types.stringLiteral(name)]);
if (violation.isAssignmentExpression()) { violation.get("right").replaceWith(_core.types.sequenceExpression([throwNode, violation.get("right").node])); } else if (violation.isUpdateExpression()) { violation.replaceWith(_core.types.sequenceExpression([throwNode, violation.node])); } else if (violation.isForXStatement()) { violation.ensureBlock(); violation.node.body.body.unshift(_core.types.expressionStatement(throwNode)); } } } }
updateScopeInfo(wrappedInClosure) { const blockScope = this.blockPath.scope; const parentScope = blockScope.getFunctionParent() || blockScope.getProgramParent(); const letRefs = this.letReferences;
for (const key of letRefs.keys()) { const ref = letRefs.get(key); const binding = blockScope.getBinding(ref.name); if (!binding) continue;
if (binding.kind === "let" || binding.kind === "const") { binding.kind = "var";
if (wrappedInClosure) { if (blockScope.hasOwnBinding(ref.name)) { blockScope.removeBinding(ref.name); } } else { blockScope.moveBindingTo(ref.name, parentScope); } } } }
remap() { const letRefs = this.letReferences; const outsideLetRefs = this.outsideLetReferences; const scope = this.scope; const blockPathScope = this.blockPath.scope;
for (const key of letRefs.keys()) { const ref = letRefs.get(key);
if (scope.parentHasBinding(key) || scope.hasGlobal(key)) { const binding = scope.getOwnBinding(key);
if (binding) { const parentBinding = scope.parent.getOwnBinding(key);
if (binding.kind === "hoisted" && !binding.path.node.async && !binding.path.node.generator && (!parentBinding || isVar(parentBinding.path.parent)) && !isStrict(binding.path.parentPath)) { continue; }
scope.rename(ref.name); }
if (blockPathScope.hasOwnBinding(key)) { blockPathScope.rename(ref.name); } } }
for (const key of outsideLetRefs.keys()) { const ref = letRefs.get(key);
if (isInLoop(this.blockPath) && blockPathScope.hasOwnBinding(key)) { blockPathScope.rename(ref.name); } } }
wrapClosure() { if (this.throwIfClosureRequired) { throw this.blockPath.buildCodeFrameError("Compiling let/const in this block would add a closure " + "(throwIfClosureRequired)."); }
const block = this.block; const outsideRefs = this.outsideLetReferences;
if (this.loop) { for (const name of Array.from(outsideRefs.keys())) { const id = outsideRefs.get(name);
if (this.scope.hasGlobal(id.name) || this.scope.parentHasBinding(id.name)) { outsideRefs.delete(id.name); this.letReferences.delete(id.name); this.scope.rename(id.name); this.letReferences.set(id.name, id); outsideRefs.set(id.name, id); } } }
this.has = this.checkLoop(); this.hoistVarDeclarations(); const args = Array.from(outsideRefs.values(), node => _core.types.cloneNode(node)); const params = args.map(id => _core.types.cloneNode(id)); const isSwitch = this.blockPath.isSwitchStatement();
const fn = _core.types.functionExpression(null, params, _core.types.blockStatement(isSwitch ? [block] : block.body));
this.addContinuations(fn);
let call = _core.types.callExpression(_core.types.nullLiteral(), args);
let basePath = ".callee";
const hasYield = _core.traverse.hasType(fn.body, "YieldExpression", _core.types.FUNCTION_TYPES);
if (hasYield) { fn.generator = true; call = _core.types.yieldExpression(call, true); basePath = ".argument" + basePath; }
const hasAsync = _core.traverse.hasType(fn.body, "AwaitExpression", _core.types.FUNCTION_TYPES);
if (hasAsync) { fn.async = true; call = _core.types.awaitExpression(call); basePath = ".argument" + basePath; }
let placeholderPath; let index;
if (this.has.hasReturn || this.has.hasBreakContinue) { const ret = this.scope.generateUid("ret"); this.body.push(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(ret), call)])); placeholderPath = "declarations.0.init" + basePath; index = this.body.length - 1; this.buildHas(ret); } else { this.body.push(_core.types.expressionStatement(call)); placeholderPath = "expression" + basePath; index = this.body.length - 1; }
let callPath;
if (isSwitch) { const { parentPath, listKey, key } = this.blockPath; this.blockPath.replaceWithMultiple(this.body); callPath = parentPath.get(listKey)[key + index]; } else { block.body = this.body; callPath = this.blockPath.get("body")[index]; }
const placeholder = callPath.get(placeholderPath); let fnPath;
if (this.loop) { const loopId = this.scope.generateUid("loop"); const p = this.loopPath.insertBefore(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(loopId), fn)])); placeholder.replaceWith(_core.types.identifier(loopId)); fnPath = p[0].get("declarations.0.init"); } else { placeholder.replaceWith(fn); fnPath = placeholder; }
fnPath.unwrapFunctionEnvironment(); }
addContinuations(fn) { const state = { reassignments: {}, returnStatements: [], outsideReferences: this.outsideLetReferences }; this.scope.traverse(fn, continuationVisitor, state);
for (let i = 0; i < fn.params.length; i++) { const param = fn.params[i]; if (!state.reassignments[param.name]) continue; const paramName = param.name; const newParamName = this.scope.generateUid(param.name); fn.params[i] = _core.types.identifier(newParamName); this.scope.rename(paramName, newParamName, fn); state.returnStatements.forEach(returnStatement => { returnStatement.insertBefore(_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.identifier(paramName), _core.types.identifier(newParamName)))); }); fn.body.body.push(_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.identifier(paramName), _core.types.identifier(newParamName)))); } }
getLetReferences() { const block = this.block; let declarators = [];
if (this.loop) { const init = this.loop.left || this.loop.init;
if (isBlockScoped(init)) { declarators.push(init);
const names = _core.types.getBindingIdentifiers(init);
for (const name of Object.keys(names)) { this.outsideLetReferences.set(name, names[name]); } } }
const addDeclarationsFromChild = (path, node) => { node = node || path.node;
if (_core.types.isClassDeclaration(node) || _core.types.isFunctionDeclaration(node) || isBlockScoped(node)) { if (isBlockScoped(node)) { convertBlockScopedToVar(path, node, block, this.scope); }
declarators = declarators.concat(node.declarations || node); }
if (_core.types.isLabeledStatement(node)) { addDeclarationsFromChild(path.get("body"), node.body); } };
if (block.body) { const declarPaths = this.blockPath.get("body");
for (let i = 0; i < block.body.length; i++) { addDeclarationsFromChild(declarPaths[i]); } }
if (block.cases) { const declarPaths = this.blockPath.get("cases");
for (let i = 0; i < block.cases.length; i++) { const consequents = block.cases[i].consequent;
for (let j = 0; j < consequents.length; j++) { const declar = consequents[j]; addDeclarationsFromChild(declarPaths[i], declar); } } }
for (let i = 0; i < declarators.length; i++) { const declar = declarators[i];
const keys = _core.types.getBindingIdentifiers(declar, false, true);
for (const key of Object.keys(keys)) { this.letReferences.set(key, keys[key]); }
this.hasLetReferences = true; }
if (!this.hasLetReferences) return; const state = { letReferences: this.letReferences, closurify: false, loopDepth: 0, tdzEnabled: this.tdzEnabled, addHelper: name => this.state.addHelper(name) };
if (isInLoop(this.blockPath)) { state.loopDepth++; }
this.blockPath.traverse(letReferenceBlockVisitor, state); return state.closurify; }
checkLoop() { const state = { hasBreakContinue: false, ignoreLabeless: false, inSwitchCase: false, innerLabels: [], hasReturn: false, isLoop: !!this.loop, map: {}, LOOP_IGNORE: Symbol() }; this.blockPath.traverse(loopLabelVisitor, state); this.blockPath.traverse(loopVisitor, state); return state; }
hoistVarDeclarations() { this.blockPath.traverse(hoistVarDeclarationsVisitor, this); }
pushDeclar(node) { const declars = [];
const names = _core.types.getBindingIdentifiers(node);
for (const name of Object.keys(names)) { declars.push(_core.types.variableDeclarator(names[name])); }
this.body.push(_core.types.variableDeclaration(node.kind, declars)); const replace = [];
for (let i = 0; i < node.declarations.length; i++) { const declar = node.declarations[i]; if (!declar.init) continue;
const expr = _core.types.assignmentExpression("=", _core.types.cloneNode(declar.id), _core.types.cloneNode(declar.init));
replace.push(_core.types.inherits(expr, declar)); }
return replace; }
buildHas(ret) { const body = this.body; const has = this.has;
if (has.hasBreakContinue) { for (const key of Object.keys(has.map)) { body.push(_core.types.ifStatement(_core.types.binaryExpression("===", _core.types.identifier(ret), _core.types.stringLiteral(key)), has.map[key])); } }
if (has.hasReturn) { body.push(buildRetCheck({ RETURN: _core.types.identifier(ret) })); } }
}
|