Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 46 additions & 0 deletions transforms/__testfixtures__/pure-component.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,50 @@ class ImpureWithRef extends React.Component {
}
}

class PureWithTypes extends React.Component {
props: { foo: string };
render() {
return <div className={this.props.foo} />;
}
}

type Props = { foo: string };

class PureWithTypes2 extends React.Component {
props: Props;
render() {
return <div className={this.props.foo} />;
}
}

class ImpureClassProperty extends React.Component {
state = { foo: 2 };
render() {
return <div />;
}
}

class ImpureClassPropertyWithTypes extends React.Component {
state: { x: string };
render() {
return <div />;
}
}

class PureWithPropTypes extends React.Component {
static propTypes = { foo: React.PropTypes.string };
static foo = 'bar';
render() {
return <div />;
}
}

class PureWithPropTypes2 extends React.Component {
props: { foo: string };
static propTypes = { foo: React.PropTypes.string };
render() {
return <div />;
}
}

var A = props => <div className={props.foo} />;
37 changes: 37 additions & 0 deletions transforms/__testfixtures__/pure-component.output.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,41 @@ class ImpureWithRef extends React.Component {
}
}

function PureWithTypes(props: { foo: string }) {
return <div className={props.foo} />;
}

type Props = { foo: string };

function PureWithTypes2(props: Props) {
return <div className={props.foo} />;
}

class ImpureClassProperty extends React.Component {
state = { foo: 2 };
render() {
return <div />;
}
}

class ImpureClassPropertyWithTypes extends React.Component {
state: { x: string };
render() {
return <div />;
}
}

function PureWithPropTypes(props) {
return <div />;
}

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

function PureWithPropTypes2(props: { foo: string }) {
return <div />;
}

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

var A = props => <div className={props.foo} />;
23 changes: 23 additions & 0 deletions transforms/__testfixtures__/pure-component2.input.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,27 @@ class Impure extends React.Component {
}
}

class PureWithTypes extends React.Component {
props: { foo: string };
render() {
return <div className={this.props.foo} />;
}
}

type Props = { foo: string };

class PureWithTypes2 extends React.Component {
props: Props;
render() {
return <div className={this.props.foo} />;
}
}

class PureWithPropTypes extends React.Component {
static propTypes = { foo: React.PropTypes.string };
render() {
return <div />;
}
}

var A = props => <div className={props.foo} />;
16 changes: 16 additions & 0 deletions transforms/__testfixtures__/pure-component2.output.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,20 @@ class Impure extends React.Component {
}
}

const PureWithTypes = (props: { foo: string }) => {
return <div className={props.foo} />;
};

type Props = { foo: string };

const PureWithTypes2 = (props: Props) => {
return <div className={props.foo} />;
};

const PureWithPropTypes = props => {
return <div />;
};

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

var A = props => <div className={props.foo} />;
63 changes: 56 additions & 7 deletions transforms/pure-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,29 @@ module.exports = function(file, api, options) {
node.key.name == 'render'
);

const isPropsProperty = node => (
node.type === 'ClassProperty' &&
node.key.type === 'Identifier' &&
node.key.name === 'props'
);

const isStaticProperty = node => (
node.type === 'ClassProperty' &&
node.static
);

const onlyHasRenderMethod = path =>
j(path)
.find(j.MethodDefinition)
.filter(p => !isRenderMethod(p.value))
.size() === 0;

const onlyHasSafeClassProperties = path =>
j(path)
.find(j.ClassProperty)
.filter(p => !(isPropsProperty(p.value) || isStaticProperty(p.value)))
.size() === 0;

const hasRefs = path =>
j(path)
.find(j.JSXAttribute, {
Expand All @@ -60,26 +77,50 @@ module.exports = function(file, api, options) {
.find(j.MemberExpression, THIS_PROPS)
.replaceWith(j.identifier('props'));

const buildPureComponentFunction = (name, body) =>
const buildIdentifierWithTypeAnnotation = (name, typeAnnotation) => {
const identifier = j.identifier(name);
if (typeAnnotation) {
identifier.typeAnnotation = j.typeAnnotation(typeAnnotation);
}
return identifier;
};

const findPropsTypeAnnotation = body => {
const property = body.find(isPropsProperty);

return property && property.typeAnnotation.typeAnnotation;
};

const buildPureComponentFunction = (name, body, typeAnnotation) =>
j.functionDeclaration(
j.identifier(name),
[j.identifier('props')],
[buildIdentifierWithTypeAnnotation('props', typeAnnotation)],
body
);

const buildPureComponentArrowFunction = (name, body) =>
const buildPureComponentArrowFunction = (name, body, typeAnnotation) =>
j.variableDeclaration(
'const', [
j.variableDeclarator(
j.identifier(name),
j.arrowFunctionExpression(
[j.identifier('props')],
[buildIdentifierWithTypeAnnotation('props', typeAnnotation)],
body
)
),
]
);

const buildStatics = (name, properties) => properties.map(prop => (
j.expressionStatement(
j.assignmentExpression(
'=',
j.memberExpression(j.identifier(name), prop.key),
prop.value
)
)
));

const reportSkipped = path => {
const name = getClassName(path);
const fileName = file.path;
Expand All @@ -92,7 +133,7 @@ module.exports = function(file, api, options) {

const pureClasses = ReactUtils.findReactES6ClassDeclaration(f)
.filter(path => {
const isPure = onlyHasRenderMethod(path) && !hasRefs(path);
const isPure = onlyHasRenderMethod(path) && onlyHasSafeClassProperties(path) && !hasRefs(path);
if (!isPure && !silenceWarnings) {
reportSkipped(path);
}
Expand All @@ -107,13 +148,21 @@ module.exports = function(file, api, options) {
const name = p.node.id.name;
const renderMethod = p.value.body.body.filter(isRenderMethod)[0];
const renderBody = renderMethod.value.body;
const propsTypeAnnotation = findPropsTypeAnnotation(p.value.body.body);
const statics = p.value.body.body.filter(isStaticProperty);

replaceThisProps(renderBody);

if (useArrows) {
return buildPureComponentArrowFunction(name, renderBody);
return [
buildPureComponentArrowFunction(name, renderBody, propsTypeAnnotation),
...buildStatics(name, statics)
];
} else {
return buildPureComponentFunction(name, renderBody);
return [
buildPureComponentFunction(name, renderBody, propsTypeAnnotation),
...buildStatics(name, statics)
];
}
});

Expand Down