diff --git a/npm-react-codemod/README.md b/npm-react-codemod/README.md index 5ebb3c21cc..69c9cce41c 100644 --- a/npm-react-codemod/README.md +++ b/npm-react-codemod/README.md @@ -11,6 +11,17 @@ APIs. * Use the `-d` option for a dry-run and use `-p` to print the output for comparison +### Included Scripts + +`findDOMNode.js` 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 +update calls on the component instance or its refs. You can use this script to +update most calls to `getDOMNode` and then manually go through the remaining +calls. + + * `react-codemod findDOMNode ` + ### Recast Options Options to [recast](https://github.com/benjamn/recast)'s printer can be provided diff --git a/npm-react-codemod/test/__tests__/transform-tests.js b/npm-react-codemod/test/__tests__/transform-tests.js index 7d73c7f060..d551036d07 100644 --- a/npm-react-codemod/test/__tests__/transform-tests.js +++ b/npm-react-codemod/test/__tests__/transform-tests.js @@ -34,6 +34,9 @@ function test(transformName, testFileName, options) { describe('Transform Tests', () => { + it('transforms the "findDOMNode" tests correctly', () => { + test('findDOMNode', 'findDOMNode-test'); + }); }); diff --git a/npm-react-codemod/test/findDOMNode-test.js b/npm-react-codemod/test/findDOMNode-test.js new file mode 100644 index 0000000000..fbdb0a95f6 --- /dev/null +++ b/npm-react-codemod/test/findDOMNode-test.js @@ -0,0 +1,34 @@ +'use strict'; + +var React = require('React'); + +var Composer = React.createClass({ + componentWillReceiveProps: function(nextProps) { + this.getDOMNode(); + return foo(this.refs.input.getDOMNode()); + }, + + foo: function() { + var ref = 'foo'; + var element = this.refs[ref]; + var domNode = element.getDOMNode(); + }, + + bar: function() { + var thing = this.refs.foo; + thing.getDOMNode(); + }, + + foobar: function() { + passThisOn(this.refs.main.refs.list.getDOMNode()); + } +}); + +var SomeDialog = React.createClass({ + render: function() { + call(this.refs.SomeThing); + return ( +
+ ); + } +}); diff --git a/npm-react-codemod/test/findDOMNode-test.output.js b/npm-react-codemod/test/findDOMNode-test.output.js new file mode 100644 index 0000000000..45bfca5bbb --- /dev/null +++ b/npm-react-codemod/test/findDOMNode-test.output.js @@ -0,0 +1,34 @@ +'use strict'; + +var React = require('React'); + +var Composer = React.createClass({ + componentWillReceiveProps: function(nextProps) { + React.findDOMNode(this); + return foo(React.findDOMNode(this.refs.input)); + }, + + foo: function() { + var ref = 'foo'; + var element = this.refs[ref]; + var domNode = React.findDOMNode(element); + }, + + bar: function() { + var thing = this.refs.foo; + React.findDOMNode(thing); + }, + + foobar: function() { + passThisOn(React.findDOMNode(this.refs.main.refs.list)); + } +}); + +var SomeDialog = React.createClass({ + render: function() { + call(this.refs.SomeThing); + return ( +
+ ); + } +}); diff --git a/npm-react-codemod/transforms/findDOMNode.js b/npm-react-codemod/transforms/findDOMNode.js new file mode 100644 index 0000000000..0a7425b02d --- /dev/null +++ b/npm-react-codemod/transforms/findDOMNode.js @@ -0,0 +1,135 @@ +/*eslint-disable no-comma-dangle*/ + +'use strict'; + +function getDOMNodeToFindDOMNode(file, api, options) { + const j = api.jscodeshift; + + require('./utils/array-polyfills'); + const ReactUtils = require('./utils/ReactUtils')(j); + + const printOptions = options.printOptions || {quote: 'single'}; + const root = j(file.source); + + const createReactFindDOMNodeCall = arg => j.callExpression( + j.memberExpression( + j.identifier('React'), + j.identifier('findDOMNode'), + false + ), + [arg] + ); + + const updateRefCall = (path, refName) => { + j(path) + .find(j.CallExpression, { + callee: { + object: { + type: 'Identifier', + name: refName, + }, + property: { + type: 'Identifier', + name: 'getDOMNode', + }, + }, + }) + .forEach(callPath => j(callPath).replaceWith( + createReactFindDOMNodeCall(j.identifier(refName)) + )); + }; + + const updateToFindDOMNode = classPath => { + var sum = 0; + + // this.getDOMNode() + sum += j(classPath) + .find(j.CallExpression, { + callee: { + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'getDOMNode', + }, + }, + }) + .forEach(path => j(path).replaceWith( + createReactFindDOMNodeCall(j.thisExpression()) + )) + .size(); + + // this.refs.xxx.getDOMNode() or this.refs.xxx.refs.yyy.getDOMNode() + sum += j(classPath) + .find(j.MemberExpression, { + object: { + type: 'MemberExpression', + object: { + type: 'MemberExpression', + object: { + type: 'ThisExpression', + }, + property: { + type: 'Identifier', + name: 'refs', + }, + }, + }, + }) + .closest(j.CallExpression) + .filter(path => ( + path.value.callee.property && + path.value.callee.property.type === 'Identifier' && + path.value.callee.property.name === 'getDOMNode' + )) + .forEach(path => j(path).replaceWith( + createReactFindDOMNodeCall(path.value.callee.object) + )) + .size(); + + // someVariable.getDOMNode() wherre `someVariable = this.refs.xxx` + sum += j(classPath) + .findVariableDeclarators() + .filter(path => { + const init = path.value.init; + const value = init && init.object; + return ( + value && + value.type === 'MemberExpression' && + value.object && + value.object.type === 'ThisExpression' && + value.property && + value.property.type === 'Identifier' && + value.property.name === 'refs' && + init.property && + init.property.type === 'Identifier' + ); + }) + .forEach(path => j(path) + .closest(j.FunctionExpression) + .forEach(fnPath => updateRefCall(fnPath, path.value.id.name)) + ) + .size(); + + return sum > 0; + }; + + if ( + options['no-explicit-require'] || + ReactUtils.hasReact(root) + ) { + const didTransform = ReactUtils + .findReactCreateClass(root) + .filter(updateToFindDOMNode) + .size() > 0; + + if (didTransform) { + return root.toSource(printOptions) + '\n'; + } + } + + return null; +} + +module.exports = getDOMNodeToFindDOMNode;