diff --git a/test/__tests__/create-element-to-jsx-test.js b/test/__tests__/create-element-to-jsx-test.js
index 47fd1698..807be4e6 100644
--- a/test/__tests__/create-element-to-jsx-test.js
+++ b/test/__tests__/create-element-to-jsx-test.js
@@ -38,6 +38,22 @@ describe('create-element-to-jsx', () => {
test('create-element-to-jsx', 'create-element-to-jsx-literal-prop');
test('create-element-to-jsx', 'create-element-to-jsx-call-as-children');
+
+ test('create-element-to-jsx', 'create-element-to-jsx-react-spread');
+
+ test('create-element-to-jsx', 'create-element-to-jsx-object-assign');
+ });
+
+ it('raises when it does not recognize a property type', () => {
+ const jscodeshift = require('jscodeshift');
+ const transform = require('../../transforms/create-element-to-jsx');
+ const source = `
+ var React = require("react/addons");
+ React.createElement("foo", 1)
+ `;
+
+ expect(() => transform({source}, {jscodeshift}, {}))
+ .toThrow('Unexpected attribute of type "Literal"');
});
});
diff --git a/test/create-element-to-jsx-object-assign.js b/test/create-element-to-jsx-object-assign.js
new file mode 100644
index 00000000..b0f2f9b5
--- /dev/null
+++ b/test/create-element-to-jsx-object-assign.js
@@ -0,0 +1,7 @@
+var React = require('react/addons');
+
+React.createElement(Foo, Object.assign({
+ 'foo': 'bar'
+}, props, {
+ 'bar': 'foo'
+}));
diff --git a/test/create-element-to-jsx-object-assign.output.js b/test/create-element-to-jsx-object-assign.output.js
new file mode 100644
index 00000000..d71e1feb
--- /dev/null
+++ b/test/create-element-to-jsx-object-assign.output.js
@@ -0,0 +1,3 @@
+var React = require('react/addons');
+
+;
diff --git a/test/create-element-to-jsx-react-spread.js b/test/create-element-to-jsx-react-spread.js
new file mode 100644
index 00000000..ab14fbc4
--- /dev/null
+++ b/test/create-element-to-jsx-react-spread.js
@@ -0,0 +1,7 @@
+var React = require('react/addons');
+
+React.createElement(Foo, React.__spread({
+ 'foo': 'bar'
+}, props, {
+ 'bar': 'foo'
+}));
diff --git a/test/create-element-to-jsx-react-spread.output.js b/test/create-element-to-jsx-react-spread.output.js
new file mode 100644
index 00000000..d71e1feb
--- /dev/null
+++ b/test/create-element-to-jsx-react-spread.output.js
@@ -0,0 +1,3 @@
+var React = require('react/addons');
+
+;
diff --git a/transforms/create-element-to-jsx.js b/transforms/create-element-to-jsx.js
index f8ae3498..b84e3a3a 100644
--- a/transforms/create-element-to-jsx.js
+++ b/transforms/create-element-to-jsx.js
@@ -3,43 +3,61 @@ module.exports = function(file, api, options) {
const root = j(file.source);
const ReactUtils = require('./utils/ReactUtils')(j);
- const convertObjectExpressionToJSXAttributes = (objectExpression) => {
- if (objectExpression.type === 'Identifier') {
- return [j.jsxSpreadAttribute(objectExpression)];
- }
-
- if (!objectExpression.properties) {
- return [];
- }
-
- const attributes = objectExpression.properties.map((property) => {
- if (property.type === 'SpreadProperty') {
- return j.jsxSpreadAttribute(property.argument);
- } else if (property.type === 'Property') {
- const propertyValueType = property.value.type;
-
- let value;
- if (propertyValueType === 'Literal' && typeof property.value.value === 'string') {
- value = j.literal(property.value.value);
- } else {
- value = j.jsxExpressionContainer(property.value);
- }
+ const convertExpressionToJSXAttributes = (expression) => {
+ 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';
+
+ if (expression.type === 'Identifier') {
+ return [j.jsxSpreadAttribute(expression)];
+ } else if (isReactSpread || isObjectAssign) {
+ const jsxAttributes = [];
+
+ expression.arguments.forEach((expression) =>
+ jsxAttributes.push(...convertExpressionToJSXAttributes(expression))
+ );
- let propertyKeyName;
- if (property.key.type === 'Literal') {
- propertyKeyName = property.key.value;
- } else {
- propertyKeyName = property.key.name;
+ return jsxAttributes;
+ } else if (expression.type === 'ObjectExpression') {
+ const attributes = expression.properties.map((property) => {
+ if (property.type === 'SpreadProperty') {
+ return j.jsxSpreadAttribute(property.argument);
+ } else if (property.type === 'Property') {
+ const propertyValueType = property.value.type;
+
+ let value;
+ if (propertyValueType === 'Literal' && typeof property.value.value === 'string') {
+ value = j.literal(property.value.value);
+ } else {
+ value = j.jsxExpressionContainer(property.value);
+ }
+
+ let propertyKeyName;
+ if (property.key.type === 'Literal') {
+ propertyKeyName = property.key.value;
+ } else {
+ propertyKeyName = property.key.name;
+ }
+
+ return j.jsxAttribute(
+ j.jsxIdentifier(propertyKeyName),
+ value
+ );
}
+ });
- return j.jsxAttribute(
- j.jsxIdentifier(propertyKeyName),
- value
- );
- }
- });
-
- return attributes;
+ return attributes;
+ } else if (expression.type === 'Literal' && expression.value === null) {
+ return [];
+ } else {
+ throw new Error(`Unexpected attribute of type "${expression.type}"`);
+ }
};
const convertNodeToJSX = (node) => {
@@ -49,7 +67,7 @@ module.exports = function(file, api, options) {
const elementName = elementType === 'Literal' ? args[0].value : args[0].name;
const props = args[1];
- const attributes = convertObjectExpressionToJSXAttributes(props);
+ const attributes = convertExpressionToJSXAttributes(props);
const children = node.value.arguments.slice(2).map((child, index) => {
if (child.type === 'Literal' && typeof child.value === 'string') {