@@ -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,16 +253,16 @@ 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+ node . parent . id . parent = node . parent ; // patch for bug in eslint@4 in which ObjectPattern has no parent
262+ markPropTypesAsUsed ( node . parent . id , allNames ) ;
263+ }
271264 // Do not mark computed props as used.
272265 type = name !== '__COMPUTED_PROP__' ? 'direct' : null ;
273- } else if (
274- node . parent . id &&
275- node . parent . id . properties &&
276- node . parent . id . properties . length &&
277- ast . getKeyValue ( context , node . parent . id . properties [ 0 ] )
278- ) {
279- type = 'destructuring' ;
280- properties = node . parent . id . properties ;
281266 }
282267 break ;
283268 case 'ArrowFunctionExpression' :
@@ -293,31 +278,9 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
293278 propParam . properties ;
294279 break ;
295280 }
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- } ) ;
281+ case 'ObjectPattern' :
282+ type = 'destructuring' ;
283+ properties = node . properties ;
321284 break ;
322285 default :
323286 throw new Error ( `${ node . type } ASTNodes are not handled by markPropTypesAsUsed` ) ;
@@ -334,15 +297,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
334297 break ;
335298 }
336299
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- ) ;
300+ const reportedNode = node . property ;
346301 usedPropTypes . push ( {
347302 name,
348303 allNames,
@@ -358,7 +313,8 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
358313 }
359314 const propName = ast . getKeyValue ( context , properties [ k ] ) ;
360315
361- let currentNode = node ;
316+ // Get parent names in the right hand side of `const {foo} = props.a.b`
317+ let currentNode = ( node . parent && node . parent . init ) || { } ;
362318 allNames = [ ] ;
363319 while ( currentNode . property && currentNode . property . name !== 'props' ) {
364320 allNames . unshift ( currentNode . property . name ) ;
@@ -436,19 +392,35 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
436392
437393 return {
438394 VariableDeclarator ( node ) {
439- const destructuring = node . init && node . id && node . id . type === 'ObjectPattern' ;
395+ // Only handles destructuring
396+ if ( node . id . type !== 'ObjectPattern' ) {
397+ return ;
398+ }
399+
440400 // 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- ) ;
401+ const propsProperty = node . id . properties . find ( property => (
402+ property . key &&
403+ ( property . key . name === 'props' || property . key . value === 'props' ) &&
404+ property . value . type === 'ObjectPattern'
405+ ) ) ;
406+ if ( propsProperty && node . init . type === 'ThisExpression' ) {
407+ markPropTypesAsUsed ( propsProperty . value ) ;
408+ return ;
409+ }
447410
448- if ( ! thisDestructuring && ! statelessDestructuring ) {
411+ // let {firstname} = props
412+ if (
413+ isCommonVariableNameForProps ( node . init . name ) &&
414+ ( utils . getParentStatelessComponent ( ) || isInLifeCycleMethod ( node , checkAsyncSafeLifeCycles ) )
415+ ) {
416+ markPropTypesAsUsed ( node . id ) ;
449417 return ;
450418 }
451- markPropTypesAsUsed ( node ) ;
419+
420+ // let {firstname} = this.props
421+ if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) ) {
422+ markPropTypesAsUsed ( node . id ) ;
423+ }
452424 } ,
453425
454426 FunctionDeclaration : handleFunctionLikeExpressions ,
@@ -465,7 +437,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
465437 } ,
466438
467439 MemberExpression ( node ) {
468- if ( isPropTypesUsage ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
440+ if ( isPropTypesUsageByMemberExpression ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
469441 markPropTypesAsUsed ( node ) ;
470442 }
471443 } ,
0 commit comments