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.
		
		
		
		
		
			
		
			
				
					268 lines
				
				8.6 KiB
			
		
		
			
		
	
	
					268 lines
				
				8.6 KiB
			| 
											3 years ago
										 | "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()) 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)); | ||
|  |       } | ||
|  |     } | ||
|  |     if (assign.length > 0) { | ||
|  |       let replacement = assign.length === 1 ? assign[0] : _core.types.sequenceExpression(assign); | ||
|  |       if (!_core.types.isForStatement(varPath.parent, { | ||
|  |         init: varPath.node | ||
|  |       }) && !_core.types.isForXStatement(varPath.parent, { | ||
|  |         left: varPath.node | ||
|  |       })) { | ||
|  |         replacement = _core.types.expressionStatement(replacement); | ||
|  |       } | ||
|  |       varPath.replaceWith(replacement); | ||
|  |     } else { | ||
|  |       varPath.remove(); | ||
|  |     } | ||
|  |   } | ||
|  |   if (varNames.length) { | ||
|  |     bodyStmts.push(_core.types.variableDeclaration("var", varNames.map(name => _core.types.variableDeclarator(_core.types.identifier(name))))); | ||
|  |   } | ||
|  |   if (state.breaksContinues.length === 0 && state.returns.length === 0) { | ||
|  |     bodyStmts.push(_core.types.expressionStatement(call)); | ||
|  |   } else { | ||
|  |     const completionId = loopPath.scope.generateUid("ret"); | ||
|  |     bodyStmts.push(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(completionId), call)])); | ||
|  |     const injected = new Set(); | ||
|  |     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.returnStatement(_core.types.stringLiteral(name))); | ||
|  |       if (updaterNode) path.insertBefore(_core.types.cloneNode(updaterNode)); | ||
|  |       if (injected.has(name)) continue; | ||
|  |       injected.add(name); | ||
|  |       bodyStmts.push(_core.template.statement.ast`
 | ||
|  |         if ( | ||
|  |           ${_core.types.identifier(completionId)} === ${_core.types.stringLiteral(name)} | ||
|  |         ) ${node} | ||
|  |       `);
 | ||
|  |     } | ||
|  |     if (state.returns.length) { | ||
|  |       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 (typeof ${_core.types.identifier(completionId)} === "object") | ||
|  |           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
 |