Support export default in the class codemod.
cpojer committed
Aug 13, 2015 at 19:08 UTC
989caa870d614458aee2f40fb08b2c1229a04248
5 files changed
+67
-48
packages/react-codemod/README.md
+4
-4
@@ -25,8 +25,8 @@ calls.
25
* `jscodeshift -t react/packages/react-codemod/transforms/findDOMNode.js <file>`
26
27
`pure-render-mixin` removes `PureRenderMixin` and inlines
28
-`shouldComponentUpdate` so that the ES6 class transform can pick up the React
29
-component and turn it into an ES6 class. NOTE: This currently only works if you
28
+`shouldComponentUpdate` so that the ES2015 class transform can pick up the React
29
+component and turn it into an ES2015 class. NOTE: This currently only works if you
30
are using the master version (>0.13.1) of React as it is using
31
`React.addons.shallowCompare`
32
@@ -36,7 +36,7 @@ are using the master version (>0.13.1) of React as it is using
36
namespaced name for the mixin. `mixins: [React.addons.PureRenderMixin]` will
37
not currently work.
38
39
-`class` transforms `React.createClass` calls into ES6 classes.
39
+`class` transforms `React.createClass` calls into ES2015 classes.
40
41
* `jscodeshift -t react/packages/react-codemod/transforms/class.js <file>`
42
* If `--no-super-class=true` is specified it will not extend
@@ -49,7 +49,7 @@ All 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
-### Explanation of the ES6 class transform
52
+### Explanation of the ES2015 class transform
53
54
* Ignore components with calls to deprecated APIs. This is very defensive, if
55
the script finds any identifiers called `isMounted`, `getDOMNode`,
packages/react-codemod/test/export-default-class-test.js
+4
@@ -9,6 +9,10 @@ export default React.createClass({
9
};
10
},
11
12
+ propTypes: {
13
+ foo: React.PropTypes.string,
14
+ },
15
+
16
render: function() {
17
return <div />;
18
}
packages/react-codemod/test/export-default-class-test.output.js
+5
-2
@@ -11,8 +11,11 @@ export default class extends React.Component {
11
};
12
}
13
14
+ static propTypes = {
15
+ foo: React.PropTypes.string,
16
+ };
17
+
18
render() {
19
return <div />;
20
}
17
-};
18
-
21
+}
packages/react-codemod/transforms/class.js
+42
-25
@@ -10,7 +10,7 @@
10
11
'use strict';
12
13
-function updateReactCreateClassToES6(file, api, options) {
13
+module.exports = (file, api, options) => {
14
const j = api.jscodeshift;
15
16
require('./utils/array-polyfills');
@@ -341,9 +341,9 @@ function updateReactCreateClassToES6(file, api, options) {
341
];
342
};
343
344
- const createES6Class = (
344
+ const createESClass = (
345
name,
346
- functions,
346
+ properties,
347
getInitialState,
348
autobindFunctions,
349
comments,
@@ -358,7 +358,7 @@ function updateReactCreateClassToES6(file, api, options) {
358
autobindFunctions,
359
shouldAddSuperClass
360
),
361
- functions.map(createMethodDefinition)
361
+ properties
362
)
363
),
364
shouldAddSuperClass ? j.memberExpression(
@@ -384,6 +384,17 @@ function updateReactCreateClassToES6(file, api, options) {
384
const createStaticAssignmentExpressions = (name, statics) =>
385
statics.map(staticProperty => createStaticAssignment(name, staticProperty));
386
387
+ const createStaticClassProperty = staticProperty =>
388
+ withComments(j.classProperty(
389
+ j.identifier(staticProperty.key.name),
390
+ staticProperty.value,
391
+ null,
392
+ true
393
+ ), staticProperty);
394
+
395
+ const createStaticClassProperties = statics =>
396
+ statics.map(createStaticClassProperty);
397
+
398
const hasSingleReturnStatement = value => (
399
value.type === 'FunctionExpression' &&
400
value.body &&
@@ -434,7 +445,7 @@ function updateReactCreateClassToES6(file, api, options) {
445
false
446
);
447
437
- const updateToES6Class = (classPath, shouldExtend, isModuleExports) => {
448
+ const updateToClass = (classPath, shouldExtend, type) => {
449
const specPath = ReactUtils.getReactCreateClassSpec(classPath);
450
const name = ReactUtils.getComponentName(classPath);
451
const statics = collectStatics(specPath);
@@ -448,16 +459,19 @@ function updateReactCreateClassToES6(file, api, options) {
459
name ? j.identifier(name) : createModuleExportsMemberExpression();
460
461
var path;
451
- if (isModuleExports) {
462
+ if (type == 'moduleExports' || type == 'exportDefault') {
463
path = ReactUtils.findReactCreateClassCallExpression(classPath);
464
} else {
465
path = j(classPath).closest(j.VariableDeclaration);
466
}
467
468
+ const properties =
469
+ (type == 'exportDefault') ? createStaticClassProperties(statics) : [];
470
+
471
path.replaceWith(
458
- createES6Class(
472
+ createESClass(
473
name,
460
- functions,
474
+ properties.concat(functions.map(createMethodDefinition)),
475
getInitialState,
476
autobindFunctions,
477
comments,
@@ -465,43 +479,46 @@ function updateReactCreateClassToES6(file, api, options) {
479
)
480
);
481
468
- const staticAssignments =
469
- createStaticAssignmentExpressions(staticName, statics);
470
- if (isModuleExports) {
471
- const body = root.get().value.body;
472
- body.push.apply(body, staticAssignments);
473
- } else {
474
- staticAssignments
475
- .forEach(expression => path = path.insertAfter(expression));
482
+ if (type == 'moduleExports' || type == 'var') {
483
+ const staticAssignments = createStaticAssignmentExpressions(
484
+ staticName,
485
+ statics
486
+ );
487
+ if (type == 'moduleExports') {
488
+ root.get().value.program.body.push(...staticAssignments);
489
+ } else {
490
+ path.insertAfter(staticAssignments.reverse());
491
+ }
492
}
493
};
494
495
if (
496
options['no-explicit-require'] || ReactUtils.hasReact(root)
497
) {
482
- const apply = (path, isModuleExports) =>
498
+ const apply = (path, type) =>
499
path
500
.filter(hasMixins)
501
.filter(callsDeprecatedAPIs)
486
- .forEach(classPath => updateToES6Class(
502
+ .forEach(classPath => updateToClass(
503
classPath,
504
!options['no-super-class'],
489
- isModuleExports
505
+ type
506
));
507
508
const didTransform = (
493
- apply(ReactUtils.findReactCreateClass(root), false).size() +
494
- apply(ReactUtils.findReactCreateClassModuleExports(root), true).size() +
495
- apply(ReactUtils.findReactCreateClassExportDefault(root), false).size()
509
+ apply(ReactUtils.findReactCreateClass(root), 'var')
510
+ .size() +
511
+ apply(ReactUtils.findReactCreateClassModuleExports(root), 'moduleExports')
512
+ .size() +
513
+ apply(ReactUtils.findReactCreateClassExportDefault(root), 'exportDefault')
514
+ .size()
515
) > 0;
516
517
if (didTransform) {
518
return root.toSource(printOptions);
519
}
501
-
520
+
521
}
522
523
return null;
524
}
506
-
507
-module.exports = updateReactCreateClassToES6;
packages/react-codemod/transforms/utils/ReactUtils.js
+12
-17
@@ -34,9 +34,9 @@ module.exports = function(j) {
34
source: {
35
type: 'Literal',
36
}
37
- }).filter((importDeclarator) => {
38
- return importDeclarator.value.source.value === module;
39
- }).size() === 1
37
+ })
38
+ .filter(declarator => declarator.value.source.value === module)
39
+ .size() === 1
40
41
const hasReact = path => (
42
hasModule(path, 'React') ||
@@ -56,19 +56,13 @@ module.exports = function(j) {
56
.findVariableDeclarators()
57
.filter(decl => findReactCreateClassCallExpression(decl).size() > 0);
58
59
- const findReactCreateClassExportDefault = path => {
60
- var collection = [];
61
- path
62
- .find(j.ExportDefaultDeclaration, {
63
- type: 'ExportDefaultDeclaration',
64
- declaration: {
65
- type: 'CallExpression',
66
- callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION
67
- }
68
- })
69
- .forEach((p) => collection.push(p.value.declaration))
70
- return j(collection);
71
- }
59
+ const findReactCreateClassExportDefault = path =>
60
+ path.find(j.ExportDefaultDeclaration, {
61
+ declaration: {
62
+ type: 'CallExpression',
63
+ callee: REACT_CREATE_CLASS_MEMBER_EXPRESSION
64
+ }
65
+ });
66
67
const findReactCreateClassModuleExports = path =>
68
path
@@ -129,7 +123,8 @@ module.exports = function(j) {
123
// ---------------------------------------------------------------------------
124
// Others
125
const getReactCreateClassSpec = classPath => {
132
- const spec = (classPath.value.init || classPath.value.right || classPath.value).arguments[0]
126
+ var {value} = classPath;
127
+ const spec = (value.init || value.right || value.declaration).arguments[0];
128
if (spec.type === 'ObjectExpression' && Array.isArray(spec.properties)) {
129
return spec;
130
}