index.js 8.0 KB

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