Skip to content

Commit 59f26d7

Browse files
committed
Teach prop-types about destructing in function signatures
The following should cause an error for missing propType validations, but it doesn't. const Test = ({ name }) => { return ( <div>{name}</div> ); }; If you replace the destructuring with using the props object, it works as expected. This commit fixes this problem. Fixes #354.
1 parent dedf67a commit 59f26d7

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

docs/rules/prop-types.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ var Hello = React.createClass({
2323
return <div>Hello {this.props.firstname} {this.props.lastname}</div>; // lastname type is not defined in propTypes
2424
}
2525
});
26+
27+
function Hello({ name }) {
28+
return <div>Hello {this.props.name}</div>;
29+
}
2630
```
2731

2832
The following patterns are not considered warnings:
@@ -50,6 +54,13 @@ var Hello = React.createClass({
5054
return <div>Hello {this.props.name}</div>;
5155
}
5256
});
57+
58+
function Hello({ name }) {
59+
return <div>Hello {this.props.name}</div>;
60+
}
61+
Hello.propTypes = {
62+
name: React.PropTypes.string.isRequired,
63+
};
5364
```
5465

5566
## Rule Options

lib/rules/prop-types.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,12 @@ module.exports = Components.detect(function(context, components, utils) {
505505
properties = node.parent.id.properties;
506506
}
507507
break;
508+
case 'ArrowFunctionExpression':
509+
case 'FunctionDeclaration':
510+
case 'FunctionExpression':
511+
type = 'destructuring';
512+
properties = node.params[0].properties;
513+
break;
508514
case 'VariableDeclarator':
509515
for (var i = 0, j = node.id.properties.length; i < j; i++) {
510516
// let {props: {firstname}} = this
@@ -701,6 +707,21 @@ module.exports = Components.detect(function(context, components, utils) {
701707
return annotation;
702708
}
703709

710+
/**
711+
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
712+
* FunctionDeclaration, or FunctionExpression
713+
*/
714+
function markDestructuredFunctionArgumentsAsUsed(node) {
715+
var destructuring = node.params &&
716+
node.params.length === 1 &&
717+
node.params[0] &&
718+
node.params[0].type === 'ObjectPattern';
719+
720+
if (destructuring) {
721+
markPropTypesAsUsed(node);
722+
}
723+
}
724+
704725
// --------------------------------------------------------------------------
705726
// Public
706727
// --------------------------------------------------------------------------
@@ -727,6 +748,12 @@ module.exports = Components.detect(function(context, components, utils) {
727748
markPropTypesAsUsed(node);
728749
},
729750

751+
FunctionDeclaration: markDestructuredFunctionArgumentsAsUsed,
752+
753+
ArrowFunctionExpression: markDestructuredFunctionArgumentsAsUsed,
754+
755+
FunctionExpression: markDestructuredFunctionArgumentsAsUsed,
756+
730757
MemberExpression: function(node) {
731758
var type;
732759
if (isPropTypesUsage(node)) {

tests/lib/rules/prop-types.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,66 @@ ruleTester.run('prop-types', rule, {
836836
'};'
837837
].join('\n'),
838838
parserOptions: parserOptions
839+
}, {
840+
code: [
841+
'const statelessComponent = ({ someProp }) => {',
842+
' const subRender = () => {',
843+
' return <span>{someProp}</span>;',
844+
' };',
845+
' return <div>{subRender()}</div>;',
846+
'};',
847+
'statelessComponent.propTypes = {',
848+
' someProp: PropTypes.string',
849+
'};'
850+
].join('\n'),
851+
parserOptions: parserOptions
852+
}, {
853+
code: [
854+
'const statelessComponent = function({ someProp }) {',
855+
' const subRender = () => {',
856+
' return <span>{someProp}</span>;',
857+
' };',
858+
' return <div>{subRender()}</div>;',
859+
'};',
860+
'statelessComponent.propTypes = {',
861+
' someProp: PropTypes.string',
862+
'};'
863+
].join('\n'),
864+
parserOptions: parserOptions
865+
}, {
866+
code: [
867+
'function statelessComponent({ someProp }) {',
868+
' const subRender = () => {',
869+
' return <span>{someProp}</span>;',
870+
' };',
871+
' return <div>{subRender()}</div>;',
872+
'};',
873+
'statelessComponent.propTypes = {',
874+
' someProp: PropTypes.string',
875+
'};'
876+
].join('\n'),
877+
parserOptions: parserOptions
878+
}, {
879+
code: [
880+
'function notAComponent({ something }) {',
881+
' return something + 1;',
882+
'};'
883+
].join('\n'),
884+
parserOptions: parserOptions
885+
}, {
886+
code: [
887+
'const notAComponent = function({ something }) {',
888+
' return something + 1;',
889+
'};'
890+
].join('\n'),
891+
parserOptions: parserOptions
892+
}, {
893+
code: [
894+
'const notAComponent = ({ something }) => {',
895+
' return something + 1;',
896+
'};'
897+
].join('\n'),
898+
parserOptions: parserOptions
839899
}, {
840900
// Validation is ignored on reassigned props object
841901
code: [
@@ -1446,6 +1506,36 @@ ruleTester.run('prop-types', rule, {
14461506
errors: [{
14471507
message: '\'name\' is missing in props validation'
14481508
}]
1509+
}, {
1510+
code: [
1511+
'function Hello({ name }) {',
1512+
' return <div>Hello {name}</div>;',
1513+
'}'
1514+
].join('\n'),
1515+
parser: 'babel-eslint',
1516+
errors: [{
1517+
message: '\'name\' is missing in props validation'
1518+
}]
1519+
}, {
1520+
code: [
1521+
'const Hello = function({ name }) {',
1522+
' return <div>Hello {name}</div>;',
1523+
'}'
1524+
].join('\n'),
1525+
parser: 'babel-eslint',
1526+
errors: [{
1527+
message: '\'name\' is missing in props validation'
1528+
}]
1529+
}, {
1530+
code: [
1531+
'const Hello = ({ name }) => {',
1532+
' return <div>Hello {name}</div>;',
1533+
'}'
1534+
].join('\n'),
1535+
parser: 'babel-eslint',
1536+
errors: [{
1537+
message: '\'name\' is missing in props validation'
1538+
}]
14491539
}, {
14501540
code: [
14511541
'class Hello extends React.Component {',

0 commit comments

Comments
 (0)