You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

731 lines
20 KiB

4 years ago
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _helperPluginUtils = require("@babel/helper-plugin-utils");
  7. var _tdz = require("./tdz");
  8. var _values = _interopRequireDefault(require("lodash/values"));
  9. var _extend = _interopRequireDefault(require("lodash/extend"));
  10. var _core = require("@babel/core");
  11. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  12. const DONE = new WeakSet();
  13. var _default = (0, _helperPluginUtils.declare)((api, opts) => {
  14. api.assertVersion(7);
  15. const {
  16. throwIfClosureRequired = false,
  17. tdz: tdzEnabled = false
  18. } = opts;
  19. if (typeof throwIfClosureRequired !== "boolean") {
  20. throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`);
  21. }
  22. if (typeof tdzEnabled !== "boolean") {
  23. throw new Error(`.tdz must be a boolean, or undefined`);
  24. }
  25. return {
  26. name: "transform-block-scoping",
  27. visitor: {
  28. VariableDeclaration(path) {
  29. const {
  30. node,
  31. parent,
  32. scope
  33. } = path;
  34. if (!isBlockScoped(node)) return;
  35. convertBlockScopedToVar(path, null, parent, scope, true);
  36. if (node._tdzThis) {
  37. const nodes = [node];
  38. for (let i = 0; i < node.declarations.length; i++) {
  39. const decl = node.declarations[i];
  40. const assign = _core.types.assignmentExpression("=", decl.id, decl.init || scope.buildUndefinedNode());
  41. assign._ignoreBlockScopingTDZ = true;
  42. nodes.push(_core.types.expressionStatement(assign));
  43. decl.init = this.addHelper("temporalUndefined");
  44. }
  45. node._blockHoist = 2;
  46. if (path.isCompletionRecord()) {
  47. nodes.push(_core.types.expressionStatement(scope.buildUndefinedNode()));
  48. }
  49. path.replaceWithMultiple(nodes);
  50. }
  51. },
  52. Loop(path, state) {
  53. const {
  54. parent,
  55. scope
  56. } = path;
  57. path.ensureBlock();
  58. const blockScoping = new BlockScoping(path, path.get("body"), parent, scope, throwIfClosureRequired, tdzEnabled, state);
  59. const replace = blockScoping.run();
  60. if (replace) path.replaceWith(replace);
  61. },
  62. CatchClause(path, state) {
  63. const {
  64. parent,
  65. scope
  66. } = path;
  67. const blockScoping = new BlockScoping(null, path.get("body"), parent, scope, throwIfClosureRequired, tdzEnabled, state);
  68. blockScoping.run();
  69. },
  70. "BlockStatement|SwitchStatement|Program"(path, state) {
  71. if (!ignoreBlock(path)) {
  72. const blockScoping = new BlockScoping(null, path, path.parent, path.scope, throwIfClosureRequired, tdzEnabled, state);
  73. blockScoping.run();
  74. }
  75. }
  76. }
  77. };
  78. });
  79. exports.default = _default;
  80. function ignoreBlock(path) {
  81. return _core.types.isLoop(path.parent) || _core.types.isCatchClause(path.parent);
  82. }
  83. const buildRetCheck = (0, _core.template)(`
  84. if (typeof RETURN === "object") return RETURN.v;
  85. `);
  86. function isBlockScoped(node) {
  87. if (!_core.types.isVariableDeclaration(node)) return false;
  88. if (node[_core.types.BLOCK_SCOPED_SYMBOL]) return true;
  89. if (node.kind !== "let" && node.kind !== "const") return false;
  90. return true;
  91. }
  92. function isInLoop(path) {
  93. const loopOrFunctionParent = path.find(path => path.isLoop() || path.isFunction());
  94. return loopOrFunctionParent && loopOrFunctionParent.isLoop();
  95. }
  96. function convertBlockScopedToVar(path, node, parent, scope, moveBindingsToParent = false) {
  97. if (!node) {
  98. node = path.node;
  99. }
  100. if (isInLoop(path) && !_core.types.isFor(parent)) {
  101. for (let i = 0; i < node.declarations.length; i++) {
  102. const declar = node.declarations[i];
  103. declar.init = declar.init || scope.buildUndefinedNode();
  104. }
  105. }
  106. node[_core.types.BLOCK_SCOPED_SYMBOL] = true;
  107. node.kind = "var";
  108. if (moveBindingsToParent) {
  109. const parentScope = scope.getFunctionParent() || scope.getProgramParent();
  110. for (const name of Object.keys(path.getBindingIdentifiers())) {
  111. const binding = scope.getOwnBinding(name);
  112. if (binding) binding.kind = "var";
  113. scope.moveBindingTo(name, parentScope);
  114. }
  115. }
  116. }
  117. function isVar(node) {
  118. return _core.types.isVariableDeclaration(node, {
  119. kind: "var"
  120. }) && !isBlockScoped(node);
  121. }
  122. const letReferenceBlockVisitor = _core.traverse.visitors.merge([{
  123. Loop: {
  124. enter(path, state) {
  125. state.loopDepth++;
  126. },
  127. exit(path, state) {
  128. state.loopDepth--;
  129. }
  130. },
  131. Function(path, state) {
  132. if (state.loopDepth > 0) {
  133. path.traverse(letReferenceFunctionVisitor, state);
  134. } else {
  135. path.traverse(_tdz.visitor, state);
  136. }
  137. return path.skip();
  138. }
  139. }, _tdz.visitor]);
  140. const letReferenceFunctionVisitor = _core.traverse.visitors.merge([{
  141. ReferencedIdentifier(path, state) {
  142. const ref = state.letReferences[path.node.name];
  143. if (!ref) return;
  144. const localBinding = path.scope.getBindingIdentifier(path.node.name);
  145. if (localBinding && localBinding !== ref) return;
  146. state.closurify = true;
  147. }
  148. }, _tdz.visitor]);
  149. const hoistVarDeclarationsVisitor = {
  150. enter(path, self) {
  151. const {
  152. node,
  153. parent
  154. } = path;
  155. if (path.isForStatement()) {
  156. if (isVar(node.init, node)) {
  157. const nodes = self.pushDeclar(node.init);
  158. if (nodes.length === 1) {
  159. node.init = nodes[0];
  160. } else {
  161. node.init = _core.types.sequenceExpression(nodes);
  162. }
  163. }
  164. } else if (path.isFor()) {
  165. if (isVar(node.left, node)) {
  166. self.pushDeclar(node.left);
  167. node.left = node.left.declarations[0].id;
  168. }
  169. } else if (isVar(node, parent)) {
  170. path.replaceWithMultiple(self.pushDeclar(node).map(expr => _core.types.expressionStatement(expr)));
  171. } else if (path.isFunction()) {
  172. return path.skip();
  173. }
  174. }
  175. };
  176. const loopLabelVisitor = {
  177. LabeledStatement({
  178. node
  179. }, state) {
  180. state.innerLabels.push(node.label.name);
  181. }
  182. };
  183. const continuationVisitor = {
  184. enter(path, state) {
  185. if (path.isAssignmentExpression() || path.isUpdateExpression()) {
  186. for (const name of Object.keys(path.getBindingIdentifiers())) {
  187. if (state.outsideReferences[name] !== path.scope.getBindingIdentifier(name)) {
  188. continue;
  189. }
  190. state.reassignments[name] = true;
  191. }
  192. } else if (path.isReturnStatement()) {
  193. state.returnStatements.push(path);
  194. }
  195. }
  196. };
  197. function loopNodeTo(node) {
  198. if (_core.types.isBreakStatement(node)) {
  199. return "break";
  200. } else if (_core.types.isContinueStatement(node)) {
  201. return "continue";
  202. }
  203. }
  204. const loopVisitor = {
  205. Loop(path, state) {
  206. const oldIgnoreLabeless = state.ignoreLabeless;
  207. state.ignoreLabeless = true;
  208. path.traverse(loopVisitor, state);
  209. state.ignoreLabeless = oldIgnoreLabeless;
  210. path.skip();
  211. },
  212. Function(path) {
  213. path.skip();
  214. },
  215. SwitchCase(path, state) {
  216. const oldInSwitchCase = state.inSwitchCase;
  217. state.inSwitchCase = true;
  218. path.traverse(loopVisitor, state);
  219. state.inSwitchCase = oldInSwitchCase;
  220. path.skip();
  221. },
  222. "BreakStatement|ContinueStatement|ReturnStatement"(path, state) {
  223. const {
  224. node,
  225. scope
  226. } = path;
  227. if (node[this.LOOP_IGNORE]) return;
  228. let replace;
  229. let loopText = loopNodeTo(node);
  230. if (loopText) {
  231. if (node.label) {
  232. if (state.innerLabels.indexOf(node.label.name) >= 0) {
  233. return;
  234. }
  235. loopText = `${loopText}|${node.label.name}`;
  236. } else {
  237. if (state.ignoreLabeless) return;
  238. if (_core.types.isBreakStatement(node) && state.inSwitchCase) return;
  239. }
  240. state.hasBreakContinue = true;
  241. state.map[loopText] = node;
  242. replace = _core.types.stringLiteral(loopText);
  243. }
  244. if (path.isReturnStatement()) {
  245. state.hasReturn = true;
  246. replace = _core.types.objectExpression([_core.types.objectProperty(_core.types.identifier("v"), node.argument || scope.buildUndefinedNode())]);
  247. }
  248. if (replace) {
  249. replace = _core.types.returnStatement(replace);
  250. replace[this.LOOP_IGNORE] = true;
  251. path.skip();
  252. path.replaceWith(_core.types.inherits(replace, node));
  253. }
  254. }
  255. };
  256. class BlockScoping {
  257. constructor(loopPath, blockPath, parent, scope, throwIfClosureRequired, tdzEnabled, state) {
  258. this.parent = parent;
  259. this.scope = scope;
  260. this.state = state;
  261. this.throwIfClosureRequired = throwIfClosureRequired;
  262. this.tdzEnabled = tdzEnabled;
  263. this.blockPath = blockPath;
  264. this.block = blockPath.node;
  265. this.outsideLetReferences = Object.create(null);
  266. this.hasLetReferences = false;
  267. this.letReferences = Object.create(null);
  268. this.body = [];
  269. if (loopPath) {
  270. this.loopParent = loopPath.parent;
  271. this.loopLabel = _core.types.isLabeledStatement(this.loopParent) && this.loopParent.label;
  272. this.loopPath = loopPath;
  273. this.loop = loopPath.node;
  274. }
  275. }
  276. run() {
  277. const block = this.block;
  278. if (DONE.has(block)) return;
  279. DONE.add(block);
  280. const needsClosure = this.getLetReferences();
  281. this.checkConstants();
  282. if (_core.types.isFunction(this.parent) || _core.types.isProgram(this.block)) {
  283. this.updateScopeInfo();
  284. return;
  285. }
  286. if (!this.hasLetReferences) return;
  287. if (needsClosure) {
  288. this.wrapClosure();
  289. } else {
  290. this.remap();
  291. }
  292. this.updateScopeInfo(needsClosure);
  293. if (this.loopLabel && !_core.types.isLabeledStatement(this.loopParent)) {
  294. return _core.types.labeledStatement(this.loopLabel, this.loop);
  295. }
  296. }
  297. checkConstants() {
  298. const scope = this.scope;
  299. const state = this.state;
  300. for (const name of Object.keys(scope.bindings)) {
  301. const binding = scope.bindings[name];
  302. if (binding.kind !== "const") continue;
  303. for (const violation of binding.constantViolations) {
  304. const readOnlyError = state.addHelper("readOnlyError");
  305. const throwNode = _core.types.callExpression(readOnlyError, [_core.types.stringLiteral(name)]);
  306. if (violation.isAssignmentExpression()) {
  307. violation.get("right").replaceWith(_core.types.sequenceExpression([throwNode, violation.get("right").node]));
  308. } else if (violation.isUpdateExpression()) {
  309. violation.replaceWith(_core.types.sequenceExpression([throwNode, violation.node]));
  310. } else if (violation.isForXStatement()) {
  311. violation.ensureBlock();
  312. violation.node.body.body.unshift(_core.types.expressionStatement(throwNode));
  313. }
  314. }
  315. }
  316. }
  317. updateScopeInfo(wrappedInClosure) {
  318. const blockScope = this.blockPath.scope;
  319. const parentScope = blockScope.getFunctionParent() || blockScope.getProgramParent();
  320. const letRefs = this.letReferences;
  321. for (const key of Object.keys(letRefs)) {
  322. const ref = letRefs[key];
  323. const binding = blockScope.getBinding(ref.name);
  324. if (!binding) continue;
  325. if (binding.kind === "let" || binding.kind === "const") {
  326. binding.kind = "var";
  327. if (wrappedInClosure) {
  328. if (blockScope.hasOwnBinding(ref.name)) {
  329. blockScope.removeBinding(ref.name);
  330. }
  331. } else {
  332. blockScope.moveBindingTo(ref.name, parentScope);
  333. }
  334. }
  335. }
  336. }
  337. remap() {
  338. const letRefs = this.letReferences;
  339. const outsideLetRefs = this.outsideLetReferences;
  340. const scope = this.scope;
  341. const blockPathScope = this.blockPath.scope;
  342. for (const key of Object.keys(letRefs)) {
  343. const ref = letRefs[key];
  344. if (scope.parentHasBinding(key) || scope.hasGlobal(key)) {
  345. if (scope.hasOwnBinding(key)) {
  346. scope.rename(ref.name);
  347. }
  348. if (blockPathScope.hasOwnBinding(key)) {
  349. blockPathScope.rename(ref.name);
  350. }
  351. }
  352. }
  353. for (const key of Object.keys(outsideLetRefs)) {
  354. const ref = letRefs[key];
  355. if (isInLoop(this.blockPath) && blockPathScope.hasOwnBinding(key)) {
  356. blockPathScope.rename(ref.name);
  357. }
  358. }
  359. }
  360. wrapClosure() {
  361. if (this.throwIfClosureRequired) {
  362. throw this.blockPath.buildCodeFrameError("Compiling let/const in this block would add a closure " + "(throwIfClosureRequired).");
  363. }
  364. const block = this.block;
  365. const outsideRefs = this.outsideLetReferences;
  366. if (this.loop) {
  367. for (const name of Object.keys(outsideRefs)) {
  368. const id = outsideRefs[name];
  369. if (this.scope.hasGlobal(id.name) || this.scope.parentHasBinding(id.name)) {
  370. delete outsideRefs[id.name];
  371. delete this.letReferences[id.name];
  372. this.scope.rename(id.name);
  373. this.letReferences[id.name] = id;
  374. outsideRefs[id.name] = id;
  375. }
  376. }
  377. }
  378. this.has = this.checkLoop();
  379. this.hoistVarDeclarations();
  380. const args = (0, _values.default)(outsideRefs).map(id => _core.types.cloneNode(id));
  381. const params = args.map(id => _core.types.cloneNode(id));
  382. const isSwitch = this.blockPath.isSwitchStatement();
  383. const fn = _core.types.functionExpression(null, params, _core.types.blockStatement(isSwitch ? [block] : block.body));
  384. this.addContinuations(fn);
  385. let call = _core.types.callExpression(_core.types.nullLiteral(), args);
  386. let basePath = ".callee";
  387. const hasYield = _core.traverse.hasType(fn.body, "YieldExpression", _core.types.FUNCTION_TYPES);
  388. if (hasYield) {
  389. fn.generator = true;
  390. call = _core.types.yieldExpression(call, true);
  391. basePath = ".argument" + basePath;
  392. }
  393. const hasAsync = _core.traverse.hasType(fn.body, "AwaitExpression", _core.types.FUNCTION_TYPES);
  394. if (hasAsync) {
  395. fn.async = true;
  396. call = _core.types.awaitExpression(call);
  397. basePath = ".argument" + basePath;
  398. }
  399. let placeholderPath;
  400. let index;
  401. if (this.has.hasReturn || this.has.hasBreakContinue) {
  402. const ret = this.scope.generateUid("ret");
  403. this.body.push(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(ret), call)]));
  404. placeholderPath = "declarations.0.init" + basePath;
  405. index = this.body.length - 1;
  406. this.buildHas(ret);
  407. } else {
  408. this.body.push(_core.types.expressionStatement(call));
  409. placeholderPath = "expression" + basePath;
  410. index = this.body.length - 1;
  411. }
  412. let callPath;
  413. if (isSwitch) {
  414. const {
  415. parentPath,
  416. listKey,
  417. key
  418. } = this.blockPath;
  419. this.blockPath.replaceWithMultiple(this.body);
  420. callPath = parentPath.get(listKey)[key + index];
  421. } else {
  422. block.body = this.body;
  423. callPath = this.blockPath.get("body")[index];
  424. }
  425. const placeholder = callPath.get(placeholderPath);
  426. let fnPath;
  427. if (this.loop) {
  428. const loopId = this.scope.generateUid("loop");
  429. const p = this.loopPath.insertBefore(_core.types.variableDeclaration("var", [_core.types.variableDeclarator(_core.types.identifier(loopId), fn)]));
  430. placeholder.replaceWith(_core.types.identifier(loopId));
  431. fnPath = p[0].get("declarations.0.init");
  432. } else {
  433. placeholder.replaceWith(fn);
  434. fnPath = placeholder;
  435. }
  436. fnPath.unwrapFunctionEnvironment();
  437. }
  438. addContinuations(fn) {
  439. const state = {
  440. reassignments: {},
  441. returnStatements: [],
  442. outsideReferences: this.outsideLetReferences
  443. };
  444. this.scope.traverse(fn, continuationVisitor, state);
  445. for (let i = 0; i < fn.params.length; i++) {
  446. const param = fn.params[i];
  447. if (!state.reassignments[param.name]) continue;
  448. const paramName = param.name;
  449. const newParamName = this.scope.generateUid(param.name);
  450. fn.params[i] = _core.types.identifier(newParamName);
  451. this.scope.rename(paramName, newParamName, fn);
  452. state.returnStatements.forEach(returnStatement => {
  453. returnStatement.insertBefore(_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.identifier(paramName), _core.types.identifier(newParamName))));
  454. });
  455. fn.body.body.push(_core.types.expressionStatement(_core.types.assignmentExpression("=", _core.types.identifier(paramName), _core.types.identifier(newParamName))));
  456. }
  457. }
  458. getLetReferences() {
  459. const block = this.block;
  460. let declarators = [];
  461. if (this.loop) {
  462. const init = this.loop.left || this.loop.init;
  463. if (isBlockScoped(init)) {
  464. declarators.push(init);
  465. (0, _extend.default)(this.outsideLetReferences, _core.types.getBindingIdentifiers(init));
  466. }
  467. }
  468. const addDeclarationsFromChild = (path, node) => {
  469. node = node || path.node;
  470. if (_core.types.isClassDeclaration(node) || _core.types.isFunctionDeclaration(node) || isBlockScoped(node)) {
  471. if (isBlockScoped(node)) {
  472. convertBlockScopedToVar(path, node, block, this.scope);
  473. }
  474. declarators = declarators.concat(node.declarations || node);
  475. }
  476. if (_core.types.isLabeledStatement(node)) {
  477. addDeclarationsFromChild(path.get("body"), node.body);
  478. }
  479. };
  480. if (block.body) {
  481. const declarPaths = this.blockPath.get("body");
  482. for (let i = 0; i < block.body.length; i++) {
  483. addDeclarationsFromChild(declarPaths[i]);
  484. }
  485. }
  486. if (block.cases) {
  487. const declarPaths = this.blockPath.get("cases");
  488. for (let i = 0; i < block.cases.length; i++) {
  489. const consequents = block.cases[i].consequent;
  490. for (let j = 0; j < consequents.length; j++) {
  491. const declar = consequents[j];
  492. addDeclarationsFromChild(declarPaths[i], declar);
  493. }
  494. }
  495. }
  496. for (let i = 0; i < declarators.length; i++) {
  497. const declar = declarators[i];
  498. const keys = _core.types.getBindingIdentifiers(declar, false, true);
  499. (0, _extend.default)(this.letReferences, keys);
  500. this.hasLetReferences = true;
  501. }
  502. if (!this.hasLetReferences) return;
  503. const state = {
  504. letReferences: this.letReferences,
  505. closurify: false,
  506. loopDepth: 0,
  507. tdzEnabled: this.tdzEnabled,
  508. addHelper: name => this.state.addHelper(name)
  509. };
  510. if (isInLoop(this.blockPath)) {
  511. state.loopDepth++;
  512. }
  513. this.blockPath.traverse(letReferenceBlockVisitor, state);
  514. return state.closurify;
  515. }
  516. checkLoop() {
  517. const state = {
  518. hasBreakContinue: false,
  519. ignoreLabeless: false,
  520. inSwitchCase: false,
  521. innerLabels: [],
  522. hasReturn: false,
  523. isLoop: !!this.loop,
  524. map: {},
  525. LOOP_IGNORE: Symbol()
  526. };
  527. this.blockPath.traverse(loopLabelVisitor, state);
  528. this.blockPath.traverse(loopVisitor, state);
  529. return state;
  530. }
  531. hoistVarDeclarations() {
  532. this.blockPath.traverse(hoistVarDeclarationsVisitor, this);
  533. }
  534. pushDeclar(node) {
  535. const declars = [];
  536. const names = _core.types.getBindingIdentifiers(node);
  537. for (const name of Object.keys(names)) {
  538. declars.push(_core.types.variableDeclarator(names[name]));
  539. }
  540. this.body.push(_core.types.variableDeclaration(node.kind, declars));
  541. const replace = [];
  542. for (let i = 0; i < node.declarations.length; i++) {
  543. const declar = node.declarations[i];
  544. if (!declar.init) continue;
  545. const expr = _core.types.assignmentExpression("=", _core.types.cloneNode(declar.id), _core.types.cloneNode(declar.init));
  546. replace.push(_core.types.inherits(expr, declar));
  547. }
  548. return replace;
  549. }
  550. buildHas(ret) {
  551. const body = this.body;
  552. let retCheck;
  553. const has = this.has;
  554. const cases = [];
  555. if (has.hasReturn) {
  556. retCheck = buildRetCheck({
  557. RETURN: _core.types.identifier(ret)
  558. });
  559. }
  560. if (has.hasBreakContinue) {
  561. for (const key of Object.keys(has.map)) {
  562. cases.push(_core.types.switchCase(_core.types.stringLiteral(key), [has.map[key]]));
  563. }
  564. if (has.hasReturn) {
  565. cases.push(_core.types.switchCase(null, [retCheck]));
  566. }
  567. if (cases.length === 1) {
  568. const single = cases[0];
  569. body.push(_core.types.ifStatement(_core.types.binaryExpression("===", _core.types.identifier(ret), single.test), single.consequent[0]));
  570. } else {
  571. if (this.loop) {
  572. for (let i = 0; i < cases.length; i++) {
  573. const caseConsequent = cases[i].consequent[0];
  574. if (_core.types.isBreakStatement(caseConsequent) && !caseConsequent.label) {
  575. if (!this.loopLabel) {
  576. this.loopLabel = this.scope.generateUidIdentifier("loop");
  577. }
  578. caseConsequent.label = _core.types.cloneNode(this.loopLabel);
  579. }
  580. }
  581. }
  582. body.push(_core.types.switchStatement(_core.types.identifier(ret), cases));
  583. }
  584. } else {
  585. if (has.hasReturn) {
  586. body.push(retCheck);
  587. }
  588. }
  589. }
  590. }