Example #1
0
    _proto.canUnpackArrayPattern = function canUnpackArrayPattern(pattern, arr) {
      if (!_core.types.isArrayExpression(arr)) return false;
      if (pattern.elements.length > arr.elements.length) return;

      if (pattern.elements.length < arr.elements.length && !hasRest(pattern)) {
        return false;
      }

      var _arr3 = pattern.elements;

      for (var _i3 = 0; _i3 < _arr3.length; _i3++) {
        var elem = _arr3[_i3];
        if (!elem) return false;
        if (_core.types.isMemberExpression(elem)) return false;
      }

      var _arr4 = arr.elements;

      for (var _i4 = 0; _i4 < _arr4.length; _i4++) {
        var _elem = _arr4[_i4];
        if (_core.types.isSpreadElement(_elem)) return false;
        if (_core.types.isCallExpression(_elem)) return false;
        if (_core.types.isMemberExpression(_elem)) return false;
      }

      var bindings = _core.types.getBindingIdentifiers(pattern);

      var state = {
        deopt: false,
        bindings: bindings
      };
      this.scope.traverse(arr, arrayUnpackVisitor, state);
      return !state.deopt;
    };
Example #2
0
  function pushComputedPropsLoose(path, file) {
    const { node, scope, parent } = path;
    const { left } = node;
    let declar, id, intermediate;

    if (
      t.isIdentifier(left) ||
      t.isPattern(left) ||
      t.isMemberExpression(left)
    ) {
      // for (i of test), for ({ i } of test)
      id = left;
      intermediate = null;
    } else if (t.isVariableDeclaration(left)) {
      // for (let i of test)
      id = scope.generateUidIdentifier("ref");
      declar = t.variableDeclaration(left.kind, [
        t.variableDeclarator(left.declarations[0].id, t.identifier(id.name)),
      ]);
      intermediate = t.variableDeclaration("var", [
        t.variableDeclarator(t.identifier(id.name)),
      ]);
    } else {
      throw file.buildCodeFrameError(
        left,
        `Unknown node type ${left.type} in ForStatement`,
      );
    }

    const iteratorKey = scope.generateUidIdentifier("iterator");
    const isArrayKey = scope.generateUidIdentifier("isArray");

    const loop = buildForOfLoose({
      LOOP_OBJECT: iteratorKey,
      IS_ARRAY: isArrayKey,
      OBJECT: node.right,
      INDEX: scope.generateUidIdentifier("i"),
      ID: id,
      INTERMEDIATE: intermediate,
    });

    //
    const isLabeledParent = t.isLabeledStatement(parent);
    let labeled;

    if (isLabeledParent) {
      labeled = t.labeledStatement(parent.label, loop);
    }

    return {
      replaceParent: isLabeledParent,
      declar: declar,
      node: labeled || loop,
      loop: loop,
    };
  }
Example #3
0
    _proto.init = function init(pattern, ref) {
      if (!_core.types.isArrayExpression(ref) && !_core.types.isMemberExpression(ref)) {
        var memo = this.scope.maybeGenerateMemoised(ref, true);

        if (memo) {
          this.nodes.push(this.buildVariableDeclaration(memo, _core.types.cloneNode(ref)));
          ref = memo;
        }
      }

      this.push(pattern, ref);
      return this.nodes;
    };
Example #4
0
/**
 *
 * @param {import('@babel/core').Node} valueNode
 */
function getRefInstance(valueNode) {
  if (!babel.types.isMemberExpression(valueNode)) {
    throw new Error('Expected a member expression in refInstanceof');
  }

  switch (valueNode.object.name) {
    case 'window':
      return valueNode.property.name;
    case 'React':
      return `React.${valueNode.property.name}`;
    default:
      throw new Error(`Unrecognized member expression starting with '${valueNode.object.name}'`);
  }
}
Example #5
0
    _proto.buildVariableAssignment = function buildVariableAssignment(id, init) {
      var op = this.operator;
      if (_core.types.isMemberExpression(id)) op = "=";
      var node;

      if (op) {
        node = _core.types.expressionStatement(_core.types.assignmentExpression(op, id, _core.types.cloneNode(init)));
      } else {
        node = _core.types.variableDeclaration(this.kind, [_core.types.variableDeclarator(id, _core.types.cloneNode(init))]);
      }

      node._blockHoist = this.blockHoist;
      return node;
    };
