hoister.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _t = require("@babel/types");
  7. var _t2 = _t;
  8. const {
  9. react
  10. } = _t;
  11. const {
  12. cloneNode,
  13. jsxExpressionContainer,
  14. variableDeclaration,
  15. variableDeclarator
  16. } = _t2;
  17. const referenceVisitor = {
  18. ReferencedIdentifier(path, state) {
  19. if (path.isJSXIdentifier() && react.isCompatTag(path.node.name) && !path.parentPath.isJSXMemberExpression()) {
  20. return;
  21. }
  22. if (path.node.name === "this") {
  23. let scope = path.scope;
  24. do {
  25. if (scope.path.isFunction() && !scope.path.isArrowFunctionExpression()) {
  26. break;
  27. }
  28. } while (scope = scope.parent);
  29. if (scope) state.breakOnScopePaths.push(scope.path);
  30. }
  31. const binding = path.scope.getBinding(path.node.name);
  32. if (!binding) return;
  33. for (const violation of binding.constantViolations) {
  34. if (violation.scope !== binding.path.scope) {
  35. state.mutableBinding = true;
  36. path.stop();
  37. return;
  38. }
  39. }
  40. if (binding !== state.scope.getBinding(path.node.name)) return;
  41. state.bindings[path.node.name] = binding;
  42. }
  43. };
  44. class PathHoister {
  45. constructor(path, scope) {
  46. this.breakOnScopePaths = void 0;
  47. this.bindings = void 0;
  48. this.mutableBinding = void 0;
  49. this.scopes = void 0;
  50. this.scope = void 0;
  51. this.path = void 0;
  52. this.attachAfter = void 0;
  53. this.breakOnScopePaths = [];
  54. this.bindings = {};
  55. this.mutableBinding = false;
  56. this.scopes = [];
  57. this.scope = scope;
  58. this.path = path;
  59. this.attachAfter = false;
  60. }
  61. isCompatibleScope(scope) {
  62. for (const key of Object.keys(this.bindings)) {
  63. const binding = this.bindings[key];
  64. if (!scope.bindingIdentifierEquals(key, binding.identifier)) {
  65. return false;
  66. }
  67. }
  68. return true;
  69. }
  70. getCompatibleScopes() {
  71. let scope = this.path.scope;
  72. do {
  73. if (this.isCompatibleScope(scope)) {
  74. this.scopes.push(scope);
  75. } else {
  76. break;
  77. }
  78. if (this.breakOnScopePaths.indexOf(scope.path) >= 0) {
  79. break;
  80. }
  81. } while (scope = scope.parent);
  82. }
  83. getAttachmentPath() {
  84. let path = this._getAttachmentPath();
  85. if (!path) return;
  86. let targetScope = path.scope;
  87. if (targetScope.path === path) {
  88. targetScope = path.scope.parent;
  89. }
  90. if (targetScope.path.isProgram() || targetScope.path.isFunction()) {
  91. for (const name of Object.keys(this.bindings)) {
  92. if (!targetScope.hasOwnBinding(name)) continue;
  93. const binding = this.bindings[name];
  94. if (binding.kind === "param" || binding.path.parentKey === "params") {
  95. continue;
  96. }
  97. const bindingParentPath = this.getAttachmentParentForPath(binding.path);
  98. if (bindingParentPath.key >= path.key) {
  99. this.attachAfter = true;
  100. path = binding.path;
  101. for (const violationPath of binding.constantViolations) {
  102. if (this.getAttachmentParentForPath(violationPath).key > path.key) {
  103. path = violationPath;
  104. }
  105. }
  106. }
  107. }
  108. }
  109. return path;
  110. }
  111. _getAttachmentPath() {
  112. const scopes = this.scopes;
  113. const scope = scopes.pop();
  114. if (!scope) return;
  115. if (scope.path.isFunction()) {
  116. if (this.hasOwnParamBindings(scope)) {
  117. if (this.scope === scope) return;
  118. const bodies = scope.path.get("body").get("body");
  119. for (let i = 0; i < bodies.length; i++) {
  120. if (bodies[i].node._blockHoist) continue;
  121. return bodies[i];
  122. }
  123. } else {
  124. return this.getNextScopeAttachmentParent();
  125. }
  126. } else if (scope.path.isProgram()) {
  127. return this.getNextScopeAttachmentParent();
  128. }
  129. }
  130. getNextScopeAttachmentParent() {
  131. const scope = this.scopes.pop();
  132. if (scope) return this.getAttachmentParentForPath(scope.path);
  133. }
  134. getAttachmentParentForPath(path) {
  135. do {
  136. if (!path.parentPath || Array.isArray(path.container) && path.isStatement()) {
  137. return path;
  138. }
  139. } while (path = path.parentPath);
  140. }
  141. hasOwnParamBindings(scope) {
  142. for (const name of Object.keys(this.bindings)) {
  143. if (!scope.hasOwnBinding(name)) continue;
  144. const binding = this.bindings[name];
  145. if (binding.kind === "param" && binding.constant) return true;
  146. }
  147. return false;
  148. }
  149. run() {
  150. this.path.traverse(referenceVisitor, this);
  151. if (this.mutableBinding) return;
  152. this.getCompatibleScopes();
  153. const attachTo = this.getAttachmentPath();
  154. if (!attachTo) return;
  155. if (attachTo.getFunctionParent() === this.path.getFunctionParent()) return;
  156. let uid = attachTo.scope.generateUidIdentifier("ref");
  157. const declarator = variableDeclarator(uid, this.path.node);
  158. const insertFn = this.attachAfter ? "insertAfter" : "insertBefore";
  159. const [attached] = attachTo[insertFn]([attachTo.isVariableDeclarator() ? declarator : variableDeclaration("var", [declarator])]);
  160. const parent = this.path.parentPath;
  161. if (parent.isJSXElement() && this.path.container === parent.node.children) {
  162. uid = jsxExpressionContainer(uid);
  163. }
  164. this.path.replaceWith(cloneNode(uid));
  165. return attachTo.isVariableDeclarator() ? attached.get("init") : attached.get("declarations.0.init");
  166. }
  167. }
  168. exports.default = PathHoister;