[79a0317] | 1 | "use strict";
|
---|
| 2 |
|
---|
| 3 | Object.defineProperty(exports, "__esModule", {
|
---|
| 4 | value: true
|
---|
| 5 | });
|
---|
| 6 | exports.getLoopBodyBindings = getLoopBodyBindings;
|
---|
| 7 | exports.getUsageInBody = getUsageInBody;
|
---|
| 8 | exports.isVarInLoopHead = isVarInLoopHead;
|
---|
| 9 | exports.wrapLoopBody = wrapLoopBody;
|
---|
| 10 | var _core = require("@babel/core");
|
---|
| 11 | const collectLoopBodyBindingsVisitor = {
|
---|
| 12 | "Expression|Declaration|Loop"(path) {
|
---|
| 13 | path.skip();
|
---|
| 14 | },
|
---|
| 15 | Scope(path, state) {
|
---|
| 16 | if (path.isFunctionParent()) path.skip();
|
---|
| 17 | const {
|
---|
| 18 | bindings
|
---|
| 19 | } = path.scope;
|
---|
| 20 | for (const name of Object.keys(bindings)) {
|
---|
| 21 | const binding = bindings[name];
|
---|
| 22 | if (binding.kind === "let" || binding.kind === "const" || binding.kind === "hoisted") {
|
---|
| 23 | state.blockScoped.push(binding);
|
---|
| 24 | }
|
---|
| 25 | }
|
---|
| 26 | }
|
---|
| 27 | };
|
---|
| 28 | function getLoopBodyBindings(loopPath) {
|
---|
| 29 | const state = {
|
---|
| 30 | blockScoped: []
|
---|
| 31 | };
|
---|
| 32 | loopPath.traverse(collectLoopBodyBindingsVisitor, state);
|
---|
| 33 | return state.blockScoped;
|
---|
| 34 | }
|
---|
| 35 | function getUsageInBody(binding, loopPath) {
|
---|
| 36 | const seen = new WeakSet();
|
---|
| 37 | let capturedInClosure = false;
|
---|
| 38 | const constantViolations = filterMap(binding.constantViolations, path => {
|
---|
| 39 | const {
|
---|
| 40 | inBody,
|
---|
| 41 | inClosure
|
---|
| 42 | } = relativeLoopLocation(path, loopPath);
|
---|
| 43 | if (!inBody) return null;
|
---|
| 44 | capturedInClosure || (capturedInClosure = inClosure);
|
---|
| 45 | const id = path.isUpdateExpression() ? path.get("argument") : path.isAssignmentExpression() ? path.get("left") : null;
|
---|
| 46 | if (id) seen.add(id.node);
|
---|
| 47 | return id;
|
---|
| 48 | });
|
---|
| 49 | const references = filterMap(binding.referencePaths, path => {
|
---|
| 50 | if (seen.has(path.node)) return null;
|
---|
| 51 | const {
|
---|
| 52 | inBody,
|
---|
| 53 | inClosure
|
---|
| 54 | } = relativeLoopLocation(path, loopPath);
|
---|
| 55 | if (!inBody) return null;
|
---|
| 56 | capturedInClosure || (capturedInClosure = inClosure);
|
---|
| 57 | return path;
|
---|
| 58 | });
|
---|
| 59 | return {
|
---|
| 60 | capturedInClosure,
|
---|
| 61 | hasConstantViolations: constantViolations.length > 0,
|
---|
| 62 | usages: references.concat(constantViolations)
|
---|
| 63 | };
|
---|
| 64 | }
|
---|
| 65 | function relativeLoopLocation(path, loopPath) {
|
---|
| 66 | const bodyPath = loopPath.get("body");
|
---|
| 67 | let inClosure = false;
|
---|
| 68 | for (let currPath = path; currPath; currPath = currPath.parentPath) {
|
---|
| 69 | if (currPath.isFunction() || currPath.isClass() || currPath.isMethod()) {
|
---|
| 70 | inClosure = true;
|
---|
| 71 | }
|
---|
| 72 | if (currPath === bodyPath) {
|
---|
| 73 | return {
|
---|
| 74 | inBody: true,
|
---|
| 75 | inClosure
|
---|
| 76 | };
|
---|
| 77 | } else if (currPath === loopPath) {
|
---|
| 78 | return {
|
---|
| 79 | inBody: false,
|
---|
| 80 | inClosure
|
---|
| 81 | };
|
---|
| 82 | }
|
---|
| 83 | }
|
---|
| 84 | throw new Error("Internal Babel error: path is not in loop. Please report this as a bug.");
|
---|
| 85 | }
|
---|
| 86 | const collectCompletionsAndVarsVisitor = {
|
---|
| 87 | Function(path) {
|
---|
| 88 | path.skip();
|
---|
| 89 | },
|
---|
| 90 | LabeledStatement: {
|
---|
| 91 | enter({
|
---|
| 92 | node
|
---|
| 93 | }, state) {
|
---|
| 94 | state.labelsStack.push(node.label.name);
|
---|
| 95 | },
|
---|
| 96 | exit({
|
---|
| 97 | node
|
---|
| 98 | }, state) {
|
---|
| 99 | const popped = state.labelsStack.pop();
|
---|
| 100 | if (popped !== node.label.name) {
|
---|
| 101 | throw new Error("Assertion failure. Please report this bug to Babel.");
|
---|
| 102 | }
|
---|
| 103 | }
|
---|
| 104 | },
|
---|
| 105 | Loop: {
|
---|
| 106 | enter(_, state) {
|
---|
| 107 | state.labellessContinueTargets++;
|
---|
| 108 | state.labellessBreakTargets++;
|
---|
| 109 | },
|
---|
| 110 | exit(_, state) {
|
---|
| 111 | state.labellessContinueTargets--;
|
---|
| 112 | state.labellessBreakTargets--;
|
---|
| 113 | }
|
---|
| 114 | },
|
---|
| 115 | SwitchStatement: {
|
---|
| 116 | enter(_, state) {
|
---|
| 117 | state.labellessBreakTargets++;
|
---|
| 118 | },
|
---|
| 119 | exit(_, state) {
|
---|
| 120 | state.labellessBreakTargets--;
|
---|
| 121 | }
|
---|
| 122 | },
|
---|
| 123 | "BreakStatement|ContinueStatement"(path, state) {
|
---|
| 124 | const {
|
---|
| 125 | label
|
---|
| 126 | } = path.node;
|
---|
| 127 | if (label) {
|
---|
| 128 | if (state.labelsStack.includes(label.name)) return;
|
---|
| 129 | } else if (path.isBreakStatement() ? state.labellessBreakTargets > 0 : state.labellessContinueTargets > 0) {
|
---|
| 130 | return;
|
---|
| 131 | }
|
---|
| 132 | state.breaksContinues.push(path);
|
---|
| 133 | },
|
---|
| 134 | ReturnStatement(path, state) {
|
---|
| 135 | state.returns.push(path);
|
---|
| 136 | },
|
---|
| 137 | VariableDeclaration(path, state) {
|
---|
| 138 | if (path.parent === state.loopNode && isVarInLoopHead(path)) return;
|
---|
| 139 | if (path.node.kind === "var") state.vars.push(path);
|
---|
| 140 | }
|
---|
| 141 | };
|
---|
| 142 | function wrapLoopBody(loopPath, captured, updatedBindingsUsages) {
|
---|
| 143 | const loopNode = loopPath.node;
|
---|
| 144 | const state = {
|
---|
| 145 | breaksContinues: [],
|
---|
| 146 | returns: [],
|
---|
| 147 | labelsStack: [],
|
---|
| 148 | labellessBreakTargets: 0,
|
---|
| 149 | labellessContinueTargets: 0,
|
---|
| 150 | vars: [],
|
---|
| 151 | loopNode
|
---|
| 152 | };
|
---|
| 153 | loopPath.traverse(collectCompletionsAndVarsVisitor, state);
|
---|
| 154 | const callArgs = [];
|
---|
| 155 | const closureParams = [];
|
---|
| 156 | const updater = [];
|
---|
| 157 | for (const [name, updatedUsage] of updatedBindingsUsages) {
|
---|
| 158 | callArgs.push(_core.types.identifier(name));
|
---|
| 159 | const innerName = loopPath.scope.generateUid(name);
|
---|
| 160 | closureParams.push(_core.types.identifier(innerName));
|
---|
| 161 | updater.push(_core.types.assignmentExpression("=", _core.types.identifier(name), _core.types.identifier(innerName)));
|
---|
| 162 | for (const path of updatedUsage) path.replaceWith(_core.types.identifier(innerName));
|
---|
| 163 | }
|
---|
| 164 | for (const name of captured) {
|
---|
| 165 | if (updatedBindingsUsages.has(name)) continue;
|
---|
| 166 | callArgs.push(_core.types.identifier(name));
|
---|
| 167 | closureParams.push(_core.types.identifier(name));
|
---|
| 168 | }
|
---|
| 169 | const id = loopPath.scope.generateUid("loop");
|
---|
| 170 | const fn = _core.types.functionExpression(null, closureParams, _core.types.toBlock(loopNode.body));
|
---|
| 171 | let call = _core.types.callExpression(_core.types.identifier(id), callArgs);
|
---|
| 172 | const fnParent = loopPath.findParent(p => p.isFunction());
|
---|
| 173 | if (fnParent) {
|
---|
| 174 | const {
|
---|
| 175 | async,
|
---|
| 176 | generator
|
---|
| 177 | } = fnParent.node;
|
---|
| 178 | fn.async = async;
|
---|
| 179 | fn.generator = generator;
|
---|
| 180 | if (generator) call = _core.types.yieldExpression(call, true);else if (async) call = _core.types.awaitExpression(call);
|
---|
| 181 | }
|
---|
| 182 | const updaterNode = updater.length > 0 ? _core.types.expressionStatement(_core.types.sequenceExpression(updater)) : null;
|
---|
| 183 | if (updaterNode) fn.body.body.push(updaterNode);
|
---|
| 184 | const [varPath] = loopPath.insertBefore(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(id), fn)]));
|
---|
| 185 | const bodyStmts = [];
|
---|
| 186 | const varNames = [];
|
---|
| 187 | for (const varPath of state.vars) {
|
---|
| 188 | const assign = [];
|
---|
| 189 | for (const decl of varPath.node.declarations) {
|
---|
| 190 | varNames.push(...Object.keys(_core.types.getBindingIdentifiers(decl.id)));
|
---|
| 191 | if (decl.init) {
|
---|
| 192 | assign.push(_core.types.assignmentExpression("=", decl.id, decl.init));
|
---|
| 193 | } else if (_core.types.isForXStatement(varPath.parent, {
|
---|
| 194 | left: varPath.node
|
---|
| 195 | })) {
|
---|
| 196 | assign.push(decl.id);
|
---|
| 197 | }
|
---|
| 198 | }
|
---|
| 199 | if (assign.length > 0) {
|
---|
| 200 | const replacement = assign.length === 1 ? assign[0] : _core.types.sequenceExpression(assign);
|
---|
| 201 | varPath.replaceWith(replacement);
|
---|
| 202 | } else {
|
---|
| 203 | varPath.remove();
|
---|
| 204 | }
|
---|
| 205 | }
|
---|
| 206 | if (varNames.length) {
|
---|
| 207 | varPath.pushContainer("declarations", varNames.map(name => _core.types.variableDeclarator(_core.types.identifier(name))));
|
---|
| 208 | }
|
---|
| 209 | const labelNum = state.breaksContinues.length;
|
---|
| 210 | const returnNum = state.returns.length;
|
---|
| 211 | if (labelNum + returnNum === 0) {
|
---|
| 212 | bodyStmts.push(_core.types.expressionStatement(call));
|
---|
| 213 | } else if (labelNum === 1 && returnNum === 0) {
|
---|
| 214 | for (const path of state.breaksContinues) {
|
---|
| 215 | const {
|
---|
| 216 | node
|
---|
| 217 | } = path;
|
---|
| 218 | const {
|
---|
| 219 | type,
|
---|
| 220 | label
|
---|
| 221 | } = node;
|
---|
| 222 | let name = type === "BreakStatement" ? "break" : "continue";
|
---|
| 223 | if (label) name += " " + label.name;
|
---|
| 224 | path.replaceWith(_core.types.addComment(_core.types.returnStatement(_core.types.numericLiteral(1)), "trailing", " " + name, true));
|
---|
| 225 | if (updaterNode) path.insertBefore(_core.types.cloneNode(updaterNode));
|
---|
| 226 | bodyStmts.push(_core.template.statement.ast`
|
---|
| 227 | if (${call}) ${node}
|
---|
| 228 | `);
|
---|
| 229 | }
|
---|
| 230 | } else {
|
---|
| 231 | const completionId = loopPath.scope.generateUid("ret");
|
---|
| 232 | if (varPath.isVariableDeclaration()) {
|
---|
| 233 | varPath.pushContainer("declarations", [_core.types.variableDeclarator(_core.types.identifier(completionId))]);
|
---|
| 234 | bodyStmts.push(_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.identifier(completionId), call)));
|
---|
| 235 | } else {
|
---|
| 236 | bodyStmts.push(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(completionId), call)]));
|
---|
| 237 | }
|
---|
| 238 | const injected = [];
|
---|
| 239 | for (const path of state.breaksContinues) {
|
---|
| 240 | const {
|
---|
| 241 | node
|
---|
| 242 | } = path;
|
---|
| 243 | const {
|
---|
| 244 | type,
|
---|
| 245 | label
|
---|
| 246 | } = node;
|
---|
| 247 | let name = type === "BreakStatement" ? "break" : "continue";
|
---|
| 248 | if (label) name += " " + label.name;
|
---|
| 249 | let i = injected.indexOf(name);
|
---|
| 250 | const hasInjected = i !== -1;
|
---|
| 251 | if (!hasInjected) {
|
---|
| 252 | injected.push(name);
|
---|
| 253 | i = injected.length - 1;
|
---|
| 254 | }
|
---|
| 255 | path.replaceWith(_core.types.addComment(_core.types.returnStatement(_core.types.numericLiteral(i)), "trailing", " " + name, true));
|
---|
| 256 | if (updaterNode) path.insertBefore(_core.types.cloneNode(updaterNode));
|
---|
| 257 | if (hasInjected) continue;
|
---|
| 258 | bodyStmts.push(_core.template.statement.ast`
|
---|
| 259 | if (${_core.types.identifier(completionId)} === ${_core.types.numericLiteral(i)}) ${node}
|
---|
| 260 | `);
|
---|
| 261 | }
|
---|
| 262 | if (returnNum) {
|
---|
| 263 | for (const path of state.returns) {
|
---|
| 264 | const arg = path.node.argument || path.scope.buildUndefinedNode();
|
---|
| 265 | path.replaceWith(_core.template.statement.ast`
|
---|
| 266 | return { v: ${arg} };
|
---|
| 267 | `);
|
---|
| 268 | }
|
---|
| 269 | bodyStmts.push(_core.template.statement.ast`
|
---|
| 270 | if (${_core.types.identifier(completionId)}) return ${_core.types.identifier(completionId)}.v;
|
---|
| 271 | `);
|
---|
| 272 | }
|
---|
| 273 | }
|
---|
| 274 | loopNode.body = _core.types.blockStatement(bodyStmts);
|
---|
| 275 | return varPath;
|
---|
| 276 | }
|
---|
| 277 | function isVarInLoopHead(path) {
|
---|
| 278 | if (_core.types.isForStatement(path.parent)) return path.key === "init";
|
---|
| 279 | if (_core.types.isForXStatement(path.parent)) return path.key === "left";
|
---|
| 280 | return false;
|
---|
| 281 | }
|
---|
| 282 | function filterMap(list, fn) {
|
---|
| 283 | const result = [];
|
---|
| 284 | for (const item of list) {
|
---|
| 285 | const mapped = fn(item);
|
---|
| 286 | if (mapped) result.push(mapped);
|
---|
| 287 | }
|
---|
| 288 | return result;
|
---|
| 289 | }
|
---|
| 290 |
|
---|
| 291 | //# sourceMappingURL=loop.js.map
|
---|