From d9d86189e8c0a5d9fcb42e1eb1e8741cc25e7f44 Mon Sep 17 00:00:00 2001 From: Remon Oldenbeuving Date: Thu, 10 Dec 2015 11:52:55 +0100 Subject: [PATCH 1/3] Added support for React.__spread and Object.assign in JSX transform --- test/__tests__/create-element-to-jsx-test.js | 4 ++++ test/create-element-to-jsx-object-assign.js | 7 +++++++ ...ate-element-to-jsx-object-assign.output.js | 3 +++ test/create-element-to-jsx-react-spread.js | 7 +++++++ ...eate-element-to-jsx-react-spread.output.js | 3 +++ transforms/create-element-to-jsx.js | 20 +++++++++++++++++++ 6 files changed, 44 insertions(+) create mode 100644 test/create-element-to-jsx-object-assign.js create mode 100644 test/create-element-to-jsx-object-assign.output.js create mode 100644 test/create-element-to-jsx-react-spread.js create mode 100644 test/create-element-to-jsx-react-spread.output.js diff --git a/test/__tests__/create-element-to-jsx-test.js b/test/__tests__/create-element-to-jsx-test.js index 47fd1698..56236bb7 100644 --- a/test/__tests__/create-element-to-jsx-test.js +++ b/test/__tests__/create-element-to-jsx-test.js @@ -38,6 +38,10 @@ 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'); }); }); 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..87bbe7d4 100644 --- a/transforms/create-element-to-jsx.js +++ b/transforms/create-element-to-jsx.js @@ -8,6 +8,26 @@ module.exports = function(file, api, options) { return [j.jsxSpreadAttribute(objectExpression)]; } + const isReactSpread = objectExpression.type === 'CallExpression' && + objectExpression.callee.type === 'MemberExpression' && + objectExpression.callee.object.name === 'React' && + objectExpression.callee.property.name === '__spread'; + + const isObjectAssign = objectExpression.type === 'CallExpression' && + objectExpression.callee.type === 'MemberExpression' && + objectExpression.callee.object.name === 'Object' && + objectExpression.callee.property.name === 'assign'; + + if (isReactSpread || isObjectAssign) { + var jsxAttributes = []; + + objectExpression.arguments.forEach((objectExpression) => + jsxAttributes.push(...convertObjectExpressionToJSXAttributes(objectExpression)) + ); + + return jsxAttributes; + } + if (!objectExpression.properties) { return []; } From b82d80c3ccc0e260785d7f17ee6dfeb916a52b74 Mon Sep 17 00:00:00 2001 From: Remon Oldenbeuving Date: Thu, 10 Dec 2015 13:37:50 +0100 Subject: [PATCH 2/3] Throw error if JSX attribute isn't recognized While reviewing the output of the JSX transform I noticed that `React.__spread` attributes not only weren't ignored, they caused all attributes to be lost. I figured that it would probably be better if the transform errors when it doesn't recognize input. --- test/__tests__/create-element-to-jsx-test.js | 12 +++ transforms/create-element-to-jsx.js | 98 ++++++++++---------- 2 files changed, 60 insertions(+), 50 deletions(-) diff --git a/test/__tests__/create-element-to-jsx-test.js b/test/__tests__/create-element-to-jsx-test.js index 56236bb7..807be4e6 100644 --- a/test/__tests__/create-element-to-jsx-test.js +++ b/test/__tests__/create-element-to-jsx-test.js @@ -44,4 +44,16 @@ describe('create-element-to-jsx', () => { 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/transforms/create-element-to-jsx.js b/transforms/create-element-to-jsx.js index 87bbe7d4..9c65af80 100644 --- a/transforms/create-element-to-jsx.js +++ b/transforms/create-element-to-jsx.js @@ -3,63 +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)]; - } - - const isReactSpread = objectExpression.type === 'CallExpression' && - objectExpression.callee.type === 'MemberExpression' && - objectExpression.callee.object.name === 'React' && - objectExpression.callee.property.name === '__spread'; - - const isObjectAssign = objectExpression.type === 'CallExpression' && - objectExpression.callee.type === 'MemberExpression' && - objectExpression.callee.object.name === 'Object' && - objectExpression.callee.property.name === 'assign'; - - if (isReactSpread || isObjectAssign) { + 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) { var jsxAttributes = []; - objectExpression.arguments.forEach((objectExpression) => - jsxAttributes.push(...convertObjectExpressionToJSXAttributes(objectExpression)) + expression.arguments.forEach((expression) => + jsxAttributes.push(...convertExpressionToJSXAttributes(expression)) ); 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 + ); + } + }); - if (!objectExpression.properties) { + return attributes; + } else if (expression.type === 'Literal' && expression.value === null) { return []; + } else { + throw new Error(`Unexpected attribute of type "${expression.type}"`); } - - 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); - } - - let propertyKeyName; - if (property.key.type === 'Literal') { - propertyKeyName = property.key.value; - } else { - propertyKeyName = property.key.name; - } - - return j.jsxAttribute( - j.jsxIdentifier(propertyKeyName), - value - ); - } - }); - - return attributes; }; const convertNodeToJSX = (node) => { @@ -69,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') { From 9140737e0fb23257406497357d066527ab588050 Mon Sep 17 00:00:00 2001 From: Remon Oldenbeuving Date: Thu, 10 Dec 2015 13:46:27 +0100 Subject: [PATCH 3/3] Used `const` instead of `var` --- transforms/create-element-to-jsx.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transforms/create-element-to-jsx.js b/transforms/create-element-to-jsx.js index 9c65af80..b84e3a3a 100644 --- a/transforms/create-element-to-jsx.js +++ b/transforms/create-element-to-jsx.js @@ -17,7 +17,7 @@ module.exports = function(file, api, options) { if (expression.type === 'Identifier') { return [j.jsxSpreadAttribute(expression)]; } else if (isReactSpread || isObjectAssign) { - var jsxAttributes = []; + const jsxAttributes = []; expression.arguments.forEach((expression) => jsxAttributes.push(...convertExpressionToJSXAttributes(expression))