@samitouri / QOS-React-2 / commits / fb7e566b12

Add react-to-react-dom codemod

I ran it over Facebook so it probably works.

Ben Alpert committed Sep 1, 2015 at 17:36 UTC fb7e566b1294f919e02bc27272d741b0ebe12047
2 files changed +330 -1
packages/react-codemod/README.md
+10 -1
@@ -45,10 +45,19 @@ are using the master version (>0.13.1) of React as it is using
45 if you are using or planning to use [Flow](http://flowtype.org/). Also make
46 sure you are not calling `setState` anywhere outside of your component.
47
48 -All scripts take an option `--no-explicit-require=true` if you don't have a
48 +These three scripts take an option `--no-explicit-require=true` if you don't have a
49 `require('React')` statement in your code files and if you access React as a
50 global.
51
52 +`react-to-react-dom` updates code for the split of the `react` and `react-dom`
53 +packages (e.g., `React.render` to `ReactDOM.render`). It looks for
54 +`require('react')` and replaces the appropriate property accesses using
55 +`require('react-dom')`. It does not support ES6 modules or other non-CommonJS
56 +systems.
57 +
58 + * `jscodeshift -t react/packages/react-codemod/transforms/react-to-react-dom.js <file>`
59 + * After running the automated codemod, you may want to run a regex-based find-and-replace to remove extra whitespace between the added requires, such as `codemod.py -m -d src --extensions js '(var React\s*=\s*require\(.react.\);)\n\n(\s*var ReactDOM)' '\1\n\2'` using https://github.com/facebook/codemod.
60 +
61 ### Explanation of the ES2015 class transform
62
63 * Ignore components with calls to deprecated APIs. This is very defensive, if
packages/react-codemod/transforms/react-to-react-dom.js new
+320
@@ -0,0 +1,320 @@
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 +var CORE_PROPERTIES = [
14 + 'Children',
15 + 'Component',
16 + 'createElement',
17 + 'cloneElement',
18 + 'isValidElement',
19 + 'PropTypes',
20 + 'createClass',
21 + 'createFactory',
22 + 'createMixin',
23 + 'DOM',
24 + '__spread',
25 +];
26 +
27 +var DOM_PROPERTIES = [
28 + 'findDOMNode',
29 + 'render',
30 + 'unmountComponentAtNode',
31 + 'unstable_batchedUpdates',
32 + 'unstable_renderSubtreeIntoContainer',
33 +];
34 +
35 +var DOM_SERVER_PROPERTIES = [
36 + 'renderToString',
37 + 'renderToStaticMarkup',
38 +];
39 +
40 +function reportError(node, error) {
41 + throw new Error(
42 + `At ${node.loc.start.line}:${node.loc.start.column}: ${error}`
43 + );
44 +}
45 +
46 +function isRequire(path, moduleName) {
47 + return (
48 + path.value.type === 'CallExpression' &&
49 + path.value.callee.type === 'Identifier' &&
50 + path.value.callee.name === 'require' &&
51 + path.value.arguments.length === 1 &&
52 + path.value.arguments[0].type === 'Literal' &&
53 + path.value.arguments[0].value === moduleName
54 + );
55 +}
56 +
57 +module.exports = function(file, api) {
58 + var j = api.jscodeshift;
59 + var root = j(file.source);
60 +
61 + [
62 + ['React', 'ReactDOM', 'ReactDOMServer'],
63 + ['react', 'react-dom', 'react-dom/server'],
64 + ].forEach(function(pair) {
65 + var coreModuleName = pair[0];
66 + var domModuleName = pair[1];
67 + var domServerModuleName = pair[2];
68 +
69 + var domAlreadyDeclared = false;
70 + var domServerAlreadyDeclared = false;
71 +
72 + var coreRequireDeclarator;
73 + root
74 + .find(j.CallExpression)
75 + .filter(p => isRequire(p, coreModuleName))
76 + .forEach(p => {
77 + if (p.parent.value.type === 'VariableDeclarator') {
78 + if (p.parent.value.id.type === 'ObjectPattern') {
79 + var pattern = p.parent.value.id;
80 + var all = pattern.properties.every(function(prop) {
81 + if (prop.key.type === 'Identifier') {
82 + var name = prop.key.name;
83 + return CORE_PROPERTIES.indexOf(name) !== -1;
84 + }
85 + return false;
86 + });
87 + if (all) {
88 + // var {PropTypes} = require('React'); so leave alone
89 + return;
90 + }
91 + }
92 + if (coreRequireDeclarator) {
93 + reportError(
94 + p.value,
95 + 'Multiple declarations of React'
96 + );
97 + }
98 + if (p.parent.value.id.type !== 'Identifier') {
99 + reportError(
100 + p.value,
101 + 'Unexpected destructuring in require of ' + coreModuleName
102 + );
103 + }
104 + var name = p.parent.value.id.name;
105 + var scope = p.scope.lookup(name);
106 + if (scope.declares('ReactDOM')) {
107 + console.log('Using existing ReactDOM var in ' + file.path);
108 + domAlreadyDeclared = true;
109 + }
110 + if (scope.declares('ReactDOMServer')) {
111 + console.log('Using existing ReactDOMServer var in ' + file.path);
112 + domServerAlreadyDeclared = true;
113 + }
114 + coreRequireDeclarator = p.parent;
115 + } else if (p.parent.value.type === 'AssignmentExpression') {
116 + if (p.parent.value.left.type !== 'Identifier') {
117 + reportError(
118 + p.value,
119 + 'Unexpected destructuring in require of ' + coreModuleName
120 + );
121 + }
122 + var name = p.parent.value.left.name;
123 + var scope = p.scope.lookup(name);
124 + var reactBindings = scope.getBindings()[name];
125 + if (reactBindings.length !== 1) {
126 + throw new Error(
127 + 'Unexpected number of bindings: ' + reactBindings.length
128 + );
129 + }
130 + coreRequireDeclarator = reactBindings[0].parent;
131 + if (coreRequireDeclarator.value.init &&
132 + !isRequire(coreRequireDeclarator.get('init'), coreModuleName)) {
133 + reportError(
134 + coreRequireDeclarator.value,
135 + 'Unexpected initialization of ' + coreModuleName
136 + );
137 + }
138 + if (scope.declares('ReactDOM')) {
139 + console.log('Using existing ReactDOM var in ' + file.path);
140 + domAlreadyDeclared = true;
141 + }
142 + if (scope.declares('ReactDOMServer')) {
143 + console.log('Using existing ReactDOMServer var in ' + file.path);
144 + domServerAlreadyDeclared = true;
145 + }
146 + }
147 + });
148 + if (!coreRequireDeclarator) {
149 + return;
150 + }
151 +
152 + if (!domAlreadyDeclared &&
153 + root.find(j.Identifier, {name: 'ReactDOM'}).size() > 0) {
154 + throw new Error(
155 + 'ReactDOM is already defined in a different scope than React'
156 + );
157 + }
158 + if (!domServerAlreadyDeclared &&
159 + root.find(j.Identifier, {name: 'ReactDOMServer'}).size() > 0) {
160 + throw new Error(
161 + 'ReactDOMServer is already defined in a different scope than React'
162 + );
163 + }
164 +
165 + var coreName = coreRequireDeclarator.value.id.name;
166 +
167 + var processed = new Set();
168 + var requireAssignments = [];
169 + var coreUses = 0;
170 + var domUses = 0;
171 + var domServerUses = 0;
172 +
173 + root
174 + .find(j.Identifier, {name: coreName})
175 + .forEach(p => {
176 + if (processed.has(p.value)) {
177 + // https://github.com/facebook/jscodeshift/issues/36
178 + return;
179 + }
180 + processed.add(p.value);
181 + if (p.parent.value.type === 'MemberExpression' ||
182 + p.parent.value.type === 'QualifiedTypeIdentifier') {
183 + var left;
184 + var right;
185 + if (p.parent.value.type === 'MemberExpression') {
186 + left = p.parent.value.object;
187 + right = p.parent.value.property;
188 + } else {
189 + left = p.parent.value.qualification;
190 + right = p.parent.value.id;
191 + }
192 + if (left === p.value) {
193 + // React.foo (or React[foo])
194 + if (right.type === 'Identifier') {
195 + var name = right.name;
196 + if (CORE_PROPERTIES.indexOf(name) !== -1) {
197 + coreUses++;
198 + } else if (DOM_PROPERTIES.indexOf(name) !== -1) {
199 + domUses++;
200 + j(p).replaceWith(j.identifier('ReactDOM'));
201 + } else if (DOM_SERVER_PROPERTIES.indexOf(name) !== -1) {
202 + domServerUses++;
203 + j(p).replaceWith(j.identifier('ReactDOMServer'));
204 + } else {
205 + throw new Error('Unknown property React.' + name);
206 + }
207 + }
208 + } else if (right === p.value) {
209 + // foo.React, no need to transform
210 + } else {
211 + throw new Error('unimplemented');
212 + }
213 + } else if (p.parent.value.type === 'VariableDeclarator') {
214 + if (p.parent.value.id === p.value) {
215 + // var React = ...;
216 + } else if (p.parent.value.init === p.value) {
217 + // var ... = React;
218 + var pattern = p.parent.value.id;
219 + if (pattern.type === 'ObjectPattern') {
220 + // var {PropTypes} = React;
221 + // Most of these cases will just be looking at {PropTypes} so this
222 + // is usually a no-op.
223 + var coreProperties = [];
224 + var domProperties = [];
225 + pattern.properties.forEach(function(prop) {
226 + if (prop.key.type === 'Identifier') {
227 + var key = prop.key.name;
228 + if (CORE_PROPERTIES.indexOf(key) !== -1) {
229 + coreProperties.push(prop);
230 + } else if (DOM_PROPERTIES.indexOf(key) !== -1) {
231 + domProperties.push(prop);
232 + } else {
233 + throw new Error(
234 + 'Unknown property React.' + key + ' while destructuring'
235 + );
236 + }
237 + } else {
238 + throw new Error('unimplemented');
239 + }
240 + });
241 + var domDeclarator = j.variableDeclarator(
242 + j.objectPattern(domProperties),
243 + j.identifier('ReactDOM')
244 + );
245 + if (coreProperties.length && !domProperties.length) {
246 + // nothing to do
247 + coreUses++;
248 + } else if (domProperties.length && !coreProperties.length) {
249 + domUses++;
250 + j(p.parent).replaceWith(domDeclarator);
251 + } else {
252 + coreUses++;
253 + domUses++;
254 + var decl = j(p).closest(j.VariableDeclaration);
255 + decl.insertAfter(j.variableDeclaration(
256 + decl.get().value.kind,
257 + [domDeclarator]
258 + ));
259 + }
260 + } else {
261 + throw new Error('unimplemented');
262 + }
263 + } else {
264 + throw new Error('unimplemented');
265 + }
266 + } else if (p.parent.value.type === 'AssignmentExpression') {
267 + if (p.parent.value.left === p.value) {
268 + if (isRequire(p.parent.get('right'), coreModuleName)) {
269 + requireAssignments.push(p.parent);
270 + } else {
271 + reportError(
272 + p.parent.value,
273 + 'Unexpected assignment to ' + coreModuleName
274 + );
275 + }
276 + } else {
277 + throw new Error('unimplemented');
278 + }
279 + } else {
280 + reportError(p.value, 'unimplemented ' + p.parent.value.type);
281 + }
282 + });
283 +
284 + coreUses += root.find(j.JSXElement).size();
285 +
286 + function insertRequire(name, path) {
287 + var req = j.callExpression(
288 + j.identifier('require'),
289 + [j.literal(path)]
290 + );
291 + requireAssignments.forEach(function(requireAssignment) {
292 + requireAssignment.parent.insertAfter(
293 + j.expressionStatement(
294 + j.assignmentExpression('=', j.identifier(name), req)
295 + )
296 + );
297 + });
298 + coreRequireDeclarator.parent.insertAfter(j.variableDeclaration(
299 + coreRequireDeclarator.parent.value.kind,
300 + [j.variableDeclarator(
301 + j.identifier(name),
302 + coreRequireDeclarator.value.init ? req : null
303 + )]
304 + ));
305 + }
306 +
307 + if (domServerUses > 0 && !domServerAlreadyDeclared) {
308 + insertRequire('ReactDOMServer', domServerModuleName);
309 + }
310 + if (domUses > 0 && !domAlreadyDeclared) {
311 + insertRequire('ReactDOM', domModuleName);
312 + }
313 + if ((domUses > 0 || domServerUses > 0) && coreUses === 0) {
314 + j(coreRequireDeclarator).remove();
315 + requireAssignments.forEach(r => j(r).remove());
316 + }
317 + });
318 +
319 + return root.toSource({quote: 'single'});
320 +};