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.
291 lines
9.6 KiB
291 lines
9.6 KiB
"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
|
|
|