"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getLoopBodyBindings = getLoopBodyBindings; exports.getUsageInBody = getUsageInBody; exports.isVarInLoopHead = isVarInLoopHead; exports.wrapLoopBody = wrapLoopBody; var _core = require("@babel/core"); const collectLoopBodyBindingsVisitor = { "Expression|Declaration|Loop"(path) { path.skip(); }, Scope(path, state) { if (path.isFunctionParent()) path.skip(); const { bindings } = path.scope; for (const name of Object.keys(bindings)) { const binding = bindings[name]; if (binding.kind === "let" || binding.kind === "const" || binding.kind === "hoisted") { state.blockScoped.push(binding); } } } }; function getLoopBodyBindings(loopPath) { const state = { blockScoped: [] }; loopPath.traverse(collectLoopBodyBindingsVisitor, state); return state.blockScoped; } function getUsageInBody(binding, loopPath) { const seen = new WeakSet(); let capturedInClosure = false; const constantViolations = filterMap(binding.constantViolations, path => { const { inBody, inClosure } = relativeLoopLocation(path, loopPath); if (!inBody) return null; capturedInClosure || (capturedInClosure = inClosure); const id = path.isUpdateExpression() ? path.get("argument") : path.isAssignmentExpression() ? path.get("left") : null; if (id) seen.add(id.node); return id; }); const references = filterMap(binding.referencePaths, path => { if (seen.has(path.node)) return null; const { inBody, inClosure } = relativeLoopLocation(path, loopPath); if (!inBody) return null; capturedInClosure || (capturedInClosure = inClosure); return path; }); return { capturedInClosure, hasConstantViolations: constantViolations.length > 0, usages: references.concat(constantViolations) }; } function relativeLoopLocation(path, loopPath) { const bodyPath = loopPath.get("body"); let inClosure = false; for (let currPath = path; currPath; currPath = currPath.parentPath) { if (currPath.isFunction() || currPath.isClass() || currPath.isMethod()) { inClosure = true; } if (currPath === bodyPath) { return { inBody: true, inClosure }; } else if (currPath === loopPath) { return { inBody: false, inClosure }; } } throw new Error("Internal Babel error: path is not in loop. Please report this as a bug."); } const collectCompletionsAndVarsVisitor = { Function(path) { path.skip(); }, LabeledStatement: { enter({ node }, state) { state.labelsStack.push(node.label.name); }, exit({ node }, state) { const popped = state.labelsStack.pop(); if (popped !== node.label.name) { throw new Error("Assertion failure. Please report this bug to Babel."); } } }, Loop: { enter(_, state) { state.labellessContinueTargets++; state.labellessBreakTargets++; }, exit(_, state) { state.labellessContinueTargets--; state.labellessBreakTargets--; } }, SwitchStatement: { enter(_, state) { state.labellessBreakTargets++; }, exit(_, state) { state.labellessBreakTargets--; } }, "BreakStatement|ContinueStatement"(path, state) { const { label } = path.node; if (label) { if (state.labelsStack.includes(label.name)) return; } else if (path.isBreakStatement() ? state.labellessBreakTargets > 0 : state.labellessContinueTargets > 0) { return; } state.breaksContinues.push(path); }, ReturnStatement(path, state) { state.returns.push(path); }, VariableDeclaration(path, state) { if (path.parent === state.loopNode && isVarInLoopHead(path)) return; if (path.node.kind === "var") state.vars.push(path); } }; function wrapLoopBody(loopPath, captured, updatedBindingsUsages) { const loopNode = loopPath.node; const state = { breaksContinues: [], returns: [], labelsStack: [], labellessBreakTargets: 0, labellessContinueTargets: 0, vars: [], loopNode }; loopPath.traverse(collectCompletionsAndVarsVisitor, state); const callArgs = []; const closureParams = []; const updater = []; for (const [name, updatedUsage] of updatedBindingsUsages) { callArgs.push(_core.types.identifier(name)); const innerName = loopPath.scope.generateUid(name); closureParams.push(_core.types.identifier(innerName)); updater.push(_core.types.assignmentExpression("=", _core.types.identifier(name), _core.types.identifier(innerName))); for (const path of updatedUsage) path.replaceWith(_core.types.identifier(innerName)); } for (const name of captured) { if (updatedBindingsUsages.has(name)) continue; callArgs.push(_core.types.identifier(name)); closureParams.push(_core.types.identifier(name)); } const id = loopPath.scope.generateUid("loop"); const fn = _core.types.functionExpression(null, closureParams, _core.types.toBlock(loopNode.body)); let call = _core.types.callExpression(_core.types.identifier(id), callArgs); const fnParent = loopPath.findParent(p => p.isFunction()); if (fnParent) { const { async, generator } = fnParent.node; fn.async = async; fn.generator = generator; if (generator) call = _core.types.yieldExpression(call, true);else if (async) call = _core.types.awaitExpression(call); } const updaterNode = updater.length > 0 ? _core.types.expressionStatement(_core.types.sequenceExpression(updater)) : null; if (updaterNode) fn.body.body.push(updaterNode); const [varPath] = loopPath.insertBefore(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(id), fn)])); const bodyStmts = []; const varNames = []; for (const varPath of state.vars) { const assign = []; for (const decl of varPath.node.declarations) { varNames.push(...Object.keys(_core.types.getBindingIdentifiers(decl.id))); if (decl.init) { assign.push(_core.types.assignmentExpression("=", decl.id, decl.init)); } else if (_core.types.isForXStatement(varPath.parent, { left: varPath.node })) { assign.push(decl.id); } } if (assign.length > 0) { const replacement = assign.length === 1 ? assign[0] : _core.types.sequenceExpression(assign); varPath.replaceWith(replacement); } else { varPath.remove(); } } if (varNames.length) { varPath.pushContainer("declarations", varNames.map(name => _core.types.variableDeclarator(_core.types.identifier(name)))); } const labelNum = state.breaksContinues.length; const returnNum = state.returns.length; if (labelNum + returnNum === 0) { bodyStmts.push(_core.types.expressionStatement(call)); } else if (labelNum === 1 && returnNum === 0) { for (const path of state.breaksContinues) { const { node } = path; const { type, label } = node; let name = type === "BreakStatement" ? "break" : "continue"; if (label) name += " " + label.name; path.replaceWith(_core.types.addComment(_core.types.returnStatement(_core.types.numericLiteral(1)), "trailing", " " + name, true)); if (updaterNode) path.insertBefore(_core.types.cloneNode(updaterNode)); bodyStmts.push(_core.template.statement.ast` if (${call}) ${node} `); } } else { const completionId = loopPath.scope.generateUid("ret"); if (varPath.isVariableDeclaration()) { varPath.pushContainer("declarations", [_core.types.variableDeclarator(_core.types.identifier(completionId))]); bodyStmts.push(_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.identifier(completionId), call))); } else { bodyStmts.push(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(completionId), call)])); } const injected = []; for (const path of state.breaksContinues) { const { node } = path; const { type, label } = node; let name = type === "BreakStatement" ? "break" : "continue"; if (label) name += " " + label.name; let i = injected.indexOf(name); const hasInjected = i !== -1; if (!hasInjected) { injected.push(name); i = injected.length - 1; } path.replaceWith(_core.types.addComment(_core.types.returnStatement(_core.types.numericLiteral(i)), "trailing", " " + name, true)); if (updaterNode) path.insertBefore(_core.types.cloneNode(updaterNode)); if (hasInjected) continue; bodyStmts.push(_core.template.statement.ast` if (${_core.types.identifier(completionId)} === ${_core.types.numericLiteral(i)}) ${node} `); } if (returnNum) { for (const path of state.returns) { const arg = path.node.argument || path.scope.buildUndefinedNode(); path.replaceWith(_core.template.statement.ast` return { v: ${arg} }; `); } bodyStmts.push(_core.template.statement.ast` if (${_core.types.identifier(completionId)}) return ${_core.types.identifier(completionId)}.v; `); } } loopNode.body = _core.types.blockStatement(bodyStmts); return varPath; } function isVarInLoopHead(path) { if (_core.types.isForStatement(path.parent)) return path.key === "init"; if (_core.types.isForXStatement(path.parent)) return path.key === "left"; return false; } function filterMap(list, fn) { const result = []; for (const item of list) { const mapped = fn(item); if (mapped) result.push(mapped); } return result; } //# sourceMappingURL=loop.js.map