index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665
  1. "use strict";
  2. exports.__esModule = true;
  3. var _symbol = require("babel-runtime/core-js/symbol");
  4. var _symbol2 = _interopRequireDefault(_symbol);
  5. var _create = require("babel-runtime/core-js/object/create");
  6. var _create2 = _interopRequireDefault(_create);
  7. var _classCallCheck2 = require("babel-runtime/helpers/classCallCheck");
  8. var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
  9. exports.default = function () {
  10. return {
  11. visitor: {
  12. VariableDeclaration: function VariableDeclaration(path, file) {
  13. var node = path.node;
  14. var parent = path.parent;
  15. var scope = path.scope;
  16. if (!isBlockScoped(node)) return;
  17. convertBlockScopedToVar(path, null, parent, scope, true);
  18. if (node._tdzThis) {
  19. var nodes = [node];
  20. for (var i = 0; i < node.declarations.length; i++) {
  21. var decl = node.declarations[i];
  22. if (decl.init) {
  23. var assign = t.assignmentExpression("=", decl.id, decl.init);
  24. assign._ignoreBlockScopingTDZ = true;
  25. nodes.push(t.expressionStatement(assign));
  26. }
  27. decl.init = file.addHelper("temporalUndefined");
  28. }
  29. node._blockHoist = 2;
  30. if (path.isCompletionRecord()) {
  31. nodes.push(t.expressionStatement(scope.buildUndefinedNode()));
  32. }
  33. path.replaceWithMultiple(nodes);
  34. }
  35. },
  36. Loop: function Loop(path, file) {
  37. var node = path.node;
  38. var parent = path.parent;
  39. var scope = path.scope;
  40. t.ensureBlock(node);
  41. var blockScoping = new BlockScoping(path, path.get("body"), parent, scope, file);
  42. var replace = blockScoping.run();
  43. if (replace) path.replaceWith(replace);
  44. },
  45. "BlockStatement|SwitchStatement|Program": function BlockStatementSwitchStatementProgram(path, file) {
  46. if (!t.isLoop(path.parent)) {
  47. var blockScoping = new BlockScoping(null, path, path.parent, path.scope, file);
  48. blockScoping.run();
  49. }
  50. }
  51. }
  52. };
  53. };
  54. var _babelTraverse = require("babel-traverse");
  55. var _babelTraverse2 = _interopRequireDefault(_babelTraverse);
  56. var _tdz = require("./tdz");
  57. var _babelTypes = require("babel-types");
  58. var t = _interopRequireWildcard(_babelTypes);
  59. var _values = require("lodash/values");
  60. var _values2 = _interopRequireDefault(_values);
  61. var _extend = require("lodash/extend");
  62. var _extend2 = _interopRequireDefault(_extend);
  63. var _babelTemplate = require("babel-template");
  64. var _babelTemplate2 = _interopRequireDefault(_babelTemplate);
  65. function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  66. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  67. var buildRetCheck = (0, _babelTemplate2.default)("\n if (typeof RETURN === \"object\") return RETURN.v;\n");
  68. function isBlockScoped(node) {
  69. if (!t.isVariableDeclaration(node)) return false;
  70. if (node[t.BLOCK_SCOPED_SYMBOL]) return true;
  71. if (node.kind !== "let" && node.kind !== "const") return false;
  72. return true;
  73. }
  74. function convertBlockScopedToVar(path, node, parent, scope) {
  75. var moveBindingsToParent = arguments.length <= 4 || arguments[4] === undefined ? false : arguments[4];
  76. if (!node) {
  77. node = path.node;
  78. }
  79. if (!t.isFor(parent)) {
  80. for (var i = 0; i < node.declarations.length; i++) {
  81. var declar = node.declarations[i];
  82. declar.init = declar.init || scope.buildUndefinedNode();
  83. }
  84. }
  85. node[t.BLOCK_SCOPED_SYMBOL] = true;
  86. node.kind = "var";
  87. if (moveBindingsToParent) {
  88. var parentScope = scope.getFunctionParent();
  89. var ids = path.getBindingIdentifiers();
  90. for (var name in ids) {
  91. var binding = scope.getOwnBinding(name);
  92. if (binding) binding.kind = "var";
  93. scope.moveBindingTo(name, parentScope);
  94. }
  95. }
  96. }
  97. function isVar(node) {
  98. return t.isVariableDeclaration(node, { kind: "var" }) && !isBlockScoped(node);
  99. }
  100. function replace(path, node, scope, remaps) {
  101. var remap = remaps[node.name];
  102. if (!remap) return;
  103. var ownBinding = scope.getBindingIdentifier(node.name);
  104. if (ownBinding === remap.binding) {
  105. scope.rename(node.name, remap.uid);
  106. } else {
  107. if (path) path.skip();
  108. }
  109. }
  110. var replaceVisitor = {
  111. ReferencedIdentifier: function ReferencedIdentifier(path, remaps) {
  112. replace(path, path.node, path.scope, remaps);
  113. },
  114. AssignmentExpression: function AssignmentExpression(path, remaps) {
  115. var ids = path.getBindingIdentifiers();
  116. for (var name in ids) {
  117. replace(null, ids[name], path.scope, remaps);
  118. }
  119. }
  120. };
  121. function traverseReplace(node, parent, scope, remaps) {
  122. if (t.isIdentifier(node)) {
  123. replace(node, parent, scope, remaps);
  124. }
  125. if (t.isAssignmentExpression(node)) {
  126. var ids = t.getBindingIdentifiers(node);
  127. for (var name in ids) {
  128. replace(ids[name], parent, scope, remaps);
  129. }
  130. }
  131. scope.traverse(node, replaceVisitor, remaps);
  132. }
  133. var letReferenceBlockVisitor = _babelTraverse2.default.visitors.merge([{
  134. Function: function Function(path, state) {
  135. path.traverse(letReferenceFunctionVisitor, state);
  136. return path.skip();
  137. }
  138. }, _tdz.visitor]);
  139. var letReferenceFunctionVisitor = _babelTraverse2.default.visitors.merge([{
  140. ReferencedIdentifier: function ReferencedIdentifier(path, state) {
  141. var ref = state.letReferences[path.node.name];
  142. if (!ref) return;
  143. var localBinding = path.scope.getBindingIdentifier(path.node.name);
  144. if (localBinding && localBinding !== ref) return;
  145. state.closurify = true;
  146. }
  147. }, _tdz.visitor]);
  148. var hoistVarDeclarationsVisitor = {
  149. enter: function enter(path, self) {
  150. var node = path.node;
  151. var parent = path.parent;
  152. if (path.isForStatement()) {
  153. if (isVar(node.init, node)) {
  154. var nodes = self.pushDeclar(node.init);
  155. if (nodes.length === 1) {
  156. node.init = nodes[0];
  157. } else {
  158. node.init = t.sequenceExpression(nodes);
  159. }
  160. }
  161. } else if (path.isFor()) {
  162. if (isVar(node.left, node)) {
  163. self.pushDeclar(node.left);
  164. node.left = node.left.declarations[0].id;
  165. }
  166. } else if (isVar(node, parent)) {
  167. path.replaceWithMultiple(self.pushDeclar(node).map(function (expr) {
  168. return t.expressionStatement(expr);
  169. }));
  170. } else if (path.isFunction()) {
  171. return path.skip();
  172. }
  173. }
  174. };
  175. var loopLabelVisitor = {
  176. LabeledStatement: function LabeledStatement(_ref, state) {
  177. var node = _ref.node;
  178. state.innerLabels.push(node.label.name);
  179. }
  180. };
  181. var continuationVisitor = {
  182. enter: function enter(path, state) {
  183. if (path.isAssignmentExpression() || path.isUpdateExpression()) {
  184. var bindings = path.getBindingIdentifiers();
  185. for (var name in bindings) {
  186. if (state.outsideReferences[name] !== path.scope.getBindingIdentifier(name)) continue;
  187. state.reassignments[name] = true;
  188. }
  189. }
  190. }
  191. };
  192. function loopNodeTo(node) {
  193. if (t.isBreakStatement(node)) {
  194. return "break";
  195. } else if (t.isContinueStatement(node)) {
  196. return "continue";
  197. }
  198. }
  199. var loopVisitor = {
  200. Loop: function Loop(path, state) {
  201. var oldIgnoreLabeless = state.ignoreLabeless;
  202. state.ignoreLabeless = true;
  203. path.traverse(loopVisitor, state);
  204. state.ignoreLabeless = oldIgnoreLabeless;
  205. path.skip();
  206. },
  207. Function: function Function(path) {
  208. path.skip();
  209. },
  210. SwitchCase: function SwitchCase(path, state) {
  211. var oldInSwitchCase = state.inSwitchCase;
  212. state.inSwitchCase = true;
  213. path.traverse(loopVisitor, state);
  214. state.inSwitchCase = oldInSwitchCase;
  215. path.skip();
  216. },
  217. "BreakStatement|ContinueStatement|ReturnStatement": function BreakStatementContinueStatementReturnStatement(path, state) {
  218. var node = path.node;
  219. var parent = path.parent;
  220. var scope = path.scope;
  221. if (node[this.LOOP_IGNORE]) return;
  222. var replace = void 0;
  223. var loopText = loopNodeTo(node);
  224. if (loopText) {
  225. if (node.label) {
  226. if (state.innerLabels.indexOf(node.label.name) >= 0) {
  227. return;
  228. }
  229. loopText = loopText + "|" + node.label.name;
  230. } else {
  231. if (state.ignoreLabeless) return;
  232. if (state.inSwitchCase) return;
  233. if (t.isBreakStatement(node) && t.isSwitchCase(parent)) return;
  234. }
  235. state.hasBreakContinue = true;
  236. state.map[loopText] = node;
  237. replace = t.stringLiteral(loopText);
  238. }
  239. if (path.isReturnStatement()) {
  240. state.hasReturn = true;
  241. replace = t.objectExpression([t.objectProperty(t.identifier("v"), node.argument || scope.buildUndefinedNode())]);
  242. }
  243. if (replace) {
  244. replace = t.returnStatement(replace);
  245. replace[this.LOOP_IGNORE] = true;
  246. path.skip();
  247. path.replaceWith(t.inherits(replace, node));
  248. }
  249. }
  250. };
  251. var BlockScoping = function () {
  252. function BlockScoping(loopPath, blockPath, parent, scope, file) {
  253. (0, _classCallCheck3.default)(this, BlockScoping);
  254. this.parent = parent;
  255. this.scope = scope;
  256. this.file = file;
  257. this.blockPath = blockPath;
  258. this.block = blockPath.node;
  259. this.outsideLetReferences = (0, _create2.default)(null);
  260. this.hasLetReferences = false;
  261. this.letReferences = (0, _create2.default)(null);
  262. this.body = [];
  263. if (loopPath) {
  264. this.loopParent = loopPath.parent;
  265. this.loopLabel = t.isLabeledStatement(this.loopParent) && this.loopParent.label;
  266. this.loopPath = loopPath;
  267. this.loop = loopPath.node;
  268. }
  269. }
  270. BlockScoping.prototype.run = function run() {
  271. var block = this.block;
  272. if (block._letDone) return;
  273. block._letDone = true;
  274. var needsClosure = this.getLetReferences();
  275. if (t.isFunction(this.parent) || t.isProgram(this.block)) {
  276. this.updateScopeInfo();
  277. return;
  278. }
  279. if (!this.hasLetReferences) return;
  280. if (needsClosure) {
  281. this.wrapClosure();
  282. } else {
  283. this.remap();
  284. }
  285. this.updateScopeInfo();
  286. if (this.loopLabel && !t.isLabeledStatement(this.loopParent)) {
  287. return t.labeledStatement(this.loopLabel, this.loop);
  288. }
  289. };
  290. BlockScoping.prototype.updateScopeInfo = function updateScopeInfo() {
  291. var scope = this.scope;
  292. var parentScope = scope.getFunctionParent();
  293. var letRefs = this.letReferences;
  294. for (var key in letRefs) {
  295. var ref = letRefs[key];
  296. var binding = scope.getBinding(ref.name);
  297. if (!binding) continue;
  298. if (binding.kind === "let" || binding.kind === "const") {
  299. binding.kind = "var";
  300. scope.moveBindingTo(ref.name, parentScope);
  301. }
  302. }
  303. };
  304. BlockScoping.prototype.remap = function remap() {
  305. var hasRemaps = false;
  306. var letRefs = this.letReferences;
  307. var scope = this.scope;
  308. var remaps = (0, _create2.default)(null);
  309. for (var key in letRefs) {
  310. var ref = letRefs[key];
  311. if (scope.parentHasBinding(key) || scope.hasGlobal(key)) {
  312. var uid = scope.generateUidIdentifier(ref.name).name;
  313. ref.name = uid;
  314. hasRemaps = true;
  315. remaps[key] = remaps[uid] = {
  316. binding: ref,
  317. uid: uid
  318. };
  319. }
  320. }
  321. if (!hasRemaps) return;
  322. var loop = this.loop;
  323. if (loop) {
  324. traverseReplace(loop.right, loop, scope, remaps);
  325. traverseReplace(loop.test, loop, scope, remaps);
  326. traverseReplace(loop.update, loop, scope, remaps);
  327. }
  328. this.blockPath.traverse(replaceVisitor, remaps);
  329. };
  330. BlockScoping.prototype.wrapClosure = function wrapClosure() {
  331. var block = this.block;
  332. var outsideRefs = this.outsideLetReferences;
  333. if (this.loop) {
  334. for (var name in outsideRefs) {
  335. var id = outsideRefs[name];
  336. if (this.scope.hasGlobal(id.name) || this.scope.parentHasBinding(id.name)) {
  337. delete outsideRefs[id.name];
  338. delete this.letReferences[id.name];
  339. this.scope.rename(id.name);
  340. this.letReferences[id.name] = id;
  341. outsideRefs[id.name] = id;
  342. }
  343. }
  344. }
  345. this.has = this.checkLoop();
  346. this.hoistVarDeclarations();
  347. var params = (0, _values2.default)(outsideRefs);
  348. var args = (0, _values2.default)(outsideRefs);
  349. var isSwitch = this.blockPath.isSwitchStatement();
  350. var fn = t.functionExpression(null, params, t.blockStatement(isSwitch ? [block] : block.body));
  351. fn.shadow = true;
  352. this.addContinuations(fn);
  353. var ref = fn;
  354. if (this.loop) {
  355. ref = this.scope.generateUidIdentifier("loop");
  356. this.loopPath.insertBefore(t.variableDeclaration("var", [t.variableDeclarator(ref, fn)]));
  357. }
  358. var call = t.callExpression(ref, args);
  359. var ret = this.scope.generateUidIdentifier("ret");
  360. var hasYield = _babelTraverse2.default.hasType(fn.body, this.scope, "YieldExpression", t.FUNCTION_TYPES);
  361. if (hasYield) {
  362. fn.generator = true;
  363. call = t.yieldExpression(call, true);
  364. }
  365. var hasAsync = _babelTraverse2.default.hasType(fn.body, this.scope, "AwaitExpression", t.FUNCTION_TYPES);
  366. if (hasAsync) {
  367. fn.async = true;
  368. call = t.awaitExpression(call);
  369. }
  370. this.buildClosure(ret, call);
  371. if (isSwitch) this.blockPath.replaceWithMultiple(this.body);else block.body = this.body;
  372. };
  373. BlockScoping.prototype.buildClosure = function buildClosure(ret, call) {
  374. var has = this.has;
  375. if (has.hasReturn || has.hasBreakContinue) {
  376. this.buildHas(ret, call);
  377. } else {
  378. this.body.push(t.expressionStatement(call));
  379. }
  380. };
  381. BlockScoping.prototype.addContinuations = function addContinuations(fn) {
  382. var state = {
  383. reassignments: {},
  384. outsideReferences: this.outsideLetReferences
  385. };
  386. this.scope.traverse(fn, continuationVisitor, state);
  387. for (var i = 0; i < fn.params.length; i++) {
  388. var param = fn.params[i];
  389. if (!state.reassignments[param.name]) continue;
  390. var newParam = this.scope.generateUidIdentifier(param.name);
  391. fn.params[i] = newParam;
  392. this.scope.rename(param.name, newParam.name, fn);
  393. fn.body.body.push(t.expressionStatement(t.assignmentExpression("=", param, newParam)));
  394. }
  395. };
  396. BlockScoping.prototype.getLetReferences = function getLetReferences() {
  397. var block = this.block;
  398. var declarators = [];
  399. if (this.loop) {
  400. var init = this.loop.left || this.loop.init;
  401. if (isBlockScoped(init)) {
  402. declarators.push(init);
  403. (0, _extend2.default)(this.outsideLetReferences, t.getBindingIdentifiers(init));
  404. }
  405. }
  406. if (block.body) {
  407. for (var i = 0; i < block.body.length; i++) {
  408. var declar = block.body[i];
  409. if (t.isClassDeclaration(declar) || t.isFunctionDeclaration(declar) || isBlockScoped(declar)) {
  410. var declarPath = this.blockPath.get("body")[i];
  411. if (isBlockScoped(declar)) {
  412. convertBlockScopedToVar(declarPath, null, block, this.scope);
  413. }
  414. declarators = declarators.concat(declar.declarations || declar);
  415. }
  416. }
  417. }
  418. if (block.cases) {
  419. for (var _i = 0; _i < block.cases.length; _i++) {
  420. var consequents = block.cases[_i].consequent;
  421. for (var j = 0; j < consequents.length; j++) {
  422. var _declar = consequents[j];
  423. if (t.isClassDeclaration(_declar) || t.isFunctionDeclaration(_declar) || isBlockScoped(_declar)) {
  424. var _declarPath = this.blockPath.get("cases")[_i];
  425. if (isBlockScoped(_declar)) {
  426. convertBlockScopedToVar(_declarPath, _declar, block, this.scope);
  427. }
  428. declarators = declarators.concat(_declar.declarations || _declar);
  429. }
  430. }
  431. }
  432. }
  433. for (var _i2 = 0; _i2 < declarators.length; _i2++) {
  434. var _declar2 = declarators[_i2];
  435. var keys = t.getBindingIdentifiers(_declar2);
  436. (0, _extend2.default)(this.letReferences, keys);
  437. this.hasLetReferences = true;
  438. }
  439. if (!this.hasLetReferences) return;
  440. var state = {
  441. letReferences: this.letReferences,
  442. closurify: false,
  443. file: this.file
  444. };
  445. this.blockPath.traverse(letReferenceBlockVisitor, state);
  446. return state.closurify;
  447. };
  448. BlockScoping.prototype.checkLoop = function checkLoop() {
  449. var state = {
  450. hasBreakContinue: false,
  451. ignoreLabeless: false,
  452. inSwitchCase: false,
  453. innerLabels: [],
  454. hasReturn: false,
  455. isLoop: !!this.loop,
  456. map: {},
  457. LOOP_IGNORE: (0, _symbol2.default)()
  458. };
  459. this.blockPath.traverse(loopLabelVisitor, state);
  460. this.blockPath.traverse(loopVisitor, state);
  461. return state;
  462. };
  463. BlockScoping.prototype.hoistVarDeclarations = function hoistVarDeclarations() {
  464. this.blockPath.traverse(hoistVarDeclarationsVisitor, this);
  465. };
  466. BlockScoping.prototype.pushDeclar = function pushDeclar(node) {
  467. var declars = [];
  468. var names = t.getBindingIdentifiers(node);
  469. for (var name in names) {
  470. declars.push(t.variableDeclarator(names[name]));
  471. }
  472. this.body.push(t.variableDeclaration(node.kind, declars));
  473. var replace = [];
  474. for (var i = 0; i < node.declarations.length; i++) {
  475. var declar = node.declarations[i];
  476. if (!declar.init) continue;
  477. var expr = t.assignmentExpression("=", declar.id, declar.init);
  478. replace.push(t.inherits(expr, declar));
  479. }
  480. return replace;
  481. };
  482. BlockScoping.prototype.buildHas = function buildHas(ret, call) {
  483. var body = this.body;
  484. body.push(t.variableDeclaration("var", [t.variableDeclarator(ret, call)]));
  485. var retCheck = void 0;
  486. var has = this.has;
  487. var cases = [];
  488. if (has.hasReturn) {
  489. retCheck = buildRetCheck({
  490. RETURN: ret
  491. });
  492. }
  493. if (has.hasBreakContinue) {
  494. for (var key in has.map) {
  495. cases.push(t.switchCase(t.stringLiteral(key), [has.map[key]]));
  496. }
  497. if (has.hasReturn) {
  498. cases.push(t.switchCase(null, [retCheck]));
  499. }
  500. if (cases.length === 1) {
  501. var single = cases[0];
  502. body.push(t.ifStatement(t.binaryExpression("===", ret, single.test), single.consequent[0]));
  503. } else {
  504. if (this.loop) {
  505. for (var i = 0; i < cases.length; i++) {
  506. var caseConsequent = cases[i].consequent[0];
  507. if (t.isBreakStatement(caseConsequent) && !caseConsequent.label) {
  508. caseConsequent.label = this.loopLabel = this.loopLabel || this.scope.generateUidIdentifier("loop");
  509. }
  510. }
  511. }
  512. body.push(t.switchStatement(ret, cases));
  513. }
  514. } else {
  515. if (has.hasReturn) {
  516. body.push(retCheck);
  517. }
  518. }
  519. };
  520. return BlockScoping;
  521. }();
  522. module.exports = exports["default"];