hoister.js 5.9 KB

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