Skip to content

Commit 1f04dd8

Browse files
authored
Fix pure component transform edge cases (#255)
* Lexical declarations cannot be combined with the default keyword * Remove unused super class import * Don't include props argument if not used
1 parent ad866c2 commit 1f04dd8

File tree

6 files changed

+75
-11
lines changed

6 files changed

+75
-11
lines changed

transforms/__testfixtures__/pure-component.input.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ class ImpureWithRef extends React.Component {
3131
}
3232
}
3333

34+
class PureWithoutProps extends React.Component {
35+
render() {
36+
return <div />;
37+
}
38+
}
39+
3440
class PureWithTypes extends React.Component {
3541
props: { foo: string };
3642
render() {
@@ -65,15 +71,15 @@ class PureWithPropTypes extends React.Component {
6571
static propTypes = { foo: React.PropTypes.string };
6672
static foo = 'bar';
6773
render() {
68-
return <div />;
74+
return <div>{this.props.foo}</div>;
6975
}
7076
}
7177

7278
class PureWithPropTypes2 extends React.Component {
7379
props: { foo: string };
7480
static propTypes = { foo: React.PropTypes.string };
7581
render() {
76-
return <div />;
82+
return <div>{this.props.foo}</div>;
7783
}
7884
}
7985

transforms/__testfixtures__/pure-component.output.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class ImpureWithRef extends React.Component {
2929
}
3030
}
3131

32+
function PureWithoutProps() {
33+
return <div />;
34+
}
35+
3236
function PureWithTypes(props: { foo: string }) {
3337
return <div className={props.foo} />;
3438
}
@@ -54,14 +58,14 @@ class ImpureClassPropertyWithTypes extends React.Component {
5458
}
5559

5660
function PureWithPropTypes(props) {
57-
return <div />;
61+
return <div>{props.foo}</div>;
5862
}
5963

6064
PureWithPropTypes.propTypes = { foo: React.PropTypes.string };
6165
PureWithPropTypes.foo = 'bar';
6266

6367
function PureWithPropTypes2(props: { foo: string }) {
64-
return <div />;
68+
return <div>{props.foo}</div>;
6569
}
6670

6771
PureWithPropTypes2.propTypes = { foo: React.PropTypes.string };

transforms/__testfixtures__/pure-component2.input.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ class Impure extends React.Component {
2121
}
2222
}
2323

