index.js 6.04 KB
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _helperPluginUtils = require("@babel/helper-plugin-utils");

var _helperSkipTransparentExpressionWrappers = require("@babel/helper-skip-transparent-expression-wrappers");

var _pluginSyntaxOptionalChaining = _interopRequireDefault(require("@babel/plugin-syntax-optional-chaining"));

var _core = require("@babel/core");

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var _default = (0, _helperPluginUtils.declare)((api, options) => {
  api.assertVersion(7);
  const {
    loose = false
  } = options;

  function isSimpleMemberExpression(expression) {
    expression = (0, _helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers)(expression);
    return _core.types.isIdentifier(expression) || _core.types.isSuper(expression) || _core.types.isMemberExpression(expression) && !expression.computed && isSimpleMemberExpression(expression.object);
  }

  return {
    name: "proposal-optional-chaining",
    inherits: _pluginSyntaxOptionalChaining.default,
    visitor: {
      "OptionalCallExpression|OptionalMemberExpression"(path) {
        const {
          scope
        } = path;
        let maybeWrapped = path;
        const parentPath = path.findParent(p => {
          if (!(0, _helperSkipTransparentExpressionWrappers.isTransparentExprWrapper)(p)) return true;
          maybeWrapped = p;
        });
        let isDeleteOperation = false;
        const parentIsCall = parentPath.isCallExpression({
          callee: maybeWrapped.node
        }) && path.isOptionalMemberExpression();
        const optionals = [];
        let optionalPath = path;

        while (optionalPath.isOptionalMemberExpression() || optionalPath.isOptionalCallExpression()) {
          const {
            node
          } = optionalPath;

          if (node.optional) {
            optionals.push(node);
          }

          if (optionalPath.isOptionalMemberExpression()) {
            optionalPath.node.type = "MemberExpression";
            optionalPath = (0, _helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers)(optionalPath.get("object"));
          } else if (optionalPath.isOptionalCallExpression()) {
            optionalPath.node.type = "CallExpression";
            optionalPath = (0, _helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers)(optionalPath.get("callee"));
          }
        }

        let replacementPath = path;

        if (parentPath.isUnaryExpression({
          operator: "delete"
        })) {
          replacementPath = parentPath;
          isDeleteOperation = true;
        }

        for (let i = optionals.length - 1; i >= 0; i--) {
          const node = optionals[i];

          const isCall = _core.types.isCallExpression(node);

          const replaceKey = isCall ? "callee" : "object";
          const chainWithTypes = node[replaceKey];
          let chain = chainWithTypes;

          while ((0, _helperSkipTransparentExpressionWrappers.isTransparentExprWrapper)(chain)) {
            chain = chain.expression;
          }

          let ref;
          let check;

          if (isCall && _core.types.isIdentifier(chain, {
            name: "eval"
          })) {
            check = ref = chain;
            node[replaceKey] = _core.types.sequenceExpression([_core.types.numericLiteral(0), ref]);
          } else if (loose && isCall && isSimpleMemberExpression(chain)) {
            check = ref = chainWithTypes;
          } else {
            ref = scope.maybeGenerateMemoised(chain);

            if (ref) {
              check = _core.types.assignmentExpression("=", _core.types.cloneNode(ref), chainWithTypes);
              node[replaceKey] = ref;
            } else {
              check = ref = chainWithTypes;
            }
          }

          if (isCall && _core.types.isMemberExpression(chain)) {
            if (loose && isSimpleMemberExpression(chain)) {
              node.callee = chainWithTypes;
            } else {
              const {
                object
              } = chain;
              let context = scope.maybeGenerateMemoised(object);

              if (context) {
                chain.object = _core.types.assignmentExpression("=", context, object);
              } else if (_core.types.isSuper(object)) {
                context = _core.types.thisExpression();
              } else {
                context = object;
              }

              node.arguments.unshift(_core.types.cloneNode(context));
              node.callee = _core.types.memberExpression(node.callee, _core.types.identifier("call"));
            }
          }

          let replacement = replacementPath.node;

          if (i === 0 && parentIsCall) {
            var _baseRef;

            const object = (0, _helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers)(replacementPath.get("object")).node;
            let baseRef;

            if (!loose || !isSimpleMemberExpression(object)) {
              baseRef = scope.maybeGenerateMemoised(object);

              if (baseRef) {
                replacement.object = _core.types.assignmentExpression("=", baseRef, object);
              }
            }

            replacement = _core.types.callExpression(_core.types.memberExpression(replacement, _core.types.identifier("bind")), [_core.types.cloneNode((_baseRef = baseRef) != null ? _baseRef : object)]);
          }

          replacementPath.replaceWith(_core.types.conditionalExpression(loose ? _core.types.binaryExpression("==", _core.types.cloneNode(check), _core.types.nullLiteral()) : _core.types.logicalExpression("||", _core.types.binaryExpression("===", _core.types.cloneNode(check), _core.types.nullLiteral()), _core.types.binaryExpression("===", _core.types.cloneNode(ref), scope.buildUndefinedNode())), isDeleteOperation ? _core.types.booleanLiteral(true) : scope.buildUndefinedNode(), replacement));
          replacementPath = (0, _helperSkipTransparentExpressionWrappers.skipTransparentExprWrappers)(replacementPath.get("alternate"));
        }
      }

    }
  };
});

exports.default = _default;