Example #6
0
export default function(path, { getAsyncIterator }) {
  const { node, scope, parent } = path;

  const stepKey = scope.generateUidIdentifier("step");
  const stepValue = scope.generateUidIdentifier("value");
  const left = node.left;
  let declar;

  if (t.isIdentifier(left) || t.isPattern(left) || t.isMemberExpression(left)) {
    // for await (i of test), for await ({ i } of test)
    declar = t.expressionStatement(
      t.assignmentExpression("=", left, stepValue),
    );
  } else if (t.isVariableDeclaration(left)) {
    // for await (let i of test)
    declar = t.variableDeclaration(left.kind, [
      t.variableDeclarator(left.declarations[0].id, stepValue),
    ]);
  }
  let template = buildForAwait({
    ITERATOR_HAD_ERROR_KEY: scope.generateUidIdentifier("didIteratorError"),
    ITERATOR_COMPLETION: scope.generateUidIdentifier(
      "iteratorNormalCompletion",
    ),
    ITERATOR_ERROR_KEY: scope.generateUidIdentifier("iteratorError"),
    ITERATOR_KEY: scope.generateUidIdentifier("iterator"),
    GET_ITERATOR: getAsyncIterator,
    OBJECT: node.right,
    STEP_VALUE: stepValue,
    STEP_KEY: stepKey,
  });

  // remove async function wrapper
  template = template.body.body;

  const isLabeledParent = t.isLabeledStatement(parent);
  const tryBody = template[3].block.body;
  const loop = tryBody[0];

  if (isLabeledParent) {
    tryBody[0] = t.labeledStatement(parent.label, loop);
  }

  return {
    replaceParent: isLabeledParent,
    node: template,
    declar,
    loop,
  };
}
Example #7
0
    canUnpackArrayPattern(pattern, arr) {
      // not an array so there's no way we can deal with this
      if (!t.isArrayExpression(arr)) return false;

      // pattern has less elements than the array and doesn't have a rest so some
      // elements wont be evaluated
      if (pattern.elements.length > arr.elements.length) return;
      if (pattern.elements.length < arr.elements.length && !hasRest(pattern)) {
        return false;
      }

      for (const elem of (pattern.elements: Array)) {
        // deopt on holes
        if (!elem) return false;

        // deopt on member expressions as they may be included in the RHS
        if (t.isMemberExpression(elem)) return false;
      }

      for (const elem of (arr.elements: Array)) {
        // deopt on spread elements
        if (t.isSpreadElement(elem)) return false;

        // deopt call expressions as they might change values of LHS variables
        if (t.isCallExpression(elem)) return false;

        // deopt on member expressions as they may be getter/setters and have side-effects
        if (t.isMemberExpression(elem)) return false;
      }

      // deopt on reference to left side identifiers
      const bindings = t.getBindingIdentifiers(pattern);
      const state = { deopt: false, bindings };
      this.scope.traverse(arr, arrayUnpackVisitor, state);
      return !state.deopt;
    }
Example #8
0
    buildVariableAssignment(id, init) {
      let op = this.operator;
      if (t.isMemberExpression(id)) op = "=";

      let node;

      if (op) {
        node = t.expressionStatement(
          t.assignmentExpression(op, id, t.cloneNode(init)),
        );
      } else {
        node = t.variableDeclaration(this.kind, [
          t.variableDeclarator(id, t.cloneNode(init)),
        ]);
      }

      node._blockHoist = this.blockHoist;

      return node;
    }
