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') {