diff --git a/README.md b/README.md index e3a972c7..19499de1 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,10 @@ APIs. ### Included Scripts +`create-element-to-jsx` converts calls to `React.createElement` into JSX elements. + + * `jscodeshift -t react-codemod/transforms/create-element-to-jsx.js ` + `findDOMNode` updates `this.getDOMNode()` or `this.refs.foo.getDOMNode()` calls inside of `React.createClass` components to `React.findDOMNode(foo)`. Note that it will only look at code inside of `React.createClass` calls and only diff --git a/test/__tests__/create-element-to-jsx-test.js b/test/__tests__/create-element-to-jsx-test.js new file mode 100644 index 00000000..6fb53724 --- /dev/null +++ b/test/__tests__/create-element-to-jsx-test.js @@ -0,0 +1,29 @@ +/** + * Copyright 2013-2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +'use strict'; + +describe('create-element-to-jsx', () => { + + it('transforms correctly', () => { + test('create-element-to-jsx', 'create-element-to-jsx-single-element'); + + test('create-element-to-jsx', 'create-element-to-jsx-props'); + + test('create-element-to-jsx', 'create-element-to-jsx-children-literal'); + + test('create-element-to-jsx', 'create-element-to-jsx-children'); + + test('create-element-to-jsx', 'create-element-to-jsx-spread'); + + test('create-element-to-jsx', 'create-element-to-jsx-no-react'); + }); + +}); diff --git a/test/create-element-to-jsx-children-literal.js b/test/create-element-to-jsx-children-literal.js new file mode 100644 index 00000000..c8500fda --- /dev/null +++ b/test/create-element-to-jsx-children-literal.js @@ -0,0 +1,3 @@ +var React = require('React'); + +React.createElement('div', null, 'foo'); diff --git a/test/create-element-to-jsx-children-literal.output.js b/test/create-element-to-jsx-children-literal.output.js new file mode 100644 index 00000000..bd4cc71f --- /dev/null +++ b/test/create-element-to-jsx-children-literal.output.js @@ -0,0 +1,3 @@ +var React = require('React'); + +
foo
; diff --git a/test/create-element-to-jsx-children.js b/test/create-element-to-jsx-children.js new file mode 100644 index 00000000..5d32fa99 --- /dev/null +++ b/test/create-element-to-jsx-children.js @@ -0,0 +1,12 @@ +var React = require('React'); + +var a = React.createElement( + Foo, + null, + React.createElement('div', { foo: 'bar' }), + React.createElement( + 'span', + null, + 'blah' + ) +); diff --git a/test/create-element-to-jsx-children.output.js b/test/create-element-to-jsx-children.output.js new file mode 100644 index 00000000..1312ee01 --- /dev/null +++ b/test/create-element-to-jsx-children.output.js @@ -0,0 +1,3 @@ +var React = require('React'); + +var a =
blah; diff --git a/test/create-element-to-jsx-no-react.js b/test/create-element-to-jsx-no-react.js new file mode 100644 index 00000000..aa81e70b --- /dev/null +++ b/test/create-element-to-jsx-no-react.js @@ -0,0 +1,3 @@ +var React = require('foo'); + +React.createElement(Foo, 'la'); diff --git a/test/create-element-to-jsx-no-react.output.js b/test/create-element-to-jsx-no-react.output.js new file mode 100644 index 00000000..e69de29b diff --git a/test/create-element-to-jsx-props.js b/test/create-element-to-jsx-props.js new file mode 100644 index 00000000..b8230e84 --- /dev/null +++ b/test/create-element-to-jsx-props.js @@ -0,0 +1,6 @@ +var React = require('React'); + +function foo() { + var a = React.createElement(Foo, { foo: 'bar', bar: this.state.baz }); + var b = React.createElement('div', { foo: 'bar', bar: this.state.baz }); +} diff --git a/test/create-element-to-jsx-props.output.js b/test/create-element-to-jsx-props.output.js new file mode 100644 index 00000000..97b6d5b7 --- /dev/null +++ b/test/create-element-to-jsx-props.output.js @@ -0,0 +1,6 @@ +var React = require('React'); + +function foo() { + var a = ; + var b =
; +} diff --git a/test/create-element-to-jsx-single-element.js b/test/create-element-to-jsx-single-element.js new file mode 100644 index 00000000..5b0a9614 --- /dev/null +++ b/test/create-element-to-jsx-single-element.js @@ -0,0 +1,4 @@ +var React = require('React'); + +var a = React.createElement(Foo, null); +var b = React.createElement('div', null); diff --git a/test/create-element-to-jsx-single-element.output.js b/test/create-element-to-jsx-single-element.output.js new file mode 100644 index 00000000..d68821d5 --- /dev/null +++ b/test/create-element-to-jsx-single-element.output.js @@ -0,0 +1,4 @@ +var React = require('React'); + +var a = ; +var b =
; diff --git a/test/create-element-to-jsx-spread.js b/test/create-element-to-jsx-spread.js new file mode 100644 index 00000000..87a7393e --- /dev/null +++ b/test/create-element-to-jsx-spread.js @@ -0,0 +1,3 @@ +var React = require('React'); + +React.createElement(Foo, {foo: 'bar', ...someObject}); diff --git a/test/create-element-to-jsx-spread.output.js b/test/create-element-to-jsx-spread.output.js new file mode 100644 index 00000000..bb9c69cc --- /dev/null +++ b/test/create-element-to-jsx-spread.output.js @@ -0,0 +1,3 @@ +var React = require('React'); + +; diff --git a/transforms/create-element-to-jsx.js b/transforms/create-element-to-jsx.js new file mode 100644 index 00000000..ad833470 --- /dev/null +++ b/transforms/create-element-to-jsx.js @@ -0,0 +1,86 @@ +module.exports = function(file, api, options) { + const j = api.jscodeshift; + const root = j(file.source); + const ReactUtils = require('./utils/ReactUtils')(j); + + const convertObjectExpressionToJSXAttributes = (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') { + value = j.literal(property.value.value); + } else if (propertyValueType === 'MemberExpression') { + value = j.jsxExpressionContainer(property.value); + } + + return j.jsxAttribute( + j.jsxIdentifier(property.key.name), + value + ); + } + }); + + return attributes; + }; + + const convertNodeToJSX = (node) => { + const args = node.value.arguments; + + const elementType = args[0].type; + const elementName = elementType === 'Literal' ? args[0].value : args[0].name; + const props = args[1]; + + const attributes = convertObjectExpressionToJSXAttributes(props); + + const children = node.value.arguments.slice(2).map((child, index) => { + if (child.type === 'Literal') { + return j.literal(child.value); + } + + return convertNodeToJSX(node.get('arguments', index + 2)); + }); + + const openingElement = j.jsxOpeningElement(j.jsxIdentifier(elementName), attributes); + + if (children.length) { + return j.jsxElement( + openingElement, + j.jsxClosingElement(j.jsxIdentifier(elementName)), + children + ); + } else { + openingElement.selfClosing = true; + return j.jsxElement(openingElement); + } + }; + + if (ReactUtils.hasReact(root)) { + const mutations = root + .find(j.CallExpression, { + callee: { + object: { + name: 'React', + }, + property: { + name: 'createElement', + }, + }, + }) + .replaceWith(convertNodeToJSX) + .size(); + + if (mutations) { + return root.toSource(); + } + } + + return null; +};