Example #9
0
    init(pattern, ref) {
      // trying to destructure a value that we can't evaluate more than once so we
      // need to save it to a variable

      if (!t.isArrayExpression(ref) && !t.isMemberExpression(ref)) {
        const memo = this.scope.maybeGenerateMemoised(ref, true);
        if (memo) {
          this.nodes.push(
            this.buildVariableDeclaration(memo, t.cloneNode(ref)),
          );
          ref = memo;
        }
      }

      //

      this.push(pattern, ref);

      return this.nodes;
    }
Example #10
0
export default declare(api => {
  api.assertVersion(7);

  return {
    inherits: syntaxLogicalAssignmentOperators,

    visitor: {
      AssignmentExpression(path) {
        const { node, scope } = path;
        const { operator, left, right } = node;
        if (operator !== "||=" && operator !== "&&=") {
          return;
        }

        let ref;
        if (t.isMemberExpression(left)) {
          const { object } = left;
          const memo = scope.maybeGenerateMemoised(object);
          if (memo) {
            path
              .get("left.object")
              .replaceWith(
                t.assignmentExpression("=", t.cloneNode(memo), object),
              );

            ref = t.cloneNode(left);
            ref.object = t.cloneNode(memo);
          }
        }

        path.replaceWith(
          t.logicalExpression(
            operator.slice(0, -1),
            left,
            t.assignmentExpression("=", ref || t.cloneNode(left), right),
          ),
        );
      },
    },
  };
});
Example #11
0
export default declare((api, options, dirname) => {
  api.assertVersion(7);

  const {
    corejs,
    helpers: useRuntimeHelpers = true,
    regenerator: useRuntimeRegenerator = true,
    useESModules = false,
    version: runtimeVersion = "7.0.0-beta.0",
    absoluteRuntime = false,
  } = options;

  let proposals = false;
  let rawVersion;

  if (typeof corejs === "object" && corejs !== null) {
    rawVersion = corejs.version;
    proposals = Boolean(corejs.proposals);
  } else {
    rawVersion = corejs;
  }

  const corejsVersion = rawVersion ? Number(rawVersion) : false;

  if (![false, 2, 3].includes(corejsVersion)) {
    throw new Error(
      `The \`core-js\` version must be false, 2 or 3, but got ${JSON.stringify(
        rawVersion,
      )}.`,
    );
  }

  if (proposals && (!corejsVersion || corejsVersion < 3)) {
    throw new Error(
      "The 'proposals' option is only supported when using 'corejs: 3'",
    );
  }

  if (typeof useRuntimeRegenerator !== "boolean") {
    throw new Error(
      "The 'regenerator' option must be undefined, or a boolean.",
    );
  }

  if (typeof useRuntimeHelpers !== "boolean") {
    throw new Error("The 'helpers' option must be undefined, or a boolean.");
  }

  if (typeof useESModules !== "boolean" && useESModules !== "auto") {
    throw new Error(
      "The 'useESModules' option must be undefined, or a boolean, or 'auto'.",
    );
  }

  if (
    typeof absoluteRuntime !== "boolean" &&
    typeof absoluteRuntime !== "string"
  ) {
    throw new Error(
      "The 'absoluteRuntime' option must be undefined, a boolean, or a string.",
    );
  }

  if (typeof runtimeVersion !== "string") {
    throw new Error(`The 'version' option must be a version string.`);
  }

  function has(obj, key) {
    return Object.prototype.hasOwnProperty.call(obj, key);
  }

  function hasMapping(methods, name) {
    return has(methods, name) && (proposals || methods[name].stable);
  }

  function hasStaticMapping(object, method) {
    return (
      has(StaticProperties, object) &&
      hasMapping(StaticProperties[object], method)
    );
  }

  function maybeNeedsPolyfill(path, methods, name) {
    if (!methods[name].types) return true;

    const typeAnnotation = path.get("object").getTypeAnnotation();
    const type = typeAnnotationToString(typeAnnotation);
    if (!type) return true;

    return methods[name].types.some(name => name === type);
  }

  if (has(options, "useBuiltIns")) {
    if (options.useBuiltIns) {
      throw new Error(
        "The 'useBuiltIns' option has been removed. The @babel/runtime " +
          "module now uses builtins by default.",
      );
    } else {
      throw new Error(
        "The 'useBuiltIns' option has been removed. Use the 'corejs'" +
          "option to polyfill with `core-js` via @babel/runtime.",
      );
    }
  }

  if (has(options, "polyfill")) {
    if (options.polyfill === false) {
      throw new Error(
        "The 'polyfill' option has been removed. The @babel/runtime " +
          "module now skips polyfilling by default.",
      );
    } else {
      throw new Error(
        "The 'polyfill' option has been removed. Use the 'corejs'" +
          "option to polyfill with `core-js` via @babel/runtime.",
      );
    }
  }

  if (has(options, "moduleName")) {
    throw new Error(
      "The 'moduleName' option has been removed. @babel/transform-runtime " +
        "no longer supports arbitrary runtimes. If you were using this to " +
        "set an absolute path for Babel's standard runtimes, please use the " +
        "'absoluteRuntime' option.",
    );
  }

  const esModules =
    useESModules === "auto" ? api.caller(supportsStaticESM) : useESModules;

  const injectCoreJS2 = corejsVersion === 2;
  const injectCoreJS3 = corejsVersion === 3;
  const injectCoreJS = corejsVersion !== false;

  const moduleName = injectCoreJS3
    ? "@babel/runtime-corejs3"
    : injectCoreJS2
    ? "@babel/runtime-corejs2"
    : "@babel/runtime";

  const corejsRoot = injectCoreJS3 && !proposals ? "core-js-stable" : "core-js";

  const { BuiltIns, StaticProperties, InstanceProperties } = (injectCoreJS2
    ? getCoreJS2Definitions
    : getCoreJS3Definitions)(runtimeVersion);

  const HEADER_HELPERS = ["interopRequireWildcard", "interopRequireDefault"];

  let modulePath = moduleName;
  if (absoluteRuntime !== false) {
    modulePath = resolveAbsoluteRuntime(
      moduleName,
      path.resolve(dirname, absoluteRuntime === true ? "." : absoluteRuntime),
    );
  }

  return {
    name: "transform-runtime",

    pre(file) {
      if (useRuntimeHelpers) {
        file.set("helperGenerator", name => {
          // If the helper didn't exist yet at the version given, we bail
          // out and let Babel either insert it directly, or throw an error
          // so that plugins can handle that case properly.
          if (
            file.availableHelper &&
            !file.availableHelper(name, runtimeVersion)
          ) {
            return;
          }

          const isInteropHelper = HEADER_HELPERS.indexOf(name) !== -1;

          // Explicitly set the CommonJS interop helpers to their reserve
          // blockHoist of 4 so they are guaranteed to exist
          // when other things used them to import.
          const blockHoist =
            isInteropHelper && !isModule(file.path) ? 4 : undefined;

          const helpersDir =
            esModules && file.path.node.sourceType === "module"
              ? "helpers/esm"
              : "helpers";

          return this.addDefaultImport(
            `${modulePath}/${helpersDir}/${name}`,
            name,
            blockHoist,
          );
        });
      }

      const cache = new Map();

      this.addDefaultImport = (source, nameHint, blockHoist) => {
        // If something on the page adds a helper when the file is an ES6
        // file, we can't reused the cached helper name after things have been
        // transformed because it has almost certainly been renamed.
        const cacheKey = isModule(file.path);
        const key = `${source}:${nameHint}:${cacheKey || ""}`;

        let cached = cache.get(key);
        if (cached) {
          cached = t.cloneNode(cached);
        } else {
          cached = addDefault(file.path, source, {
            importedInterop: "uncompiled",
            nameHint,
            blockHoist,
          });

          cache.set(key, cached);
        }
        return cached;
      };
    },

    visitor: {
      ReferencedIdentifier(path) {
        const { node, parent, scope } = path;
        const { name } = node;

        // transform `regeneratorRuntime`
        if (name === "regeneratorRuntime" && useRuntimeRegenerator) {
          path.replaceWith(
            this.addDefaultImport(
              `${modulePath}/regenerator`,
              "regeneratorRuntime",
            ),
          );
          return;
        }

        if (!injectCoreJS) return;

        if (t.isMemberExpression(parent)) return;
        if (!hasMapping(BuiltIns, name)) return;
        if (scope.getBindingIdentifier(name)) return;

        // transform global built-ins like `Symbol()`, `new Promise`
        path.replaceWith(
          this.addDefaultImport(
            `${modulePath}/${corejsRoot}/${BuiltIns[name].path}`,
            name,
          ),
        );
      },

      CallExpression(path) {
        if (!injectCoreJS) return;

        const { node } = path;
        const { callee } = node;

        if (!t.isMemberExpression(callee)) return;

        const { object, property } = callee;
        const propertyName = property.name;

        // transform calling instance methods like `something.includes()`
        if (injectCoreJS3 && !hasStaticMapping(object.name, propertyName)) {
          if (
            hasMapping(InstanceProperties, propertyName) &&
            maybeNeedsPolyfill(
              path.get("callee"),
              InstanceProperties,
              propertyName,
            )
          ) {
            let context1, context2;
            if (t.isIdentifier(object)) {
              context1 = object;
              context2 = t.cloneNode(object);
            } else {
              context1 = path.scope.generateDeclaredUidIdentifier("context");
              context2 = t.assignmentExpression("=", context1, object);
            }
            node.callee = t.memberExpression(
              t.callExpression(
                this.addDefaultImport(
                  `${moduleName}/${corejsRoot}/instance/${
                    InstanceProperties[propertyName].path
                  }`,
                  `${propertyName}InstanceProperty`,
                ),
                [context2],
              ),
              t.identifier("call"),
            );
            node.arguments.unshift(context1);
            return;
          }
        }
        // we can't compile this
        if (node.arguments.length) return;
        if (!callee.computed) return;
        if (!path.get("callee.property").matchesPattern("Symbol.iterator")) {
          return;
        }

        // transform `something[Symbol.iterator]()` to calling `getIterator(something)` helper
        path.replaceWith(
          t.callExpression(
            this.addDefaultImport(
              `${modulePath}/core-js/get-iterator`,
              "getIterator",
            ),
            [object],
          ),
        );
      },

      // transform `Symbol.iterator in something` to calling `isIterable(something)` helper
      BinaryExpression(path) {
        if (!injectCoreJS) return;
        if (path.node.operator !== "in") return;
        if (!path.get("left").matchesPattern("Symbol.iterator")) return;

        path.replaceWith(
          t.callExpression(
            this.addDefaultImport(
              `${modulePath}/core-js/is-iterable`,
              "isIterable",
            ),
            [path.node.right],
          ),
        );
      },

      // transform static built-ins methods like `Array.from`
      MemberExpression: {
        enter(path) {
          if (!injectCoreJS) return;
          if (!path.isReferenced()) return;

          const { node } = path;
          const { object, property } = node;

          if (!t.isReferenced(object, node)) return;

          if (node.computed) {
            if (injectCoreJS2) return;
            // transform `something[Symbol.iterator]` to calling `getIteratorMethod(something)` helper
            if (path.get("property").matchesPattern("Symbol.iterator")) {
              path.replaceWith(
                t.callExpression(
                  this.addDefaultImport(
                    `${moduleName}/core-js/get-iterator-method`,
                    "getIteratorMethod",
                  ),
                  [object],
                ),
              );
            }
            return;
          }

          const objectName = object.name;
          const propertyName = property.name;
          // doesn't reference the global
          if (
            path.scope.getBindingIdentifier(objectName) ||
            !hasStaticMapping(objectName, propertyName)
          ) {
            // transform getting of instance methods like `method = something.includes`
            if (
              injectCoreJS3 &&
              hasMapping(InstanceProperties, propertyName) &&
              maybeNeedsPolyfill(path, InstanceProperties, propertyName)
            ) {
              path.replaceWith(
                t.callExpression(
                  this.addDefaultImport(
                    `${moduleName}/${corejsRoot}/instance/${
                      InstanceProperties[propertyName].path
                    }`,
                    `${propertyName}InstanceProperty`,
                  ),
                  [object],
                ),
              );
            }
            return;
          }

          path.replaceWith(
            this.addDefaultImport(
              `${modulePath}/${corejsRoot}/${
                StaticProperties[objectName][propertyName].path
              }`,
              `${objectName}$${propertyName}`,
            ),
          );
        },

        exit(path) {
          if (!injectCoreJS) return;
          if (!path.isReferenced()) return;

          const { node } = path;
          const { object } = node;
          const { name } = object;

          if (!hasMapping(BuiltIns, name)) return;
          if (path.scope.getBindingIdentifier(name)) return;

          path.replaceWith(
            t.memberExpression(
              this.addDefaultImport(
                `${modulePath}/${corejsRoot}/${BuiltIns[name].path}`,
                name,
              ),
              node.property,
              node.computed,
            ),
          );
        },
      },
    },
  };
});
Example #12
0
  function pushComputedPropsSpec(path, file) {
    const { node, scope, parent } = path;
    const left = node.left;
    let declar;

    const stepKey = scope.generateUidIdentifier("step");
    const stepValue = t.memberExpression(stepKey, t.identifier("value"));

    if (
      t.isIdentifier(left) ||
      t.isPattern(left) ||
      t.isMemberExpression(left)
    ) {
      // for (i of test), for ({ i } of test)
      declar = t.expressionStatement(
        t.assignmentExpression("=", left, stepValue),
      );
    } else if (t.isVariableDeclaration(left)) {
      // for (let i of test)
      declar = t.variableDeclaration(left.kind, [
        t.variableDeclarator(left.declarations[0].id, stepValue),
      ]);
    } else {
      throw file.buildCodeFrameError(
        left,
        `Unknown node type ${left.type} in ForStatement`,
      );
    }

    //

    const iteratorKey = scope.generateUidIdentifier("iterator");

    const template = buildForOf({
      ITERATOR_HAD_ERROR_KEY: scope.generateUidIdentifier("didIteratorError"),
      ITERATOR_COMPLETION: scope.generateUidIdentifier(
        "iteratorNormalCompletion",
      ),
      ITERATOR_ERROR_KEY: scope.generateUidIdentifier("iteratorError"),
      ITERATOR_KEY: iteratorKey,
      STEP_KEY: stepKey,
      OBJECT: node.right,
    });

    const isLabeledParent = t.isLabeledStatement(parent);

    const tryBody = template[3].block.body;
    const loop = tryBody[0];

    if (isLabeledParent) {
      tryBody[0] = t.labeledStatement(parent.label, loop);
    }

    //

    return {
      replaceParent: isLabeledParent,
      declar: declar,
      loop: loop,
      node: template,
    };
  }
