@@ -12,23 +12,16 @@ const ast = require('./ast');
1212// Constants
1313// ------------------------------------------------------------------------------
1414
15- const DIRECT_PROPS_REGEX = / ^ p r o p s \s * ( \. | \[ ) / ;
16- const DIRECT_NEXT_PROPS_REGEX = / ^ n e x t P r o p s \s * ( \. | \[ ) / ;
17- const DIRECT_PREV_PROPS_REGEX = / ^ p r e v P r o p s \s * ( \. | \[ ) / ;
1815const LIFE_CYCLE_METHODS = [ 'componentWillReceiveProps' , 'shouldComponentUpdate' , 'componentWillUpdate' , 'componentDidUpdate' ] ;
1916const ASYNC_SAFE_LIFE_CYCLE_METHODS = [ 'getDerivedStateFromProps' , 'getSnapshotBeforeUpdate' , 'UNSAFE_componentWillReceiveProps' , 'UNSAFE_componentWillUpdate' ] ;
2017
2118/**
22- * Checks if a prop init name matches common naming patterns
23- * @param {ASTNode } node The AST node being checked.
19+ * Checks if the string is one of `props`, `nextProps`, or `prevProps`
20+ * @param {string } name The AST node being checked.
2421 * @returns {Boolean } True if the prop name matches
2522 */
26- function isPropAttributeName ( node ) {
27- return (
28- node . init . name === 'props' ||
29- node . init . name === 'nextProps' ||
30- node . init . name === 'prevProps'
31- ) ;
23+ function isCommonVariableNameForProps ( name ) {
24+ return name === 'props' || name === 'nextProps' || name === 'prevProps' ;
3225}
3326
3427/**
@@ -40,26 +33,6 @@ function mustBeValidated(component) {
4033 return ! ! ( component && ! component . ignorePropsValidation ) ;
4134}
4235
43- /**
44- * Check if we are in a class constructor
45- * @return {boolean } true if we are in a class constructor, false if not
46- */
47- function inComponentWillReceiveProps ( context ) {
48- let scope = context . getScope ( ) ;
49- while ( scope ) {
50- if (
51- scope . block &&
52- scope . block . parent &&
53- scope . block . parent . key &&
54- scope . block . parent . key . name === 'componentWillReceiveProps'
55- ) {
56- return true ;
57- }
58- scope = scope . upper ;
59- }
60- return false ;
61- }
62-
6336/**
6437 * Check if we are in a lifecycle method
6538 * @return {boolean } true if we are in a class constructor, false if not
@@ -143,7 +116,10 @@ function inSetStateUpdater(context) {
143116 return false ;
144117}
145118
146- function isPropArgumentInSetStateUpdater ( context , node ) {
119+ function isPropArgumentInSetStateUpdater ( context , name ) {
120+ if ( typeof name !== 'string' ) {
121+ return ;
122+ }
147123 let scope = context . getScope ( ) ;
148124 while ( scope ) {
149125 if (
@@ -156,13 +132,29 @@ function isPropArgumentInSetStateUpdater(context, node) {
156132 scope . block . parent . arguments [ 0 ] . params &&
157133 scope . block . parent . arguments [ 0 ] . params . length > 1
158134 ) {
159- return scope . block . parent . arguments [ 0 ] . params [ 1 ] . name === node . object . name ;
135+ return scope . block . parent . arguments [ 0 ] . params [ 1 ] . name === name ;
160136 }
161137 scope = scope . upper ;
162138 }
163139 return false ;
164140}
165141
142+ function isInClassComponent ( utils ) {
143+ return utils . getParentES6Component ( ) || utils . getParentES5Component ( ) ;
144+ }
145+
146+ /**
147+ * Checks if the node is `this.props`
148+ * @param {ASTNode|undefined } node
149+ * @returns {boolean }
150+ */
151+ function isThisDotProps ( node ) {
152+ return ! ! node &&
153+ node . type === 'MemberExpression' &&
154+ node . object . type === 'ThisExpression' &&
155+ node . property . name === 'props' ;
156+ }
157+
166158/**
167159 * Checks if the prop has spread operator.
168160 * @param {ASTNode } node The AST node being marked.
@@ -178,27 +170,7 @@ function hasSpreadOperator(context, node) {
178170 * @param {ASTNode } node The AST node with the property.
179171 * @return {string|undefined } the name of the property or undefined if not found
180172 */
181- function getPropertyName ( node , context , utils , checkAsyncSafeLifeCycles ) {
182- const sourceCode = context . getSourceCode ( ) ;
183- const isDirectProp = DIRECT_PROPS_REGEX . test ( sourceCode . getText ( node ) ) ;
184- const isDirectNextProp = DIRECT_NEXT_PROPS_REGEX . test ( sourceCode . getText ( node ) ) ;
185- const isDirectPrevProp = DIRECT_PREV_PROPS_REGEX . test ( sourceCode . getText ( node ) ) ;
186- const isDirectSetStateProp = isPropArgumentInSetStateUpdater ( context , node ) ;
187- const isInClassComponent = utils . getParentES6Component ( ) || utils . getParentES5Component ( ) ;
188- const isNotInConstructor = ! utils . inConstructor ( node ) ;
189- const isNotInLifeCycleMethod = ! inLifeCycleMethod ( context , checkAsyncSafeLifeCycles ) ;
190- const isNotInSetStateUpdater = ! inSetStateUpdater ( context ) ;
191- if ( ( isDirectProp || isDirectNextProp || isDirectPrevProp || isDirectSetStateProp ) &&
192- isInClassComponent &&
193- isNotInConstructor &&
194- isNotInLifeCycleMethod &&
195- isNotInSetStateUpdater
196- ) {
197- return ;
198- }
199- if ( ! isDirectProp && ! isDirectNextProp && ! isDirectPrevProp && ! isDirectSetStateProp ) {
200- node = node . parent ;
201- }
173+ function getPropertyName ( node ) {
202174 const property = node . property ;
203175 if ( property ) {
204176 switch ( property . type ) {
@@ -225,21 +197,34 @@ function getPropertyName(node, context, utils, checkAsyncSafeLifeCycles) {
225197}
226198
227199/**
228- * Checks if we are using a prop
229- * @param {ASTNode } node The AST node being checked.
230- * @returns {Boolean } True if we are using a prop, false if not.
200+ * Checks if the node is a propTypes usage of the form `this.props.*`, `props.*`, `prevProps.*`, or `nextProps.*`.
201+ * @param {ASTNode } node
202+ * @param {Context } context
203+ * @param {Object } utils
204+ * @param {boolean } checkAsyncSafeLifeCycles
205+ * @returns {boolean }
231206 */
232- function isPropTypesUsage ( node , context , utils , checkAsyncSafeLifeCycles ) {
233- const isThisPropsUsage = node . object . type === 'ThisExpression' && node . property . name === 'props' ;
234- const isPropsUsage = isThisPropsUsage || node . object . name === 'nextProps' || node . object . name === 'prevProps' ;
235- const isClassUsage = (
236- ( utils . getParentES6Component ( ) || utils . getParentES5Component ( ) ) &&
237- ( isThisPropsUsage || isPropArgumentInSetStateUpdater ( context , node ) )
238- ) ;
239- const isStatelessFunctionUsage = node . object . name === 'props' && ! ast . isAssignmentLHS ( node ) ;
240- return isClassUsage ||
241- isStatelessFunctionUsage ||
242- ( isPropsUsage && inLifeCycleMethod ( context , checkAsyncSafeLifeCycles ) ) ;
207+ function isPropTypesUsageByMemberExpression ( node , context , utils , checkAsyncSafeLifeCycles ) {
208+ if ( isInClassComponent ( utils ) ) {
209+ // this.props.*
210+ if ( isThisDotProps ( node . object ) ) {
211+ return true ;
212+ }
213+ // props.* or prevProps.* or nextProps.*
214+ if (
215+ isCommonVariableNameForProps ( node . object . name ) &&
216+ ( inLifeCycleMethod ( context , checkAsyncSafeLifeCycles ) || utils . inConstructor ( ) )
217+ ) {
218+ return true ;
219+ }
220+ // this.setState((_, props) => props.*))
221+ if ( isPropArgumentInSetStateUpdater ( context , node . object . name ) ) {
222+ return true ;
223+ }
224+ return false ;
225+ }
226+ // props.* in function component
227+ return node . object . name === 'props' && ! ast . isAssignmentLHS ( node ) ;
243228}
244229
245230module . exports = function usedPropTypesInstructions ( context , components , utils ) {
@@ -258,7 +243,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
258243 let properties ;
259244 switch ( node . type ) {
260245 case 'MemberExpression' :
261- name = getPropertyName ( node , context , utils , checkAsyncSafeLifeCycles ) ;
246+ name = getPropertyName ( node ) ;
262247 if ( name ) {
263248 allNames = parentNames . concat ( name ) ;
264249 if (
@@ -268,6 +253,13 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
268253 ) {
269254 markPropTypesAsUsed ( node . parent , allNames ) ;
270255 }
256+ // Handle the destructuring part of `const {foo} = props.a.b`
257+ if (
258+ node . parent . type === 'VariableDeclarator' &&
259+ node . parent . id . type === 'ObjectPattern'
260+ ) {
261+ markPropTypesAsUsed ( node . parent . id , allNames ) ;
262+ }
271263 // Do not mark computed props as used.
272264 type = name !== '__COMPUTED_PROP__' ? 'direct' : null ;
273265 } else if (
@@ -293,31 +285,9 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
293285 propParam . properties ;
294286 break ;
295287 }
296- case 'VariableDeclarator' :
297- node . id . properties . some ( ( property ) => {
298- // let {props: {firstname}} = this
299- const thisDestructuring = (
300- property . key && (
301- ( property . key . name === 'props' || property . key . value === 'props' ) &&
302- property . value . type === 'ObjectPattern'
303- )
304- ) ;
305- // let {firstname} = props
306- const genericDestructuring = isPropAttributeName ( node ) && (
307- utils . getParentStatelessComponent ( ) ||
308- isInLifeCycleMethod ( node , checkAsyncSafeLifeCycles )
309- ) ;
310-
311- if ( thisDestructuring ) {
312- properties = property . value . properties ;
313- } else if ( genericDestructuring ) {
314- properties = node . id . properties ;
315- } else {
316- return false ;
317- }
318- type = 'destructuring' ;
319- return true ;
320- } ) ;
288+ case 'ObjectPattern' :
289+ type = 'destructuring' ;
290+ properties = node . properties ;
321291 break ;
322292 default :
323293 throw new Error ( `${ node . type } ASTNodes are not handled by markPropTypesAsUsed` ) ;
@@ -334,15 +304,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
334304 break ;
335305 }
336306
337- const nodeSource = context . getSourceCode ( ) . getText ( node ) ;
338- const isDirectProp = DIRECT_PROPS_REGEX . test ( nodeSource ) ||
339- DIRECT_NEXT_PROPS_REGEX . test ( nodeSource ) ||
340- DIRECT_PREV_PROPS_REGEX . test ( nodeSource ) ;
341- const reportedNode = (
342- ! isDirectProp && ! utils . inConstructor ( ) && ! inComponentWillReceiveProps ( context ) ?
343- node . parent . property :
344- node . property
345- ) ;
307+ const reportedNode = node . property ;
346308 usedPropTypes . push ( {
347309 name,
348310 allNames,
@@ -358,7 +320,8 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
358320 }
359321 const propName = ast . getKeyValue ( context , properties [ k ] ) ;
360322
361- let currentNode = node ;
323+ // Get parent names in the right hand side of `const {foo} = props.a.b`
324+ let currentNode = node . parent . init || { } ;
362325 allNames = [ ] ;
363326 while ( currentNode . property && currentNode . property . name !== 'props' ) {
364327 allNames . unshift ( currentNode . property . name ) ;
@@ -436,19 +399,35 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
436399
437400 return {
438401 VariableDeclarator ( node ) {
439- const destructuring = node . init && node . id && node . id . type === 'ObjectPattern' ;
402+ // Only handles destructuring
403+ if ( node . id . type !== 'ObjectPattern' ) {
404+ return ;
405+ }
406+
440407 // let {props: {firstname}} = this
441- const thisDestructuring = destructuring && node . init . type === 'ThisExpression' ;
442- // let {firstname} = props
443- const statelessDestructuring = destructuring && isPropAttributeName ( node ) && (
444- utils . getParentStatelessComponent ( ) ||
445- isInLifeCycleMethod ( node , checkAsyncSafeLifeCycles )
446- ) ;
408+ const propsProperty = node . id . properties . find ( property => (
409+ property . key &&
410+ ( property . key . name === 'props' || property . key . value === 'props' ) &&
411+ property . value . type === 'ObjectPattern'
412+ ) ) ;
413+ if ( propsProperty && node . init . type === 'ThisExpression' ) {
414+ markPropTypesAsUsed ( propsProperty . value ) ;
415+ return ;
416+ }
447417
448- if ( ! thisDestructuring && ! statelessDestructuring ) {
418+ // let {firstname} = props
419+ if (
420+ isCommonVariableNameForProps ( node . init . name ) &&
421+ ( utils . getParentStatelessComponent ( ) || isInLifeCycleMethod ( node , checkAsyncSafeLifeCycles ) )
422+ ) {
423+ markPropTypesAsUsed ( node . id ) ;
449424 return ;
450425 }
451- markPropTypesAsUsed ( node ) ;
426+
427+ // let {firstname} = this.props
428+ if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) ) {
429+ markPropTypesAsUsed ( node . id ) ;
430+ }
452431 } ,
453432
454433 FunctionDeclaration : handleFunctionLikeExpressions ,
@@ -465,7 +444,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
465444 } ,
466445
467446 MemberExpression ( node ) {
468- if ( isPropTypesUsage ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
447+ if ( isPropTypesUsageByMemberExpression ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
469448 markPropTypesAsUsed ( node ) ;
470449 }
471450 } ,
0 commit comments