index.js 9.0 KB

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