index.js 18 KB

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