packages/babel-plugin-transform-new-target/src/index.js

Summary

Maintainability
A
2 hrs
Test Coverage
import { declare } from "@babel/helper-plugin-utils";
import { types as t } from "@babel/core";

export default declare(api => {
  api.assertVersion(7);

  return {
    name: "transform-new-target",

    visitor: {
      MetaProperty(path) {
        const meta = path.get("meta");
        const property = path.get("property");
        const { scope } = path;

        if (
          meta.isIdentifier({ name: "new" }) &&
          property.isIdentifier({ name: "target" })
        ) {
          const func = path.findParent(path => {
            if (path.isClass()) return true;
            if (path.isFunction() && !path.isArrowFunctionExpression()) {
              if (path.isClassMethod({ kind: "constructor" })) {
                return false;
              }

              return true;
            }
            return false;
          });

          if (!func) {
            throw path.buildCodeFrameError(
              "new.target must be under a (non-arrow) function or a class.",
            );
          }

          const { node } = func;
          if (!node.id) {
            if (func.isMethod()) {
              path.replaceWith(scope.buildUndefinedNode());
              return;
            }

            node.id = scope.generateUidIdentifier("target");
          }

          const constructor = t.memberExpression(
            t.thisExpression(),
            t.identifier("constructor"),
          );

          if (func.isClass()) {
            path.replaceWith(constructor);
            return;
          }

          path.replaceWith(
            t.conditionalExpression(
              t.binaryExpression(
                "instanceof",
                t.thisExpression(),
                t.cloneNode(node.id),
              ),
              constructor,
              scope.buildUndefinedNode(),
            ),
          );
        }
      },
    },
  };
});