Skip to content

Class transform with property initializer #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 65 commits into from
Jul 8, 2016
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
2a509a6
added boilerplates
Jun 9, 2016
eacb1e1
renamed to avoid conflict
Jun 9, 2016
2c5b461
added a failing test
Jun 10, 2016
6d9e3cb
added initial working version
Jun 13, 2016
00790d4
added support for mixins
Jun 13, 2016
93e5931
added a test for ES6 export
Jun 13, 2016
a151336
renamed liftGetInitialState
Jun 13, 2016
20aafc3
changed to update the existing one instead
Jun 14, 2016
c2bda71
fixed arrow function return type
Jun 14, 2016
f5f6da0
temporally added npm shrinkwrap
Jun 14, 2016
f70dad2
changed initial state transformation
Jun 15, 2016
c136e9c
added react-addons-pure-render-mixin support
Jun 16, 2016
311f472
Revert "temporally added npm shrinkwrap"
Jun 17, 2016
747dc5b
dont sort and bind everything
Jun 18, 2016
c7136e2
prune unused requires safely and add support for primitives as class …
Jun 20, 2016
d4faea4
updated README
Jun 20, 2016
cb88509
updated constructor args (only use props/context when needed)
Jun 20, 2016
011371d
added support for static methods
Jun 21, 2016
c4c3c1a
dont bind getChildContext
Jun 21, 2016
3936f55
handle constructor arguments properly
Jun 22, 2016
ada3fb8
fixed incorrect behavior when mixins is a non-array value
Jun 22, 2016
0cfad0a
fix early returns in getInitialState
Jun 22, 2016
84f8b71
merged master and upgraded deps
Jun 22, 2016
5f7b1b2
WIP flow transformation; switched parser to Flow
Jun 23, 2016
a7f71a3
flow works now
Jun 23, 2016
231f629
added support for flow property initializers
Jun 23, 2016
b8bb77a
better way to detect early returns in getInitialState
Jun 23, 2016
e00677f
bail out if user uses getInitialState or getDefaultProps elsewhere
Jun 24, 2016
59e3671
bail out if arguments is found
Jun 24, 2016
4f64cc2
defer state property initializer evaluation when necessary
Jun 24, 2016
d67b6a8
no shadowing in constructor
Jun 24, 2016
3cc6d71
handle inner function declarations in getInitialState correctly
Jun 24, 2016
f06bcd6
fix lint errors
Jun 24, 2016
53730d9
displayName shouldn't show up twice
Jun 24, 2016
19b611a
fixed anonymous createClass
Jun 24, 2016
b86a89d
covered more flow edge cases
Jun 24, 2016
595bb97
handle nullable prop types correctly
Jun 25, 2016
5b058f8
always print parens for single arg arrow functions
Jun 27, 2016
b532b52
fixed edge case when class spec is not an object expression
Jun 27, 2016
da84faf
support literal keys in prop types
Jun 27, 2016
7214e92
added TypeParameter and NullTypeAnnotation to ast-types
Jun 28, 2016
bb15c72
renamed recast option to flowObjectCommas
Jun 28, 2016
9f1702b
no trailing comma for single-line flow object types
Jun 28, 2016
d7083fe
catch edge case where React.createClass() is called with nothing
Jun 29, 2016
ef23055
improved the logic of repositioning `state` property
Jun 29, 2016
46d13c0
added `pure-component` option
Jun 29, 2016
82d4a52
updated npm-shrinkwrap
Jun 29, 2016
f00fbb0
rename this.context to context in constructor
Jun 30, 2016
667e592
fixed how we identify shadowing issues
Jun 30, 2016
8b15535
updated README.md
Jun 30, 2016
953386a
use FlowFixMe for unrecognizable types
Jun 30, 2016
3e62530
retain getInitialState()'s return type when possible
Jul 1, 2016
3f700c1
stricter checking of referencing APIs that will be removed
Jul 1, 2016
51494ac
optional !== nullable
Jul 2, 2016
ab1e1a7
support void (undefined) in PropTypes.oneOf()
Jul 3, 2016
62978f4
annotate state type when it's inlined in the constructor
Jul 5, 2016
ee36b61
added 'remove-runtime-proptypes' option
Jul 5, 2016
1011552
changed inexplicit any to FlowFixMe
Jul 5, 2016
847de6d
Redesigned the way we start modding components.
Jul 6, 2016
b65424c
fixed default and rest params
Jul 7, 2016
738558c
retain top comments when pure-render-mixin is the first node of the body
Jul 7, 2016
3956765
fixed trailing comma for func rest params; updated recast
Jul 8, 2016
c39d3f7
handle top comments better
Jul 8, 2016
c02901c
OtherClass.getDefaultProps() -> OtherClass.defaultProps
Jul 8, 2016
1e9204b
updated README
Jul 8, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 15 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,52 +100,40 @@ guide](https://github.com/airbnb/javascript/blob/7684892951ef663e1c4e62ad57d662e
jscodeshift -t react-codemod/transforms/sort-comp.js <path>
```

### Explanation of the ES2015 class transform
### Explanation of the new ES2015 class transform with property initializers

* Ignore components with calls to deprecated APIs. This is very defensive, if
the script finds any identifiers called `isMounted`, `getDOMNode`,
`replaceProps`, `replaceState` or `setProps` it will skip the component.
* Replaces `var A = React.createClass(spec)` with
`class A (extends React.Component) {spec}`.
* Pulls out all statics defined on `statics` plus the few special cased
statics like `propTypes`, `childContextTypes`, `contextTypes` and
`displayName` and assigns them after the class is created.
`class A {}; A.foo = bar;`
* Takes `getDefaultProps` and inlines it as a static `defaultProps`.
statics like `propTypes`, `childContextTypes`, `contextTypes`, and
`displayName` and transforms them to `static` properties at the very top.
* Takes `getDefaultProps` and inlines it as `static defaultProps = ...;`.
If `getDefaultProps` is defined as a function with a single statement that
returns an object, it optimizes and transforms
`getDefaultProps() { return {foo: 'bar'}; }` into
`A.defaultProps = {foo: 'bar'};`. If `getDefaultProps` contains more than
`static defaultProps = {foo: 'bar'};`. If `getDefaultProps` contains more than
one statement it will transform into a self-invoking function like this:
`A.defaultProps = function() {…}();`. Note that this means that the function
`static defaultProps = (function() {…})();`. Note that this means that the function
will be executed only a single time per app-lifetime. In practice this
hasn't caused any issues – `getDefaultProps` should not contain any
side-effects.
* Binds class methods to the instance if methods are referenced without being
* If there exists references to `this.props` in `getInitialState` then it creates
a constructor and converts `getInitialState` to an assignment to `this.state`;
Otherwise it lifts `getInitialState` to a property initializer (`state = ...;`).
* Transforms class methods to arrow functions as class property initializers
(i.e., to bind them) if methods are referenced without being
called directly. It checks for `this.foo` but also traces variable
assignments like `var self = this; self.foo`. It does not bind functions
from the React API and ignores functions that are being called directly
(unless it is both called directly and passed around to somewhere else)
* Creates a constructor if necessary. This is necessary if either
`getInitialState` exists in the `React.createClass` spec OR if functions
need to be bound to the instance.
* When `--no-super-class` is passed it only optionally extends
from the React API (lifecycle methods) and ignores functions that are being
called directly (unless it is both called directly and passed around to
somewhere else).
* TODO When `--no-super-class` is passed it only optionally extends
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don’t forget to remove this TODO 😉

`React.Component` when `setState` or `forceUpdate` are used within the
class.

The constructor logic is as follows:

* Call `super(props, context)` if the base class needs to be extended.
* Bind all functions that are passed around,
like `this.foo = this.foo.bind(this)`
* Inline `getInitialState` (and remove `getInitialState` from the spec). It
also updates access of `this.props.foo` to `props.foo` and adds `props` as
argument to the constructor. This is necessary in the case when the base
class does not need to be extended where `this.props` will only be set by
React after the constructor has been run.
* Changes `return StateObject` from `getInitialState` to assign `this.state`
directly.

### Recast Options

Options to [recast](https://github.com/benjamn/recast)'s printer can be provided
Expand Down
12 changes: 12 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 80 additions & 0 deletions transforms/__testfixtures__/class-test2.input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';

var React = require('React');
var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
var FooBarMixin = require('FooBarMixin');

var ComponentWithNonSimpleInitialState = React.createClass({
statics: {
iDontKnowWhyYouNeedThis: true, // but comment it
foo: 'bar',
},

getInitialState: function() {
return {
counter: this.props.initialNumber + 1,
};
},

render: function() {
return (
<div>{this.state.counter}</div>
);
},
});

// Comment
module.exports = React.createClass({
propTypes: {
foo: React.PropTypes.bool,
},

getDefaultProps: function() {
return {
foo: 12,
};
},

getInitialState: function() { // non-simple getInitialState
var data = 'bar';
return {
bar: data,
};
},

render: function() {
return <div />;
},
});

var ComponentWithOnlyPureRenderMixin = React.createClass({
mixins: [ReactComponentWithPureRenderMixin],

getInitialState: function() {
return {
counter: this.props.initialNumber + 1,
};
},

render: function() {
return (
<div>{this.state.counter}</div>
);
},
});

var ComponentWithInconvertibleMixins = React.createClass({
mixins: [ReactComponentWithPureRenderMixin, FooBarMixin],

getInitialState: function() {
return {
counter: this.props.initialNumber + 1,
};
},

render: function() {
return (
<div>{this.state.counter}</div>
);
},
});
67 changes: 63 additions & 4 deletions transforms/__testfixtures__/class-test2.output.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,45 @@
'use strict';

var React = require('React');
var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
var FooBarMixin = require('FooBarMixin');

class ComponentWithNonSimpleInitialState extends React.Component {
static iDontKnowWhyYouNeedThis = true; // but comment it
static foo = 'bar';

constructor(props, context) {
super(props, context);

this.state = {
counter: props.initialNumber + 1,
};
}

render() {
return (
<div>{this.state.counter}</div>
);
}
}

// Comment
module.exports = class extends React.Component {
static propTypes = {
foo: React.PropTypes.bool,
};

static defaultProps = {
foo: 12,
};

constructor(props, context) {
super(props, context);
// non-simple getInitialState
var data = 'bar';

this.state = {
foo: 'bar',
bar: data,
};
}

Expand All @@ -17,6 +48,34 @@ module.exports = class extends React.Component {
}
};

module.exports.propTypes = {
foo: React.PropTypes.bool,
};
class ComponentWithOnlyPureRenderMixin extends React.PureComponent {
constructor(props, context) {
super(props, context);

this.state = {
counter: props.initialNumber + 1,
};
}

render() {
return (
<div>{this.state.counter}</div>
);
}
}

var ComponentWithInconvertibleMixins = React.createClass({
mixins: [ReactComponentWithPureRenderMixin, FooBarMixin],

getInitialState: function() {
return {
counter: this.props.initialNumber + 1,
};
},

render: function() {
return (
<div>{this.state.counter}</div>
);
},
});
19 changes: 10 additions & 9 deletions transforms/__testfixtures__/class.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ var MyComponent = React.createClass({
};
},

foo: function() {
foo: function(): void {
this.setState({heyoo: 24});
},
});

// Class comment
var MyComponent2 = React.createClass({
getDefaultProps: function() {
getDefaultProps: function(): Object {
return {a: 1};
},
foo: function() {
foo: function(): void {
pass(this.foo);
this.forceUpdate();
},
Expand All @@ -35,7 +35,7 @@ var MyComponent2 = React.createClass({
var MyComponent3 = React.createClass({
statics: {
someThing: 10,
foo: function() {},
funcThatDoesNothing: function(): void {},
},
propTypes: {
highlightEntities: React.PropTypes.bool,
Expand All @@ -47,7 +47,7 @@ var MyComponent3 = React.createClass({
},

getDefaultProps: function() {
foo();
unboundFunc();
return {
linkifyEntities: true,
highlightEntities: false,
Expand All @@ -61,11 +61,12 @@ var MyComponent3 = React.createClass({
};
},

_renderText: function(text) {
// comment here
_renderText: function(text: string): ReactElement<any> { // say something
return <Text text={text} />;
},

_renderImageRange: function(text, range) {
_renderImageRange: function(text: string, range): ReactElement<any> {
var image = range.image;
if (image) {
return (
Expand All @@ -79,10 +80,10 @@ var MyComponent3 = React.createClass({
},

autobindMe: function() {},
dontAutobindMe: function() {},
dontAutobindMe: function(): number { return 12; },

// Function comment
_renderRange: function(text, range) {
_renderRange: function(text: string, range, bla: Promise<string>): ReactElement<any> {
var self = this;

self.dontAutobindMe();
Expand Down
Loading