Skip to content

Commit 3cc6d71

Browse files
author
Keyan Zhang
committed
handle inner function declarations in getInitialState correctly
1 parent d67b6a8 commit 3cc6d71

File tree

3 files changed

+126
-25
lines changed

3 files changed

+126
-25
lines changed

transforms/__testfixtures__/class-initial-state.input.js

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React from 'React';
22

3-
/*
4-
* Multiline
5-
*/
3+
// only needs props
64
var MyComponent = React.createClass({
75
getInitialState: function() {
86
var x = this.props.foo;
@@ -21,7 +19,7 @@ var ComponentWithBothPropsAndContextAccess = React.createClass({
2119
name: React.PropTypes.string,
2220
},
2321

24-
// we actually _don't_ need a constructor here since this will be
22+
// we actually don't need a constructor here since this will be
2523
// initialized after a proper super(props, context) call.
2624
// in other words, `this` will be ready when it reaches here.
2725
getInitialState: function() {
@@ -54,7 +52,7 @@ const App = React.createClass({
5452
const App2 = React.createClass({
5553
getInitialState() {
5654
const state = {
57-
whatever: this.context.whatever,
55+
whatever: this.context.whatever, // needs context
5856
};
5957
return state;
6058
},
@@ -84,7 +82,7 @@ const getContextFromInstance = (x) => x.context; // meh
8482

8583
var MyComponent3 = React.createClass({
8684
getInitialState: function() {
87-
var x = getContextFromInstance(this);
85+
var x = getContextFromInstance(this); // `this` is referenced alone
8886
return {
8987
heyoo: x,
9088
};
@@ -95,6 +93,8 @@ var MyComponent3 = React.createClass({
9593
},
9694
});
9795

96+
// we are not sure what you'll need from `this`,
97+
// so it's safe to defer `state`'s initialization
9898
var MyComponent4 = React.createClass({
9999
getInitialState: function() {
100100
return {
@@ -107,6 +107,7 @@ var MyComponent4 = React.createClass({
107107
},
108108
});
109109

110+
// intense control flow testing
110111
var Loader = React.createClass({
111112
getInitialState() {
112113
if (this.props.stuff) {
@@ -120,6 +121,17 @@ var Loader = React.createClass({
120121
{x: 3} :
121122
this.whatever(this.props);
122123
}
124+
for (let i = 0; i < 100; i++) {
125+
if (i === 20) {
126+
return {x: i};
127+
}
128+
}
129+
130+
try {
131+
doSomeThingReallyBad();
132+
} catch (e) {
133+
return {error: e};
134+
}
123135

124136
return this.lol();
125137
},
@@ -129,6 +141,33 @@ var Loader = React.createClass({
129141
},
130142
});
131143

144+
var FunctionDeclarationInGetInitialState = React.createClass({
145+
getInitialState() {
146+
function func() {
147+
var x = 1;
148+
return x; // dont change me
149+
}
150+
151+
const foo = () => {
152+
return 120; // dont change me
153+
};
154+
155+
var q = function() {
156+
return 100; // dont change me
157+
};
158+
159+
return {
160+
x: func(),
161+
y: foo(),
162+
z: q(),
163+
};
164+
},
165+
166+
render() {
167+
return null;
168+
},
169+
});
170+
132171
var DeferStateInitialization = React.createClass({
133172
getInitialState() {
134173
return {x: this.something};
@@ -143,7 +182,7 @@ var DeferStateInitialization = React.createClass({
143182

144183
var helper = () => {};
145184

146-
var PassGetInitialState = React.createClass({
185+
var PassGetInitialState = React.createClass({ // bail out here
147186
getInitialState() {
148187
return this.lol();
149188
},
@@ -157,7 +196,7 @@ var PassGetInitialState = React.createClass({
157196
},
158197
});
159198

160-
var UseGetInitialState = React.createClass({
199+
var UseGetInitialState = React.createClass({ // bail out here
161200
getInitialState() {
162201
return this.lol();
163202
},
@@ -171,7 +210,7 @@ var UseGetInitialState = React.createClass({
171210
},
172211
});
173212

174-
var UseArguments = React.createClass({
213+
var UseArguments = React.createClass({ // bail out here
175214
helper() {
176215
console.log(arguments);
177216
},
@@ -181,7 +220,7 @@ var UseArguments = React.createClass({
181220
},
182221
});
183222

184-
var ShadowingIssue = React.createClass({
223+
var ShadowingIssue = React.createClass({ // bail out here
185224
getInitialState() {
186225
const props = { x: 123 };
187226
return { x: props.x };
@@ -192,6 +231,7 @@ var ShadowingIssue = React.createClass({
192231
},
193232
});
194233

234+
// will remove unnecessary bindings
195235
var ShadowingButFine = React.createClass({
196236
getInitialState() {
197237
const props = this.props;

transforms/__testfixtures__/class-initial-state.output.js

Lines changed: 53 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React from 'React';
22

3-
/*
4-
* Multiline
5-
*/
3+
// only needs props
64
class MyComponent extends React.Component {
75
constructor(props) {
86
super(props);
@@ -23,7 +21,7 @@ class ComponentWithBothPropsAndContextAccess extends React.Component {
2321
name: React.PropTypes.string,
2422
};
2523

26-
// we actually _don't_ need a constructor here since this will be
24+
// we actually don't need a constructor here since this will be
2725
// initialized after a proper super(props, context) call.
2826
// in other words, `this` will be ready when it reaches here.
2927
state = {
@@ -58,7 +56,7 @@ class App2 extends React.Component {
5856
constructor(props, context) {
5957
super(props, context);
6058
const state = {
61-
whatever: this.context.whatever,
59+
whatever: this.context.whatever, // needs context
6260
};
6361
this.state = state;
6462
}
@@ -92,7 +90,7 @@ const getContextFromInstance = (x) => x.context; // meh
9290
class MyComponent3 extends React.Component {
9391
constructor(props, context) {
9492
super(props, context);
95-
var x = getContextFromInstance(this);
93+
var x = getContextFromInstance(this); // `this` is referenced alone
9694

9795
this.state = {
9896
heyoo: x,
@@ -104,6 +102,8 @@ class MyComponent3 extends React.Component {
104102
};
105103
}
106104

105+
// we are not sure what you'll need from `this`,
106+
// so it's safe to defer `state`'s initialization
107107
class MyComponent4 extends React.Component {
108108
foo = (): void => {
109109
this.setState({heyoo: 24});
@@ -114,6 +114,7 @@ class MyComponent4 extends React.Component {
114114
};
115115
}
116116

117+
// intense control flow testing
117118
class Loader extends React.Component {
118119
constructor(props, context) {
119120
super(props, context);
@@ -132,6 +133,19 @@ class Loader extends React.Component {
132133

133134
return;
134135
}
136+
for (let i = 0; i < 100; i++) {
137+
if (i === 20) {
138+
this.state = {x: i};
139+
return;
140+
}
141+
}
142+
143+
try {
144+
doSomeThingReallyBad();
145+
} catch (e) {
146+
this.state = {error: e};
147+
return;
148+
}
135149

136150
this.state = this.lol();
137151
}
@@ -141,6 +155,34 @@ class Loader extends React.Component {
141155
}
142156
}
143157

158+
class FunctionDeclarationInGetInitialState extends React.Component {
159+
constructor(props) {
160+
super(props);
161+
function func() {
162+
var x = 1;
163+
return x; // dont change me
164+
}
165+
166+
const foo = () => {
167+
return 120; // dont change me
168+
};
169+
170+
var q = function() {
171+
return 100; // dont change me
172+
};
173+
174+
this.state = {
175+
x: func(),
176+
y: foo(),
177+
z: q(),
178+
};
179+
}
180+
181+
render() {
182+
return null;
183+
}
184+
}
185+
144186
class DeferStateInitialization extends React.Component {
145187
something = 42;
146188
state = {x: this.something};
@@ -152,7 +194,7 @@ class DeferStateInitialization extends React.Component {
152194

153195
var helper = () => {};
154196

155-
var PassGetInitialState = React.createClass({
197+
var PassGetInitialState = React.createClass({ // bail out here
156198
getInitialState() {
157199
return this.lol();
158200
},
@@ -166,7 +208,7 @@ var PassGetInitialState = React.createClass({
166208
},
167209
});
168210

169-
var UseGetInitialState = React.createClass({
211+
var UseGetInitialState = React.createClass({ // bail out here
170212
getInitialState() {
171213
return this.lol();
172214
},
@@ -180,7 +222,7 @@ var UseGetInitialState = React.createClass({
180222
},
181223
});
182224

183-
var UseArguments = React.createClass({
225+
var UseArguments = React.createClass({ // bail out here
184226
helper() {
185227
console.log(arguments);
186228
},
@@ -190,7 +232,7 @@ var UseArguments = React.createClass({
190232
},
191233
});
192234

193-
var ShadowingIssue = React.createClass({
235+
var ShadowingIssue = React.createClass({ // bail out here
194236
getInitialState() {
195237
const props = { x: 123 };
196238
return { x: props.x };
@@ -201,6 +243,7 @@ var ShadowingIssue = React.createClass({
201243
},
202244
});
203245

246+
// will remove unnecessary bindings
204247
class ShadowingButFine extends React.Component {
205248
constructor(props, context) {
206249
super(props, context);

transforms/class.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -383,23 +383,41 @@ module.exports = (file, api, options) => {
383383
.forEach(path => j(path).replaceWith(j.identifier('props')));
384384

385385
const inlineGetInitialState = getInitialState => {
386-
const functionExpressionAST = j(getInitialState.value);
386+
const functionExpressionCollection = j(getInitialState.value);
387387

388388
// at this point if there exists bindings like `const props = ...`, we
389389
// already know the RHS must be `this.props` (see `isGetInitialStateConstructorSafe`)
390390
// so it's safe to just remove them
391-
functionExpressionAST.find(j.VariableDeclarator, {id: {name: 'props'}})
391+
functionExpressionCollection.find(j.VariableDeclarator, {id: {name: 'props'}})
392392
.forEach(path => j(path).remove());
393393

394-
functionExpressionAST.find(j.VariableDeclarator, {id: {name: 'context'}})
394+
functionExpressionCollection.find(j.VariableDeclarator, {id: {name: 'context'}})
395395
.forEach(path => j(path).remove());
396396

397-
return functionExpressionAST
397+
return functionExpressionCollection
398398
.find(j.ReturnStatement)
399+
.filter(path => {
400+
// filter out inner function declarations here (helper functions, promises, etc.).
401+
const mainBodyCollection = j(getInitialState.value.body);
402+
return (
403+
mainBodyCollection
404+
.find(j.ArrowFunctionExpression)
405+
.find(j.ReturnStatement, path.value)
406+
.size() === 0 &&
407+
mainBodyCollection
408+
.find(j.FunctionDeclaration)
409+
.find(j.ReturnStatement, path.value)
410+
.size() === 0 &&
411+
mainBodyCollection
412+
.find(j.FunctionExpression)
413+
.find(j.ReturnStatement, path.value)
414+
.size() === 0
415+
);
416+
})
399417
.forEach(path => {
400418
let shouldInsertReturnAfterAssignment = false;
401419

402-
// if the return statement is not a direct child of the function body
420+
// if the return statement is not a direct child of getInitialState's body
403421
if (getInitialState.value.body.body.indexOf(path.value) === -1) {
404422
shouldInsertReturnAfterAssignment = true;
405423
}

0 commit comments

Comments
 (0)