Skip to content

Commit

Permalink
Add codemod for 'React.DOM.div' -> 'React.createElement("div"'
Browse files Browse the repository at this point in the history
Since we are deprecating the 'React.DOM.*' factories,[1] we want to
provide a codemod so that it's easier for folks to upgrade their code.

This will include an option to use 'React.createFactory' instead of
'React.createElement' in case there is a use case where that is
preferred.

There is one use of `React.DOM.*` that I have seen which is not covered
here - sometimes it has mistakenly been used for Flow typing. In the
cases I have found it is not proper syntax and doesn't seem like
something we should cover with this codemod.

[1]: facebook/react#9398
and facebook/react#8356
  • Loading branch information
flarnie committed Apr 20, 2017
1 parent 7a6bf0c commit aac13f8
Show file tree
Hide file tree
Showing 16 changed files with 388 additions and 0 deletions.
195 changes: 195 additions & 0 deletions transforms/React-DOM-to-react-dom-factories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* 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';

module.exports = function(file, api, options) {
const j = api.jscodeshift;
const root = j(file.source);

let hasModifications;

const DOMModuleName = 'DOM';

const isDOMSpecifier = specifier => (
specifier.imported &&
specifier.imported.name === DOMModuleName
);
const removeDeconstructedDOMStatement = (j, root) => {
let hasModifications = false;
// import {
// DOM,
// foo,
// } from 'react';
root
.find(j.ImportDeclaration)
.filter(path => (
path.node.specifiers.filter(isDOMSpecifier).length > 0 &&
path.node.source.value === 'react'
))
.forEach(path => {
hasModifications = true;

path.node.specifiers = path.node.specifiers.map(
specifier => {
if (specifier.imported && specifier.imported.name === DOMModuleName) {
return j.importSpecifier(j.identifier('createElement'));
} else {
return specifier;
}
}
);
});

root
.find(j.ObjectPattern)
.filter(path => (
path.parent.node.init &&
(
// const {
// Component,
// DOM,
// } = React;
path.parent.node.init.name === 'React'
||
(
// const {
// Component,
// DOM,
// } = require('react');
path.parent.node.init.type === 'CallExpression' &&
path.parent.node.init.callee.name === 'require' &&
path.parent.node.init.arguments[0].value === 'react'
)
) &&
path.node.properties.some(property => {
return property.key.name === DOMModuleName;
})
))
.forEach(path => {
hasModifications = true;

// Replace the DOM key with 'createElement'
path.node.properties = path.node.properties.map((property) => {
if (property.key.name === DOMModuleName) {
return j.identifier('createElement');
} else {
return property;
}
});
});
return hasModifications;
};

hasModifications =
removeDeconstructedDOMStatement(j, root) || hasModifications;

// is 'DOM'
const isDOMIdentifier = path => (
path.node.name === DOMModuleName &&
path.parent.parent.node.type === 'CallExpression'
);

// We update cases where DOM.div is being called
// eg 'foo = DOM.div('a'...'
// replace with 'foo = React.createElement('div', 'a'...'
function replaceDOMReferences(j, root) {
let hasModifications = false;

root
.find(j.Identifier)
.filter(isDOMIdentifier)
.forEach(path => {
hasModifications = true;

const DOMargs = path.parent.parent.node.arguments;
const DOMFactoryPath = path.parent.node.property;
const DOMFactoryType = DOMFactoryPath.name;

// DOM.div(... -> createElement(...
j(path.parent).replaceWith(
j.identifier('createElement')
);
// createElement(... -> createElement('div', ...
DOMargs.unshift(j.literal(DOMFactoryType));
});

return hasModifications;
}

// We only need to update 'DOM.div' syntax if there was a deconstructed
// reference to React.DOM
if (hasModifications) {
hasModifications = replaceDOMReferences(j, root) || hasModifications;
}

// is 'React.DOM'
const isReactDOMIdentifier = path => (
path.node.name === DOMModuleName &&
(
path.parent.node.type === 'MemberExpression' &&
path.parent.node.object.name === 'React'
)
);

// Update React.DOM references
// eg 'foo = React.DOM.div('a'...'
// replace with 'foo = React.createElement('div', 'a'...'
function replaceReactDOMReferences(j, root) {
let hasModifications = false;

root
.find(j.Identifier)
.filter(isReactDOMIdentifier)
.forEach(path => {
hasModifications = true;
const DOMargs = path.parent.parent.parent.node.arguments;
const DOMFactoryPath = path.parent.parent.node.property;
const DOMFactoryType = DOMFactoryPath.name;

// React.DOM.div(... -> DOM.div(...
/*
j(DOMFactoryPath).replaceWith(
j.identifier('createElement') // TODO; fix
);
*/
// React.DOM.div(... -> React.DOM.createElement(...
path.parent.parent.node.property = j.identifier('createElement');
// React.DOM.createElement(... -> React.createElement(...
j(path.parent).replaceWith(j.identifier('React'));
// React.createElement(... -> React.createElement('div'...
DOMargs.unshift(j.literal(DOMFactoryType));
});

return hasModifications;
}

function removeEmptyReactImport(j, root) {
root
.find(j.ImportDeclaration)
.filter(path => (
path.node.specifiers.length === 0 &&
path.node.source.value === 'react'
))
.replaceWith();
}

hasModifications = replaceReactDOMReferences(j, root) || hasModifications;

if (hasModifications) {
// we shouldn't really need this?
removeEmptyReactImport(j, root);
}

return hasModifications
? root.toSource({ quote: 'single' })
: null;

};
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const React = require('react');

class Hello extends React.Component {
render() {
return React.DOM.div(null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const React = require('react');

class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ReactDOM from 'ReactDOM';
import {
Component,
DOM
} from 'react';

class Hello extends Component {
render() {
return DOM.div(null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import ReactDOM from 'ReactDOM';
import {
Component,
createElement
} from 'react';

class Hello extends Component {
render() {
return createElement('div', null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const ReactDOM = require('ReactDOM');
const {
Component,
DOM
} = require('react');

class Hello extends Component {
render() {
return DOM.div(null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
const ReactDOM = require('ReactDOM');
const {
Component,
createElement
} = require('react');

class Hello extends Component {
render() {
return createElement('div', null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const React = require('react');
const ReactDOM = require('ReactDOM');
const {
Component,
DOM
} = React;

class Hello extends Component {
render() {
return DOM.div(null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const React = require('react');
const ReactDOM = require('ReactDOM');
const {
Component,
createElement
} = React;

class Hello extends Component {
render() {
return createElement('div', null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
let {DOM} = require('Free');
import {DOM} from 'Free';

const foo = DOM.div('a', 'b', 'c');
const foo = Free.DOM.div('a', 'b', 'c');

DOM = 'this is a test!';

foo.DOM = {};

foo.DOM.div = () => null;

const bar = foo.DOM.div('a', 'b', 'c');
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from 'react';

class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const React = require('react');

class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}

ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
Loading

0 comments on commit aac13f8

Please sign in to comment.