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
						
					
					
				| "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
 |