index.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var t = require('@babel/types');
  4. function willPathCastToBoolean(path) {
  5. const maybeWrapped = path;
  6. const {
  7. node,
  8. parentPath
  9. } = maybeWrapped;
  10. if (parentPath.isLogicalExpression()) {
  11. const {
  12. operator,
  13. right
  14. } = parentPath.node;
  15. if (operator === "&&" || operator === "||" || operator === "??" && node === right) {
  16. return willPathCastToBoolean(parentPath);
  17. }
  18. }
  19. if (parentPath.isSequenceExpression()) {
  20. const {
  21. expressions
  22. } = parentPath.node;
  23. if (expressions[expressions.length - 1] === node) {
  24. return willPathCastToBoolean(parentPath);
  25. } else {
  26. return true;
  27. }
  28. }
  29. return parentPath.isConditional({
  30. test: node
  31. }) || parentPath.isUnaryExpression({
  32. operator: "!"
  33. }) || parentPath.isLoop({
  34. test: node
  35. });
  36. }
  37. class AssignmentMemoiser {
  38. constructor() {
  39. this._map = new WeakMap();
  40. }
  41. has(key) {
  42. return this._map.has(key);
  43. }
  44. get(key) {
  45. if (!this.has(key)) return;
  46. const record = this._map.get(key);
  47. const {
  48. value
  49. } = record;
  50. record.count--;
  51. if (record.count === 0) {
  52. return t.assignmentExpression("=", value, key);
  53. }
  54. return value;
  55. }
  56. set(key, value, count) {
  57. return this._map.set(key, {
  58. count,
  59. value
  60. });
  61. }
  62. }
  63. function toNonOptional(path, base) {
  64. const {
  65. node
  66. } = path;
  67. if (path.isOptionalMemberExpression()) {
  68. return t.memberExpression(base, node.property, node.computed);
  69. }
  70. if (path.isOptionalCallExpression()) {
  71. const callee = path.get("callee");
  72. if (path.node.optional && callee.isOptionalMemberExpression()) {
  73. const {
  74. object
  75. } = callee.node;
  76. const context = path.scope.maybeGenerateMemoised(object) || object;
  77. callee.get("object").replaceWith(t.assignmentExpression("=", context, object));
  78. return t.callExpression(t.memberExpression(base, t.identifier("call")), [context, ...node.arguments]);
  79. }
  80. return t.callExpression(base, node.arguments);
  81. }
  82. return path.node;
  83. }
  84. function isInDetachedTree(path) {
  85. while (path) {
  86. if (path.isProgram()) break;
  87. const {
  88. parentPath,
  89. container,
  90. listKey
  91. } = path;
  92. const parentNode = parentPath.node;
  93. if (listKey) {
  94. if (container !== parentNode[listKey]) return true;
  95. } else {
  96. if (container !== parentNode) return true;
  97. }
  98. path = parentPath;
  99. }
  100. return false;
  101. }
  102. const handle = {
  103. memoise() {},
  104. handle(member) {
  105. const {
  106. node,
  107. parent,
  108. parentPath,
  109. scope
  110. } = member;
  111. if (member.isOptionalMemberExpression()) {
  112. if (isInDetachedTree(member)) return;
  113. const endPath = member.find(({
  114. node,
  115. parent,
  116. parentPath
  117. }) => {
  118. if (parentPath.isOptionalMemberExpression()) {
  119. return parent.optional || parent.object !== node;
  120. }
  121. if (parentPath.isOptionalCallExpression()) {
  122. return node !== member.node && parent.optional || parent.callee !== node;
  123. }
  124. return true;
  125. });
  126. if (scope.path.isPattern()) {
  127. endPath.replaceWith(t.callExpression(t.arrowFunctionExpression([], endPath.node), []));
  128. return;
  129. }
  130. const willEndPathCastToBoolean = willPathCastToBoolean(endPath);
  131. const rootParentPath = endPath.parentPath;
  132. if (rootParentPath.isUpdateExpression({
  133. argument: node
  134. }) || rootParentPath.isAssignmentExpression({
  135. left: node
  136. })) {
  137. throw member.buildCodeFrameError(`can't handle assignment`);
  138. }
  139. const isDeleteOperation = rootParentPath.isUnaryExpression({
  140. operator: "delete"
  141. });
  142. if (isDeleteOperation && endPath.isOptionalMemberExpression() && endPath.get("property").isPrivateName()) {
  143. throw member.buildCodeFrameError(`can't delete a private class element`);
  144. }
  145. let startingOptional = member;
  146. for (;;) {
  147. if (startingOptional.isOptionalMemberExpression()) {
  148. if (startingOptional.node.optional) break;
  149. startingOptional = startingOptional.get("object");
  150. continue;
  151. } else if (startingOptional.isOptionalCallExpression()) {
  152. if (startingOptional.node.optional) break;
  153. startingOptional = startingOptional.get("callee");
  154. continue;
  155. }
  156. throw new Error(`Internal error: unexpected ${startingOptional.node.type}`);
  157. }
  158. const startingProp = startingOptional.isOptionalMemberExpression() ? "object" : "callee";
  159. const startingNode = startingOptional.node[startingProp];
  160. const baseNeedsMemoised = scope.maybeGenerateMemoised(startingNode);
  161. const baseRef = baseNeedsMemoised != null ? baseNeedsMemoised : startingNode;
  162. const parentIsOptionalCall = parentPath.isOptionalCallExpression({
  163. callee: node
  164. });
  165. const parentIsCall = parentPath.isCallExpression({
  166. callee: node
  167. });
  168. startingOptional.replaceWith(toNonOptional(startingOptional, baseRef));
  169. if (parentIsOptionalCall) {
  170. if (parent.optional) {
  171. parentPath.replaceWith(this.optionalCall(member, parent.arguments));
  172. } else {
  173. parentPath.replaceWith(this.call(member, parent.arguments));
  174. }
  175. } else if (parentIsCall) {
  176. member.replaceWith(this.boundGet(member));
  177. } else {
  178. member.replaceWith(this.get(member));
  179. }
  180. let regular = member.node;
  181. for (let current = member; current !== endPath;) {
  182. const {
  183. parentPath
  184. } = current;
  185. if (parentPath === endPath && parentIsOptionalCall && parent.optional) {
  186. regular = parentPath.node;
  187. break;
  188. }
  189. regular = toNonOptional(parentPath, regular);
  190. current = parentPath;
  191. }
  192. let context;
  193. const endParentPath = endPath.parentPath;
  194. if (t.isMemberExpression(regular) && endParentPath.isOptionalCallExpression({
  195. callee: endPath.node,
  196. optional: true
  197. })) {
  198. const {
  199. object
  200. } = regular;
  201. context = member.scope.maybeGenerateMemoised(object);
  202. if (context) {
  203. regular.object = t.assignmentExpression("=", context, object);
  204. }
  205. }
  206. let replacementPath = endPath;
  207. if (isDeleteOperation) {
  208. replacementPath = endParentPath;
  209. regular = endParentPath.node;
  210. }
  211. if (willEndPathCastToBoolean) {
  212. const nonNullishCheck = t.logicalExpression("&&", t.binaryExpression("!==", baseNeedsMemoised ? t.assignmentExpression("=", t.cloneNode(baseRef), t.cloneNode(startingNode)) : t.cloneNode(baseRef), t.nullLiteral()), t.binaryExpression("!==", t.cloneNode(baseRef), scope.buildUndefinedNode()));
  213. replacementPath.replaceWith(t.logicalExpression("&&", nonNullishCheck, regular));
  214. } else {
  215. const nullishCheck = t.logicalExpression("||", t.binaryExpression("===", baseNeedsMemoised ? t.assignmentExpression("=", t.cloneNode(baseRef), t.cloneNode(startingNode)) : t.cloneNode(baseRef), t.nullLiteral()), t.binaryExpression("===", t.cloneNode(baseRef), scope.buildUndefinedNode()));
  216. replacementPath.replaceWith(t.conditionalExpression(nullishCheck, isDeleteOperation ? t.booleanLiteral(true) : scope.buildUndefinedNode(), regular));
  217. }
  218. if (context) {
  219. const endParent = endParentPath.node;
  220. endParentPath.replaceWith(t.optionalCallExpression(t.optionalMemberExpression(endParent.callee, t.identifier("call"), false, true), [t.cloneNode(context), ...endParent.arguments], false));
  221. }
  222. return;
  223. }
  224. if (parentPath.isUpdateExpression({
  225. argument: node
  226. })) {
  227. if (this.simpleSet) {
  228. member.replaceWith(this.simpleSet(member));
  229. return;
  230. }
  231. const {
  232. operator,
  233. prefix
  234. } = parent;
  235. this.memoise(member, 2);
  236. const value = t.binaryExpression(operator[0], t.unaryExpression("+", this.get(member)), t.numericLiteral(1));
  237. if (prefix) {
  238. parentPath.replaceWith(this.set(member, value));
  239. } else {
  240. const {
  241. scope
  242. } = member;
  243. const ref = scope.generateUidIdentifierBasedOnNode(node);
  244. scope.push({
  245. id: ref
  246. });
  247. value.left = t.assignmentExpression("=", t.cloneNode(ref), value.left);
  248. parentPath.replaceWith(t.sequenceExpression([this.set(member, value), t.cloneNode(ref)]));
  249. }
  250. return;
  251. }
  252. if (parentPath.isAssignmentExpression({
  253. left: node
  254. })) {
  255. if (this.simpleSet) {
  256. member.replaceWith(this.simpleSet(member));
  257. return;
  258. }
  259. const {
  260. operator,
  261. right: value
  262. } = parent;
  263. if (operator === "=") {
  264. parentPath.replaceWith(this.set(member, value));
  265. } else {
  266. const operatorTrunc = operator.slice(0, -1);
  267. if (t.LOGICAL_OPERATORS.includes(operatorTrunc)) {
  268. this.memoise(member, 1);
  269. parentPath.replaceWith(t.logicalExpression(operatorTrunc, this.get(member), this.set(member, value)));
  270. } else {
  271. this.memoise(member, 2);
  272. parentPath.replaceWith(this.set(member, t.binaryExpression(operatorTrunc, this.get(member), value)));
  273. }
  274. }
  275. return;
  276. }
  277. if (parentPath.isCallExpression({
  278. callee: node
  279. })) {
  280. parentPath.replaceWith(this.call(member, parent.arguments));
  281. return;
  282. }
  283. if (parentPath.isOptionalCallExpression({
  284. callee: node
  285. })) {
  286. if (scope.path.isPattern()) {
  287. parentPath.replaceWith(t.callExpression(t.arrowFunctionExpression([], parentPath.node), []));
  288. return;
  289. }
  290. parentPath.replaceWith(this.optionalCall(member, parent.arguments));
  291. return;
  292. }
  293. if (parentPath.isForXStatement({
  294. left: node
  295. }) || parentPath.isObjectProperty({
  296. value: node
  297. }) && parentPath.parentPath.isObjectPattern() || parentPath.isAssignmentPattern({
  298. left: node
  299. }) && parentPath.parentPath.isObjectProperty({
  300. value: parent
  301. }) && parentPath.parentPath.parentPath.isObjectPattern() || parentPath.isArrayPattern() || parentPath.isAssignmentPattern({
  302. left: node
  303. }) && parentPath.parentPath.isArrayPattern() || parentPath.isRestElement()) {
  304. member.replaceWith(this.destructureSet(member));
  305. return;
  306. }
  307. member.replaceWith(this.get(member));
  308. }
  309. };
  310. function memberExpressionToFunctions(path, visitor, state) {
  311. path.traverse(visitor, Object.assign({}, handle, state, {
  312. memoiser: new AssignmentMemoiser()
  313. }));
  314. }
  315. exports.default = memberExpressionToFunctions;
  316. //# sourceMappingURL=index.js.map