Skip to content

Commit b480fb0

Browse files
committed
Add codemod for 'React.DOM.div' -> 'React.createElement("div"'
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
1 parent fb7a72b commit b480fb0

16 files changed

+333
-0
lines changed
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
/**
2+
* Copyright 2013-2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
'use strict';
12+
13+
module.exports = function(file, api, options) {
14+
const j = api.jscodeshift;
15+
const root = j(file.source);
16+
17+
let hasModifications;
18+
19+
const DOMModuleName = 'DOM';
20+
21+
const isDOMSpecifier = specifier => (
22+
specifier.imported &&
23+
specifier.imported.name === DOMModuleName
24+
);
25+
26+
/**
27+
* Replaces 'DOM' with 'createElement' in places where we grab 'DOM' out of
28+
* 'React' with destructuring.
29+
*/
30+
const replaceDestructuredDOMStatement = (j, root) => {
31+
let hasModifications = false;
32+
//---------
33+
// First update import statments. eg:
34+
// import {
35+
// DOM,
36+
// foo,
37+
// } from 'react';
38+
root
39+
.find(j.ImportDeclaration)
40+
.filter(path => (
41+
path.node.specifiers.filter(isDOMSpecifier).length > 0 &&
42+
path.node.source.value === 'react'
43+
))
44+
.forEach(path => {
45+
hasModifications = true;
46+
47+
// Replace the DOM key with 'createElement'
48+
path.node.specifiers = path.node.specifiers.map(
49+
specifier => {
50+
if (specifier.imported && specifier.imported.name === DOMModuleName) {
51+
return j.importSpecifier(j.identifier('createElement'));
52+
} else {
53+
return specifier;
54+
}
55+
}
56+
);
57+
});
58+
59+
//---------
60+
// Next update require statments.
61+
// This matches both
62+
// const {
63+
// Component,
64+
// DOM,
65+
// } = React;
66+
// and
67+
// const {
68+
// Component,
69+
// DOM,
70+
// } = require('react');
71+
root
72+
.find(j.ObjectPattern)
73+
.filter(path => (
74+
path.parent.node.init &&
75+
(
76+
// matches '} = React;'
77+
path.parent.node.init.name === 'React'
78+
||
79+
(
80+
// matches "} = require('react');"
81+
path.parent.node.init.type === 'CallExpression' &&
82+
path.parent.node.init.callee.name === 'require' &&
83+
path.parent.node.init.arguments[0].value === 'react'
84+
)
85+
) &&
86+
path.node.properties.some(property => {
87+
return property.key.name === DOMModuleName;
88+
})
89+
))
90+
.forEach(path => {
91+
hasModifications = true;
92+
93+
// Replace the DOM key with 'createElement'
94+
path.node.properties = path.node.properties.map((property) => {
95+
if (property.key.name === DOMModuleName) {
96+
return j.identifier('createElement');
97+
} else {
98+
return property;
99+
}
100+
});
101+
});
102+
return hasModifications;
103+
};
104+
105+
hasModifications =
106+
replaceDestructuredDOMStatement(j, root) || hasModifications;
107+
108+
const isDOMIdentifier = path => (
109+
path.node.name === DOMModuleName &&
110+
path.parent.parent.node.type === 'CallExpression'
111+
);
112+
113+
/**
114+
* Update cases where DOM.div is being called
115+
* eg 'foo = DOM.div('a'...'
116+
* replace with 'foo = createElement('div', 'a'...'
117+
*/
118+
function replaceDOMReferences(j, root) {
119+
let hasModifications = false;
120+
121+
root
122+
.find(j.Identifier)
123+
.filter(isDOMIdentifier)
124+
.forEach(path => {
125+
hasModifications = true;
126+
127+
const DOMargs = path.parent.parent.node.arguments;
128+
const DOMFactoryPath = path.parent.node.property;
129+
const DOMFactoryType = DOMFactoryPath.name;
130+
131+
// DOM.div(... -> createElement(...
132+
j(path.parent).replaceWith(
133+
j.identifier('createElement')
134+
);
135+
// createElement(... -> createElement('div', ...
136+
DOMargs.unshift(j.literal(DOMFactoryType));
137+
});
138+
139+
return hasModifications;
140+
}
141+
142+
// We only need to update 'DOM.div' syntax if there was a deconstructed
143+
// reference to React.DOM
144+
if (hasModifications) {
145+
hasModifications = replaceDOMReferences(j, root) || hasModifications;
146+
}
147+
148+
// matches 'React.DOM'
149+
const isReactDOMIdentifier = path => (
150+
path.node.name === DOMModuleName &&
151+
(
152+
path.parent.node.type === 'MemberExpression' &&
153+
path.parent.node.object.name === 'React'
154+
)
155+
);
156+
157+
/**
158+
* Update React.DOM references
159+
* eg 'foo = React.DOM.div('a'...'
160+
* replace with 'foo = React.createElement('div', 'a'...'
161+
*/
162+
function replaceReactDOMReferences(j, root) {
163+
let hasModifications = false;
164+
165+
root
166+
.find(j.Identifier)
167+
.filter(isReactDOMIdentifier)
168+
.forEach(path => {
169+
hasModifications = true;
170+
const DOMargs = path.parent.parent.parent.node.arguments;
171+
const DOMFactoryPath = path.parent.parent.node.property;
172+
const DOMFactoryType = DOMFactoryPath.name;
173+
174+
// React.DOM.div(... -> React.DOM.createElement(...
175+
path.parent.parent.node.property = j.identifier('createElement');
176+
// React.DOM.createElement(... -> React.createElement(...
177+
j(path.parent).replaceWith(j.identifier('React'));
178+
// React.createElement(... -> React.createElement('div'...
179+
DOMargs.unshift(j.literal(DOMFactoryType));
180+
});
181+
182+
return hasModifications;
183+
}
184+
185+
hasModifications = replaceReactDOMReferences(j, root) || hasModifications;
186+
187+
return hasModifications
188+
? root.toSource({ quote: 'single' })
189+
: null;
190+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const React = require('react');
2+
3+
class Hello extends React.Component {
4+
render() {
5+
return React.DOM.div(null, `Hello ${this.props.toWhat}`);
6+
}
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const React = require('react');
2+
3+
class Hello extends React.Component {
4+
render() {
5+
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
6+
}
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import ReactDOM from 'ReactDOM';
2+
import {
3+
Component,
4+
DOM
5+
} from 'react';
6+
7+
class Hello extends Component {
8+
render() {
9+
return DOM.div(null, `Hello ${this.props.toWhat}`);
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import ReactDOM from 'ReactDOM';
2+
import {
3+
Component,
4+
createElement
5+
} from 'react';
6+
7+
class Hello extends Component {
8+
render() {
9+
return createElement('div', null, `Hello ${this.props.toWhat}`);
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const ReactDOM = require('ReactDOM');
2+
const {
3+
Component,
4+
DOM
5+
} = require('react');
6+
7+
class Hello extends Component {
8+
render() {
9+
return DOM.div(null, `Hello ${this.props.toWhat}`);
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const ReactDOM = require('ReactDOM');
2+
const {
3+
Component,
4+
createElement
5+
} = require('react');
6+
7+
class Hello extends Component {
8+
render() {
9+
return createElement('div', null, `Hello ${this.props.toWhat}`);
10+
}
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const React = require('react');
2+
const ReactDOM = require('ReactDOM');
3+
const {
4+
Component,
5+
DOM
6+
} = React;
7+
8+
class Hello extends Component {
9+
render() {
10+
return DOM.div(null, `Hello ${this.props.toWhat}`);
11+
}
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const React = require('react');
2+
const ReactDOM = require('ReactDOM');
3+
const {
4+
Component,
5+
createElement
6+
} = React;
7+
8+
class Hello extends Component {
9+
render() {
10+
return createElement('div', null, `Hello ${this.props.toWhat}`);
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
let {DOM} = require('Free');
2+
import {DOM} from 'Free';
3+
4+
const foo = DOM.div('a', 'b', 'c');
5+
const bar = Free.DOM.div('a', 'b', 'c');
6+
7+
DOM = 'this is a test!';
8+
9+
foo.DOM = {};
10+
11+
foo.DOM.div = () => null;
12+
13+
const bar = foo.DOM.div('a', 'b', 'c');

transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-dom-from-other-libraries.output.js

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import React from 'react';
2+
3+
class Hello extends React.Component {
4+
render() {
5+
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
6+
}
7+
}

transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-import.output.js

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const React = require('react');
2+
3+
class Hello extends React.Component {
4+
render() {
5+
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
6+
}
7+
}

transforms/__testfixtures__/React-DOM-to-react-dom-factories/react-dom-no-change-require.output.js

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* Copyright 2013-2015, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*
9+
*/
10+
11+
'use strict';
12+
13+
const tests = [
14+
'react-dom-basic-case',
15+
'react-dom-deconstructed-import',
16+
'react-dom-deconstructed-require',
17+
'react-dom-deconstructed-require-part-two',
18+
'react-dom-no-change-import',
19+
'react-dom-no-change-require',
20+
'react-dom-no-change-dom-from-other-libraries',
21+
];
22+
23+
const defineTest = require('jscodeshift/dist/testUtils').defineTest;
24+
25+
describe('React-DOM-to-react-dom-factories', () => {
26+
tests.forEach(test =>
27+
defineTest(
28+
__dirname,
29+
'React-DOM-to-react-dom-factories',
30+
null,
31+
`React-DOM-to-react-dom-factories/${ test }`
32+
)
33+
);
34+
});

0 commit comments

Comments
 (0)