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