index.js 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', { value: true });
  3. var helperPluginUtils = require('@babel/helper-plugin-utils');
  4. var syntaxOptionalChaining = require('@babel/plugin-syntax-optional-chaining');
  5. var core = require('@babel/core');
  6. var helperSkipTransparentExpressionWrappers = require('@babel/helper-skip-transparent-expression-wrappers');
  7. function willPathCastToBoolean(path) {
  8. const maybeWrapped = findOutermostTransparentParent(path);
  9. const {
  10. node,
  11. parentPath
  12. } = maybeWrapped;
  13. if (parentPath.isLogicalExpression()) {
  14. const {
  15. operator,
  16. right
  17. } = parentPath.node;
  18. if (operator === "&&" || operator === "||" || operator === "??" && node === right) {
  19. return willPathCastToBoolean(parentPath);
  20. }
  21. }
  22. if (parentPath.isSequenceExpression()) {
  23. const {
  24. expressions
  25. } = parentPath.node;
  26. if (expressions[expressions.length - 1] === node) {
  27. return willPathCastToBoolean(parentPath);
  28. } else {
  29. return true;
  30. }
  31. }
  32. return parentPath.isConditional({
  33. test: node
  34. }) || parentPath.isUnaryExpression({
  35. operator: "!"
  36. }) || parentPath.isLoop({
  37. test: node
  38. });
  39. }
  40. function findOutermostTransparentParent(path) {
  41. let maybeWrapped = path;
  42. path.findParent(p => {
  43. if (!helperSkipTransparentExpressionWrappers.isTransparentExprWrapper(p.node)) return true;
  44. maybeWrapped = p;
  45. });
  46. return maybeWrapped;
  47. }
  48. const {
  49. ast
  50. } = core.template.expression;
  51. function isSimpleMemberExpression(expression) {
  52. expression = helperSkipTransparentExpressionWrappers.skipTransparentExprWrapperNodes(expression);
  53. return core.types.isIdentifier(expression) || core.types.isSuper(expression) || core.types.isMemberExpression(expression) && !expression.computed && isSimpleMemberExpression(expression.object);
  54. }
  55. function needsMemoize(path) {
  56. let optionalPath = path;
  57. const {
  58. scope
  59. } = path;
  60. while (optionalPath.isOptionalMemberExpression() || optionalPath.isOptionalCallExpression()) {
  61. const {
  62. node
  63. } = optionalPath;
  64. const childPath = helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers(
  65. optionalPath.isOptionalMemberExpression() ? optionalPath.get("object") : optionalPath.get("callee"));
  66. if (node.optional) {
  67. return !scope.isStatic(childPath.node);
  68. }
  69. optionalPath = childPath;
  70. }
  71. }
  72. function transform(path, {
  73. pureGetters,
  74. noDocumentAll
  75. }) {
  76. const {
  77. scope
  78. } = path;
  79. const maybeWrapped = findOutermostTransparentParent(path);
  80. const {
  81. parentPath
  82. } = maybeWrapped;
  83. const willReplacementCastToBoolean = willPathCastToBoolean(maybeWrapped);
  84. let isDeleteOperation = false;
  85. const parentIsCall = parentPath.isCallExpression({
  86. callee: maybeWrapped.node
  87. }) &&
  88. path.isOptionalMemberExpression();
  89. const optionals = [];
  90. let optionalPath = path;
  91. if (scope.path.isPattern() && needsMemoize(optionalPath)) {
  92. path.replaceWith(core.template.ast`(() => ${path.node})()`);
  93. return;
  94. }
  95. while (optionalPath.isOptionalMemberExpression() || optionalPath.isOptionalCallExpression()) {
  96. const {
  97. node
  98. } = optionalPath;
  99. if (node.optional) {
  100. optionals.push(node);
  101. }
  102. if (optionalPath.isOptionalMemberExpression()) {
  103. optionalPath.node.type = "MemberExpression";
  104. optionalPath = helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers(optionalPath.get("object"));
  105. } else if (optionalPath.isOptionalCallExpression()) {
  106. optionalPath.node.type = "CallExpression";
  107. optionalPath = helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers(optionalPath.get("callee"));
  108. }
  109. }
  110. let replacementPath = path;
  111. if (parentPath.isUnaryExpression({
  112. operator: "delete"
  113. })) {
  114. replacementPath = parentPath;
  115. isDeleteOperation = true;
  116. }
  117. for (let i = optionals.length - 1; i >= 0; i--) {
  118. const node = optionals[i];
  119. const isCall = core.types.isCallExpression(node);
  120. const chainWithTypes = isCall ?
  121. node.callee : node.object;
  122. const chain = helperSkipTransparentExpressionWrappers.skipTransparentExprWrapperNodes(chainWithTypes);
  123. let ref;
  124. let check;
  125. if (isCall && core.types.isIdentifier(chain, {
  126. name: "eval"
  127. })) {
  128. check = ref = chain;
  129. node.callee = core.types.sequenceExpression([core.types.numericLiteral(0), ref]);
  130. } else if (pureGetters && isCall && isSimpleMemberExpression(chain)) {
  131. check = ref = node.callee;
  132. } else {
  133. ref = scope.maybeGenerateMemoised(chain);
  134. if (ref) {
  135. check = core.types.assignmentExpression("=", core.types.cloneNode(ref),
  136. chainWithTypes);
  137. isCall ? node.callee = ref : node.object = ref;
  138. } else {
  139. check = ref = chainWithTypes;
  140. }
  141. }
  142. if (isCall && core.types.isMemberExpression(chain)) {
  143. if (pureGetters && isSimpleMemberExpression(chain)) {
  144. node.callee = chainWithTypes;
  145. } else {
  146. const {
  147. object
  148. } = chain;
  149. let context;
  150. if (core.types.isSuper(object)) {
  151. context = core.types.thisExpression();
  152. } else {
  153. const memoized = scope.maybeGenerateMemoised(object);
  154. if (memoized) {
  155. context = memoized;
  156. chain.object = core.types.assignmentExpression("=", memoized, object);
  157. } else {
  158. context = object;
  159. }
  160. }
  161. node.arguments.unshift(core.types.cloneNode(context));
  162. node.callee = core.types.memberExpression(node.callee, core.types.identifier("call"));
  163. }
  164. }
  165. let replacement = replacementPath.node;
  166. if (i === 0 && parentIsCall) {
  167. var _baseRef;
  168. const object = helperSkipTransparentExpressionWrappers.skipTransparentExprWrapperNodes(replacement.object
  169. );
  170. let baseRef;
  171. if (!pureGetters || !isSimpleMemberExpression(object)) {
  172. baseRef = scope.maybeGenerateMemoised(object);
  173. if (baseRef) {
  174. replacement.object = core.types.assignmentExpression("=", baseRef, object);
  175. }
  176. }
  177. replacement = core.types.callExpression(core.types.memberExpression(replacement, core.types.identifier("bind")), [core.types.cloneNode((_baseRef = baseRef) != null ? _baseRef : object)]);
  178. }
  179. if (willReplacementCastToBoolean) {
  180. const nonNullishCheck = noDocumentAll ? ast`${core.types.cloneNode(check)} != null` : ast`
  181. ${core.types.cloneNode(check)} !== null && ${core.types.cloneNode(ref)} !== void 0`;
  182. replacementPath.replaceWith(core.types.logicalExpression("&&", nonNullishCheck, replacement));
  183. replacementPath = helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers(
  184. replacementPath.get("right"));
  185. } else {
  186. const nullishCheck = noDocumentAll ? ast`${core.types.cloneNode(check)} == null` : ast`
  187. ${core.types.cloneNode(check)} === null || ${core.types.cloneNode(ref)} === void 0`;
  188. const returnValue = isDeleteOperation ? ast`true` : ast`void 0`;
  189. replacementPath.replaceWith(core.types.conditionalExpression(nullishCheck, returnValue, replacement));
  190. replacementPath = helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers(
  191. replacementPath.get("alternate"));
  192. }
  193. }
  194. }
  195. var index = helperPluginUtils.declare((api, options) => {
  196. var _api$assumption, _api$assumption2;
  197. api.assertVersion(7);
  198. const {
  199. loose = false
  200. } = options;
  201. const noDocumentAll = (_api$assumption = api.assumption("noDocumentAll")) != null ? _api$assumption : loose;
  202. const pureGetters = (_api$assumption2 = api.assumption("pureGetters")) != null ? _api$assumption2 : loose;
  203. return {
  204. name: "proposal-optional-chaining",
  205. inherits: syntaxOptionalChaining.default,
  206. visitor: {
  207. "OptionalCallExpression|OptionalMemberExpression"(path) {
  208. transform(path, {
  209. noDocumentAll,
  210. pureGetters
  211. });
  212. }
  213. }
  214. };
  215. });
  216. exports["default"] = index;
  217. exports.transform = transform;
  218. //# sourceMappingURL=index.js.map