module.exports = function()

in transforms/create-element-to-jsx.js [11:269]


module.exports = function(file, api, options) {
  const j = api.jscodeshift;
  const printOptions = options.printOptions || {};
  const root = j(file.source);
  const ReactUtils = require('./utils/ReactUtils')(j);
  const encodeJSXTextValue = value =>
    value.replace(/</g, '&lt;').replace(/>/g, '&gt;');

  const canLiteralBePropString = node =>
    node.raw.indexOf('\\') === -1 && node.value.indexOf('"') === -1;

  const convertExpressionToJSXAttributes = expression => {
    if (!expression) {
      return {
        attributes: [],
        extraComments: []
      };
    }

    const isReactSpread =
      expression.type === 'CallExpression' &&
      expression.callee.type === 'MemberExpression' &&
      expression.callee.object.name === 'React' &&
      expression.callee.property.name === '__spread';

    const isObjectAssign =
      expression.type === 'CallExpression' &&
      expression.callee.type === 'MemberExpression' &&
      expression.callee.object.name === 'Object' &&
      expression.callee.property.name === 'assign';

    const validSpreadTypes = [
      'Identifier',
      'MemberExpression',
      'CallExpression'
    ];

    if (isReactSpread || isObjectAssign) {
      const resultAttributes = [];
      const resultExtraComments = expression.comments || [];
      const { callee } = expression;
      for (const node of [callee, callee.object, callee.property]) {
        resultExtraComments.push(...(node.comments || []));
      }
      expression.arguments.forEach(expression => {
        const { attributes, extraComments } = convertExpressionToJSXAttributes(
          expression
        );
        resultAttributes.push(...attributes);
        resultExtraComments.push(...extraComments);
      });

      return {
        attributes: resultAttributes,
        extraComments: resultExtraComments
      };
    } else if (validSpreadTypes.indexOf(expression.type) != -1) {
      return {
        attributes: [j.jsxSpreadAttribute(expression)],
        extraComments: []
      };
    } else if (expression.type === 'ObjectExpression') {
      const attributes = expression.properties.map(property => {
        if (property.type === 'SpreadProperty') {
          const spreadAttribute = j.jsxSpreadAttribute(property.argument);
          spreadAttribute.comments = property.comments;
          return spreadAttribute;
        } else if (property.type === 'Property') {
          const propertyValueType = property.value.type;

          let value;
          if (
            propertyValueType === 'Literal' &&
            typeof property.value.value === 'string' &&
            canLiteralBePropString(property.value)
          ) {
            value = j.literal(property.value.value);
            value.comments = property.value.comments;
          } else {
            value = j.jsxExpressionContainer(property.value);
          }

          let jsxIdentifier;
          if (property.key.type === 'Literal') {
            jsxIdentifier = j.jsxIdentifier(property.key.value);
          } else {
            jsxIdentifier = j.jsxIdentifier(property.key.name);
          }
          jsxIdentifier.comments = property.key.comments;

          const jsxAttribute = j.jsxAttribute(jsxIdentifier, value);
          jsxAttribute.comments = property.comments;
          return jsxAttribute;
        }
        return null;
      });

      return {
        attributes,
        extraComments: expression.comments || []
      };
    } else if (expression.type === 'Literal' && expression.value === null) {
      return {
        attributes: [],
        extraComments: expression.comments || []
      };
    } else {
      throw new Error(`Unexpected attribute of type "${expression.type}"`);
    }
  };

  const canConvertToJSXIdentifier = node =>
    (node.type === 'Literal' && typeof node.value === 'string') ||
    node.type === 'Identifier' ||
    (node.type === 'MemberExpression' &&
      !node.computed &&
      canConvertToJSXIdentifier(node.object) &&
      canConvertToJSXIdentifier(node.property));

  const jsxIdentifierFor = node => {
    let identifier;
    let comments = node.comments || [];
    if (node.type === 'Literal') {
      identifier = j.jsxIdentifier(node.value);
    } else if (node.type === 'MemberExpression') {
      let {
        identifier: objectIdentifier,
        comments: objectComments
      } = jsxIdentifierFor(node.object);
      let {
        identifier: propertyIdentifier,
        comments: propertyComments
      } = jsxIdentifierFor(node.property);
      identifier = j.jsxMemberExpression(objectIdentifier, propertyIdentifier);
      comments.push(...objectComments, ...propertyComments);
    } else {
      identifier = j.jsxIdentifier(node.name);
    }
    return { identifier, comments };
  };

  const isCapitalizationInvalid = node =>
    (node.type === 'Literal' && !/^[a-z]/.test(node.value)) ||
    (node.type === 'Identifier' && /^[a-z]/.test(node.name));

  const convertNodeToJSX = node => {
    const comments = node.value.comments || [];
    const { callee } = node.value;
    for (const calleeNode of [callee, callee.object, callee.property]) {
      for (const comment of calleeNode.comments || []) {
        comment.leading = true;
        comment.trailing = false;
        comments.push(comment);
      }
    }

    const args = node.value.arguments;

    if (
      isCapitalizationInvalid(args[0]) ||
      !canConvertToJSXIdentifier(args[0])
    ) {
      return node.value;
    }

    const {
      identifier: jsxIdentifier,
      comments: identifierComments
    } = jsxIdentifierFor(args[0]);
    const props = args[1];

    const { attributes, extraComments } = convertExpressionToJSXAttributes(
      props
    );

    for (const comment of [...identifierComments, ...extraComments]) {
      comment.leading = false;
      comment.trailing = true;
      comments.push(comment);
    }

    const children = args.slice(2).map((child, index) => {
      if (
        child.type === 'Literal' &&
        typeof child.value === 'string' &&
        !child.comments &&
        child.value !== '' &&
        child.value.trim() === child.value
      ) {
        return j.jsxText(encodeJSXTextValue(child.value));
      } else if (
        child.type === 'CallExpression' &&
        child.callee.object &&
        child.callee.object.name === 'React' &&
        child.callee.property.name === 'createElement'
      ) {
        const jsxChild = convertNodeToJSX(node.get('arguments', index + 2));
        if (
          jsxChild.type !== 'JSXElement' ||
          (jsxChild.comments || []).length > 0
        ) {
          return j.jsxExpressionContainer(jsxChild);
        } else {
          return jsxChild;
        }
      } else if (child.type === 'SpreadElement') {
        return j.jsxExpressionContainer(child.argument);
      } else {
        return j.jsxExpressionContainer(child);
      }
    });

    const openingElement = j.jsxOpeningElement(jsxIdentifier, attributes);

    if (children.length) {
      const endIdentifier = Object.assign({}, jsxIdentifier, { comments: [] });
      // Add text newline nodes between elements so recast formats one child per
      // line instead of all children on one line.
      const paddedChildren = [j.jsxText('\n')];
      for (const child of children) {
        paddedChildren.push(child, j.jsxText('\n'));
      }
      const element = j.jsxElement(
        openingElement,
        j.jsxClosingElement(endIdentifier),
        paddedChildren
      );
      element.comments = comments;
      return element;
    } else {
      openingElement.selfClosing = true;
      const element = j.jsxElement(openingElement);
      element.comments = comments;
      return element;
    }
  };

  if (options['explicit-require'] === false || ReactUtils.hasReact(root)) {
    const mutations = root
      .find(j.CallExpression, {
        callee: {
          object: {
            name: 'React'
          },
          property: {
            name: 'createElement'
          }
        }
      })
      .replaceWith(convertNodeToJSX)
      .size();

    if (mutations) {
      return root.toSource(printOptions);
    }
  }

  return null;
};