Skip to content

Commit a33db3b

Browse files
author
Yannick Croissant
committed
Add support for nextProps to prop-types (fixes #814)
1 parent 0fcf321 commit a33db3b

File tree

2 files changed

+91
-26
lines changed

2 files changed

+91
-26
lines changed

lib/rules/prop-types.js

+50-26
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ var annotations = require('../util/annotations');
1515
// Constants
1616
// ------------------------------------------------------------------------------
1717

18-
var DIRECT_PROPS_REGEX = /^props\s*(\.|\[)/;
18+
var PROPS_REGEX = /^(props|nextProps)$/;
19+
var DIRECT_PROPS_REGEX = /^(props|nextProps)\s*(\.|\[)/;
1920

2021
// ------------------------------------------------------------------------------
2122
// Rule Definition
@@ -81,6 +82,39 @@ module.exports = {
8182
return value;
8283
}
8384

85+
/**
86+
* Check if we are in a class constructor
87+
* @return {boolean} true if we are in a class constructor, false if not
88+
*/
89+
function inConstructor() {
90+
var scope = context.getScope();
91+
while (scope) {
92+
if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') {
93+
return true;
94+
}
95+
scope = scope.upper;
96+
}
97+
return false;
98+
}
99+
100+
/**
101+
* Check if we are in a class constructor
102+
* @return {boolean} true if we are in a class constructor, false if not
103+
*/
104+
function inComponentWillReceiveProps() {
105+
var scope = context.getScope();
106+
while (scope) {
107+
if (
108+
scope.block && scope.block.parent &&
109+
scope.block.parent.key && scope.block.parent.key.name === 'componentWillReceiveProps'
110+
) {
111+
return true;
112+
}
113+
scope = scope.upper;
114+
}
115+
return false;
116+
}
117+
84118
/**
85119
* Checks if we are using a prop
86120
* @param {ASTNode} node The AST node being checked.
@@ -92,7 +126,8 @@ module.exports = {
92126
node.object.type === 'ThisExpression' && node.property.name === 'props'
93127
);
94128
var isStatelessFunctionUsage = node.object.name === 'props';
95-
return isClassUsage || isStatelessFunctionUsage;
129+
var isNextPropsUsage = node.object.name === 'nextProps' && inComponentWillReceiveProps();
130+
return isClassUsage || isStatelessFunctionUsage || isNextPropsUsage;
96131
}
97132

98133
/**
@@ -464,21 +499,6 @@ module.exports = {
464499
}
465500
}
466501

467-
/**
468-
* Check if we are in a class constructor
469-
* @return {boolean} true if we are in a class constructor, false if not
470-
*/
471-
function inConstructor() {
472-
var scope = context.getScope();
473-
while (scope) {
474-
if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') {
475-
return true;
476-
}
477-
scope = scope.upper;
478-
}
479-
return false;
480-
}
481-
482502
/**
483503
* Retrieve the name of a property node
484504
* @param {ASTNode} node The AST node with the property.
@@ -487,8 +507,9 @@ module.exports = {
487507
function getPropertyName(node) {
488508
var isDirectProp = DIRECT_PROPS_REGEX.test(sourceCode.getText(node));
489509
var isInClassComponent = utils.getParentES6Component() || utils.getParentES5Component();
490-
var isNotInConstructor = !inConstructor(node);
491-
if (isDirectProp && isInClassComponent && isNotInConstructor) {
510+
var isNotInConstructor = !inConstructor();
511+
var isNotInComponentWillReceiveProps = !inComponentWillReceiveProps();
512+
if (isDirectProp && isInClassComponent && isNotInConstructor && isNotInComponentWillReceiveProps) {
492513
return void 0;
493514
}
494515
if (!isDirectProp) {
@@ -561,13 +582,13 @@ module.exports = {
561582
// let {props: {firstname}} = this
562583
var thisDestructuring = (
563584
!hasSpreadOperator(node.id.properties[i]) &&
564-
(node.id.properties[i].key.name === 'props' || node.id.properties[i].key.value === 'props') &&
585+
(PROPS_REGEX.test(node.id.properties[i].key.name) || PROPS_REGEX.test(node.id.properties[i].key.value)) &&
565586
node.id.properties[i].value.type === 'ObjectPattern'
566587
);
567588
// let {firstname} = props
568589
var directDestructuring =
569-
node.init.name === 'props' &&
570-
(utils.getParentStatelessComponent() || inConstructor())
590+
PROPS_REGEX.test(node.init.name) &&
591+
(utils.getParentStatelessComponent() || inConstructor() || inComponentWillReceiveProps())
571592
;
572593

573594
if (thisDestructuring) {
@@ -600,7 +621,10 @@ module.exports = {
600621
usedPropTypes.push({
601622
name: name,
602623
allNames: allNames,
603-
node: !isDirectProp && !inConstructor(node) ? node.parent.property : node.property
624+
node: (
625+
!isDirectProp && !inConstructor() && !inComponentWillReceiveProps() ? node.parent.property :
626+
node.property
627+
)
604628
});
605629
break;
606630
case 'destructuring':
@@ -612,7 +636,7 @@ module.exports = {
612636

613637
var currentNode = node;
614638
allNames = [];
615-
while (currentNode.property && currentNode.property.name !== 'props') {
639+
while (currentNode.property && !PROPS_REGEX.test(currentNode.property.name)) {
616640
allNames.unshift(currentNode.property.name);
617641
currentNode = currentNode.object;
618642
}
@@ -813,8 +837,8 @@ module.exports = {
813837
// let {firstname} = props
814838
var directDestructuring =
815839
destructuring &&
816-
node.init.name === 'props' &&
817-
(utils.getParentStatelessComponent() || inConstructor())
840+
PROPS_REGEX.test(node.init.name) &&
841+
(utils.getParentStatelessComponent() || inConstructor() || inComponentWillReceiveProps())
818842
;
819843

820844
if (!thisDestructuring && !directDestructuring) {

tests/lib/rules/prop-types.js

+41
Original file line numberDiff line numberDiff line change
@@ -2575,6 +2575,47 @@ ruleTester.run('prop-types', rule, {
25752575
errors: [
25762576
{message: '\'foo\' is missing in props validation'}
25772577
]
2578+
}, {
2579+
code: [
2580+
'class Hello extends Component {',
2581+
' static propTypes = {',
2582+
' bar: PropTypes.func',
2583+
' }',
2584+
' componentWillReceiveProps(nextProps) {',
2585+
' if (nextProps.foo) {',
2586+
' return;',
2587+
' }',
2588+
' }',
2589+
' render() {',
2590+
' return <div bar={this.props.bar} />;',
2591+
' }',
2592+
'}'
2593+
].join('\n'),
2594+
parser: 'babel-eslint',
2595+
errors: [
2596+
{message: '\'foo\' is missing in props validation'}
2597+
]
2598+
}, {
2599+
code: [
2600+
'class Hello extends Component {',
2601+
' static propTypes = {',
2602+
' bar: PropTypes.func',
2603+
' }',
2604+
' componentWillReceiveProps(nextProps) {',
2605+
' const {foo} = nextProps;',
2606+
' if (foo) {',
2607+
' return;',
2608+
' }',
2609+
' }',
2610+
' render() {',
2611+
' return <div bar={this.props.bar} />;',
2612+
' }',
2613+
'}'
2614+
].join('\n'),
2615+
parser: 'babel-eslint',
2616+
errors: [
2617+
{message: '\'foo\' is missing in props validation'}
2618+
]
25782619
}
25792620
]
25802621
});

0 commit comments

Comments
 (0)