loop.js 9.7 KB

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