@@ -101,6 +101,48 @@ module.exports = {
101101 return false ;
102102 }
103103
104+ /**
105+ * Check if the current node is in a setState updater method
106+ * @return {boolean } true if we are in a setState updater, false if not
107+ */
108+ function inSetStateUpdater ( ) {
109+ let scope = context . getScope ( ) ;
110+ while ( scope ) {
111+ if (
112+ scope . block && scope . block . parent
113+ && scope . block . parent . type === 'CallExpression'
114+ && scope . block . parent . callee . property
115+ && scope . block . parent . callee . property . name === 'setState'
116+ // Make sure we are in the updater not the callback
117+ && scope . block . parent . arguments [ 0 ] . start === scope . block . start
118+ ) {
119+ return true ;
120+ }
121+ scope = scope . upper ;
122+ }
123+ return false ;
124+ }
125+
126+ function isPropArgumentInSetStateUpdater ( node ) {
127+ let scope = context . getScope ( ) ;
128+ while ( scope ) {
129+ if (
130+ scope . block && scope . block . parent
131+ && scope . block . parent . type === 'CallExpression'
132+ && scope . block . parent . callee . property
133+ && scope . block . parent . callee . property . name === 'setState'
134+ // Make sure we are in the updater not the callback
135+ && scope . block . parent . arguments [ 0 ] . start === scope . block . start
136+ && scope . block . parent . arguments [ 0 ] . params
137+ && scope . block . parent . arguments [ 0 ] . params . length > 0
138+ ) {
139+ return scope . block . parent . arguments [ 0 ] . params [ 1 ] . name === node . object . name ;
140+ }
141+ scope = scope . upper ;
142+ }
143+ return false ;
144+ }
145+
104146 /**
105147 * Checks if we are using a prop
106148 * @param {ASTNode } node The AST node being checked.
@@ -109,7 +151,8 @@ module.exports = {
109151 function isPropTypesUsage ( node ) {
110152 const isClassUsage = (
111153 ( utils . getParentES6Component ( ) || utils . getParentES5Component ( ) ) &&
112- node . object . type === 'ThisExpression' && node . property . name === 'props'
154+ ( ( node . object . type === 'ThisExpression' && node . property . name === 'props' )
155+ || isPropArgumentInSetStateUpdater ( node ) )
113156 ) ;
114157 const isStatelessFunctionUsage = node . object . name === 'props' ;
115158 return isClassUsage || isStatelessFunctionUsage || inLifeCycleMethod ( ) ;
@@ -534,16 +577,20 @@ module.exports = {
534577 const isDirectProp = DIRECT_PROPS_REGEX . test ( sourceCode . getText ( node ) ) ;
535578 const isDirectNextProp = DIRECT_NEXT_PROPS_REGEX . test ( sourceCode . getText ( node ) ) ;
536579 const isDirectPrevProp = DIRECT_PREV_PROPS_REGEX . test ( sourceCode . getText ( node ) ) ;
580+ const isDirectSetStateProp = isPropArgumentInSetStateUpdater ( node ) ;
537581 const isInClassComponent = utils . getParentES6Component ( ) || utils . getParentES5Component ( ) ;
538582 const isNotInConstructor = ! inConstructor ( node ) ;
539583 const isNotInLifeCycleMethod = ! inLifeCycleMethod ( ) ;
540- if ( ( isDirectProp || isDirectNextProp || isDirectPrevProp )
584+ const isNotInSetStateUpdater = ! inSetStateUpdater ( ) ;
585+ if ( ( isDirectProp || isDirectNextProp || isDirectPrevProp || isDirectSetStateProp )
541586 && isInClassComponent
542587 && isNotInConstructor
543- && isNotInLifeCycleMethod ) {
588+ && isNotInLifeCycleMethod
589+ && isNotInSetStateUpdater
590+ ) {
544591 return void 0 ;
545592 }
546- if ( ! isDirectProp && ! isDirectNextProp && ! isDirectPrevProp ) {
593+ if ( ! isDirectProp && ! isDirectNextProp && ! isDirectPrevProp && ! isDirectSetStateProp ) {
547594 node = node . parent ;
548595 }
549596 const property = node . property ;
@@ -607,6 +654,9 @@ module.exports = {
607654 case 'FunctionExpression' :
608655 type = 'destructuring' ;
609656 properties = node . params [ 0 ] . properties ;
657+ if ( inSetStateUpdater ( ) ) {
658+ properties = node . params [ 1 ] . properties ;
659+ }
610660 break ;
611661 case 'VariableDeclarator' :
612662 for ( let i = 0 , j = node . id . properties . length ; i < j ; i ++ ) {
@@ -898,11 +948,20 @@ module.exports = {
898948 markPropTypesAsDeclared ( node , resolveTypeAnnotation ( node . params [ 0 ] ) ) ;
899949 }
900950
951+ function handleSetStateUpdater ( node ) {
952+ if ( ! node . params || ! node . params . length || ! inSetStateUpdater ( ) ) {
953+ return ;
954+ }
955+ markPropTypesAsUsed ( node ) ;
956+ }
957+
901958 /**
959+ * Handle both stateless functions and setState updater functions.
902960 * @param {ASTNode } node We expect either an ArrowFunctionExpression,
903961 * FunctionDeclaration, or FunctionExpression
904962 */
905- function handleStatelessComponent ( node ) {
963+ function handleFunctionLikeExpressions ( node ) {
964+ handleSetStateUpdater ( node ) ;
906965 markDestructuredFunctionArgumentsAsUsed ( node ) ;
907966 markAnnotatedFunctionArgumentsAsDeclared ( node ) ;
908967 }
@@ -942,11 +1001,11 @@ module.exports = {
9421001 markPropTypesAsUsed ( node ) ;
9431002 } ,
9441003
945- FunctionDeclaration : handleStatelessComponent ,
1004+ FunctionDeclaration : handleFunctionLikeExpressions ,
9461005
947- ArrowFunctionExpression : handleStatelessComponent ,
1006+ ArrowFunctionExpression : handleFunctionLikeExpressions ,
9481007
949- FunctionExpression : handleStatelessComponent ,
1008+ FunctionExpression : handleFunctionLikeExpressions ,
9501009
9511010 MemberExpression : function ( node ) {
9521011 let type ;
0 commit comments