24+
class PureWithoutProps extends React.Component {
25+
render() {
26+
return <div />;
27+
}
28+
}
29+
2430
class PureWithTypes extends React.Component {
2531
props: { foo: string };
2632
render() {
@@ -40,7 +46,7 @@ class PureWithTypes2 extends React.Component {
4046
class PureWithPropTypes extends React.Component {
4147
static propTypes = { foo: React.PropTypes.string };
4248
render() {
43-
return <div />;
49+
return <div>{this.props.foo}</div>;
4450
}
4551
}
4652

transforms/__testfixtures__/pure-component2.output.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ class Impure extends React.Component {
1919
}
2020
}
2121

22+
const PureWithoutProps = () => {
23+
return <div />;
24+
};
25+
2226
const PureWithTypes = (props: { foo: string }) => {
2327
return <div className={props.foo} />;
2428
};
@@ -30,7 +34,7 @@ const PureWithTypes2 = (props: Props) => {
3034
};
3135

3236
const PureWithPropTypes = props => {
33-
return <div />;
37+
return <div>{props.foo}</div>;
3438
};
3539

3640
PureWithPropTypes.propTypes = { foo: React.PropTypes.string };

transforms/pure-component.js

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -202,16 +202,29 @@ module.exports = function(file, api, options) {
202202
return property && property.typeAnnotation.typeAnnotation;
203203
};
204204

205-
const build = useArrows => (name, body, typeAnnotation, destructure) => {
205+
const isDefaultExport = path =>
206+
path.parentPath && path.parentPath.value.type === 'ExportDefaultDeclaration';
207+
208+
const safelyDefaultExportDeclaration = (path) => {
209+
const localName = path.value.declarations[0].id.name;
210+
j(path.parent)
211+
.replaceWith(_ => path.value)
212+
.insertAfter(
213+
j.exportDeclaration(true, { type: 'Identifier', name: localName })
214+
);
215+
};
216+
217+
const build = useArrows => (name, body, typeAnnotation, destructure, hasThisDotProps) => {
206218
const identifier = j.identifier(name);
207219
const propsIdentifier = buildIdentifierWithTypeAnnotation(
208220
'props',
209221
typeAnnotation
210222
);
211-
const propsArg = [
223+
224+
const propsArg = hasThisDotProps ? [
212225
(destructure && destructureProps(j(body), typeAnnotation)) ||
213226
propsIdentifier
214-
];
227+
] : [];
215228
if (useArrows) {
216229
return j.variableDeclaration('const', [
217230
j.variableDeclarator(
@@ -261,6 +274,7 @@ module.exports = function(file, api, options) {
261274
if (!isPure && !silenceWarnings) {
262275
reportSkipped(path);
263276
}
277+
264278
return isPure;
265279
}
266280
);
@@ -269,6 +283,10 @@ module.exports = function(file, api, options) {
269283
return null;
270284
}
271285

286+
// Save the names of the deleted pure classes super class
287+
// We need this to prune unused variables at the end.
288+
const parentClassNames = pureClasses.nodes().map(node => node.superClass.name);
289+
272290
pureClasses.replaceWith(p => {
273291
const name = p.node.id.name;
274292
const renderMethod = p.value.body.body.filter(isRenderMethod)[0];
@@ -281,6 +299,7 @@ module.exports = function(file, api, options) {
281299
console.warn(`Unable to destructure ${name} props.`);
282300
}
283301

302+
const hasThisDotProps = j(renderBody).find(j.MemberExpression, THIS_PROPS).length > 0;
284303
replaceThisProps(renderBody);
285304

286305
if (useArrows) {
@@ -289,7 +308,8 @@ module.exports = function(file, api, options) {
289308
name,
290309
renderBody,
291310
propsTypeAnnotation,
292-
destructure
311+
destructure,
312+
hasThisDotProps
293313
),
294314
...buildStatics(name, statics)
295315
];
@@ -299,11 +319,20 @@ module.exports = function(file, api, options) {
299319
name,
300320
renderBody,
301321
propsTypeAnnotation,
302-
destructure
322+
destructure,
323+
hasThisDotProps
303324
),
304325
...buildStatics(name, statics)
305326
];
306327
}
328+
}).forEach(p => {
329+
// Check for combining default keyword with const declaration
330+
if (useArrows && isDefaultExport(p)) {
331+
safelyDefaultExportDeclaration(p);
332+
}
333+
}).forEach((p, i) => {
334+
const parentClassName = parentClassNames[i];
335+
ReactUtils.removeUnusedSuperClassImport(j(p), f, parentClassName);
307336
});
308337

309338
return f.toSource(printOptions);

transforms/utils/ReactUtils.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,20 @@ module.exports = function(j) {
123123
: undefined;
124124
};
125125

126+
const removeUnusedSuperClassImport = (path, file, superClassName) => {
127+
if (path.find(j.Identifier, {
128+
type: 'Identifier',
129+
name: superClassName
130+
}).length === 0) {
131+
file.find(j.ImportSpecifier, {
132+
type: 'ImportSpecifier',
133+
imported: {
134+
type: 'Identifier',
135+
name: superClassName,
136+
}
137+
}).remove();
138+
}
139+
};
126140

127141
const findReactES6ClassDeclarationByParent = (path, parentClassName) => {
128142
const componentImport = findReactComponentNameByParent(path, parentClassName);
@@ -282,6 +296,7 @@ module.exports = function(j) {
282296
hasModule,
283297
hasReact,
284298
isMixinProperty,
299+
removeUnusedSuperClassImport,
285300

286301
// "direct" methods
287302
findAllReactCreateClassCalls,

0 commit comments

Comments
 (0)