Example #13
0
  function optional(path, replacementPath) {
    const { scope } = path;
    const optionals = [];
    const nil = scope.buildUndefinedNode();

    let objectPath = path;
    while (objectPath.isMemberExpression() || objectPath.isCallExpression()) {
      const { node } = objectPath;
      if (node.optional) {
        optionals.push(node);
      }

      if (objectPath.isMemberExpression()) {
        objectPath = objectPath.get("object");
      } else {
        objectPath = objectPath.get("callee");
      }
    }

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

      const isCall = t.isCallExpression(node);
      const replaceKey = isCall ? "callee" : "object";
      const chain = node[replaceKey];

      let ref;
      let check;
      if (loose && isCall) {
        // If we are using a loose transform (avoiding a Function#call) and we are at the call,
        // we can avoid a needless memoize.
        check = ref = chain;
      } else {
        ref = scope.maybeGenerateMemoised(chain);
        if (ref) {
          check = t.assignmentExpression("=", ref, chain);
          node[replaceKey] = ref;
        } else {
          check = ref = chain;
        }
      }

      // Ensure call expressions have the proper `this`
      // `foo.bar()` has context `foo`.
      if (isCall && t.isMemberExpression(chain)) {
        if (loose) {
          // To avoid a Function#call, we can instead re-grab the property from the context object.
          // `a.?b.?()` translates roughly to `_a.b != null && _a.b()`
          node.callee = chain;
        } else {
          // Otherwise, we need to memoize the context object, and change the call into a Function#call.
          // `a.?b.?()` translates roughly to `(_b = _a.b) != null && _b.call(_a)`
          const { object } = chain;
          let context = scope.maybeGenerateMemoised(object);
          if (context) {
            chain.object = t.assignmentExpression("=", context, object);
          } else {
            context = object;
          }

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

      replacementPath.replaceWith(
        t.conditionalExpression(
          loose
            ? t.binaryExpression("==", t.clone(check), t.nullLiteral())
            : t.logicalExpression(
                "||",
                t.binaryExpression("===", t.clone(check), t.nullLiteral()),
                t.binaryExpression(
                  "===",
                  t.clone(ref),
                  scope.buildUndefinedNode(),
                ),
              ),
          nil,
          replacementPath.node,
        ),
      );

      replacementPath = replacementPath.get("alternate");
    }
  }
Example #14
0
export default declare(api => {
  api.assertVersion(7);

  function addDisplayName(id, call) {
    const props = call.arguments[0].properties;
    let safe = true;

    for (let i = 0; i < props.length; i++) {
      const prop = props[i];
      const key = t.toComputedKey(prop);
      if (t.isLiteral(key, { value: "displayName" })) {
        safe = false;
        break;
      }
    }

    if (safe) {
      props.unshift(
        t.objectProperty(t.identifier("displayName"), t.stringLiteral(id)),
      );
    }
  }

  const isCreateClassCallExpression = t.buildMatchMemberExpression(
    "React.createClass",
  );
  const isCreateClassAddon = callee => callee.name === "createReactClass";

  function isCreateClass(node) {
    if (!node || !t.isCallExpression(node)) return false;

    // not createReactClass nor React.createClass call member object
    if (
      !isCreateClassCallExpression(node.callee) &&
      !isCreateClassAddon(node.callee)
    ) {
      return false;
    }

    // no call arguments
    const args = node.arguments;
    if (args.length !== 1) return false;

    // first node arg is not an object
    const first = args[0];
    if (!t.isObjectExpression(first)) return false;

    return true;
  }

  return {
    name: "transform-react-display-name",

    visitor: {
      ExportDefaultDeclaration({ node }, state) {
        if (isCreateClass(node.declaration)) {
          const filename = state.filename || "unknown";

          let displayName = path.basename(filename, path.extname(filename));

          // ./{module name}/index.js
          if (displayName === "index") {
            displayName = path.basename(path.dirname(filename));
          }

          addDisplayName(displayName, node.declaration);
        }
      },

      CallExpression(path) {
        const { node } = path;
        if (!isCreateClass(node)) return;

        let id;

        // crawl up the ancestry looking for possible candidates for displayName inference
        path.find(function(path) {
          if (path.isAssignmentExpression()) {
            id = path.node.left;
          } else if (path.isObjectProperty()) {
            id = path.node.key;
          } else if (path.isVariableDeclarator()) {
            id = path.node.id;
          } else if (path.isStatement()) {
            // we've hit a statement, we should stop crawling up
            return true;
          }

          // we've got an id! no need to continue
          if (id) return true;
        });

        // ensure that we have an identifier we can inherit from
        if (!id) return;

        // foo.bar -> bar
        if (t.isMemberExpression(id)) {
          id = id.property;
        }

        // identifiers are the only thing we can reliably get a name from
        if (t.isIdentifier(id)) {
          addDisplayName(id.name, node);
        }
      },
    },
  };
});