@@ -12,23 +12,16 @@ const ast = require('./ast');
12
12
// Constants
13
13
// ------------------------------------------------------------------------------
14
14
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 * ( \. | \[ ) / ;
18
15
const LIFE_CYCLE_METHODS = [ 'componentWillReceiveProps' , 'shouldComponentUpdate' , 'componentWillUpdate' , 'componentDidUpdate' ] ;
19
16
const ASYNC_SAFE_LIFE_CYCLE_METHODS = [ 'getDerivedStateFromProps' , 'getSnapshotBeforeUpdate' , 'UNSAFE_componentWillReceiveProps' , 'UNSAFE_componentWillUpdate' ] ;
20
17
21
18
/**
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.
24
21
* @returns {Boolean } True if the prop name matches
25
22
*/
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' ;
32
25
}
33
26
34
27
/**
@@ -40,26 +33,6 @@ function mustBeValidated(component) {
40
33
return ! ! ( component && ! component . ignorePropsValidation ) ;
41
34
}
42
35
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
-
63
36
/**
64
37
* Check if we are in a lifecycle method
65
38
* @return {boolean } true if we are in a class constructor, false if not
@@ -143,7 +116,10 @@ function inSetStateUpdater(context) {
143
116
return false ;
144
117
}
145
118
146
- function isPropArgumentInSetStateUpdater ( context , node ) {
119
+ function isPropArgumentInSetStateUpdater ( context , name ) {
120
+ if ( typeof name !== 'string' ) {
121
+ return ;
122
+ }
147
123
let scope = context . getScope ( ) ;
148
124
while ( scope ) {
149
125
if (
@@ -156,13 +132,29 @@ function isPropArgumentInSetStateUpdater(context, node) {
156
132
scope . block . parent . arguments [ 0 ] . params &&
157
133
scope . block . parent . arguments [ 0 ] . params . length > 1
158
134
) {
159
- return scope . block . parent . arguments [ 0 ] . params [ 1 ] . name === node . object . name ;
135
+ return scope . block . parent . arguments [ 0 ] . params [ 1 ] . name === name ;
160
136
}
161
137
scope = scope . upper ;
162
138
}
163
139
return false ;
164
140
}
165
141
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
+
166
158
/**
167
159
* Checks if the prop has spread operator.
168
160
* @param {ASTNode } node The AST node being marked.
@@ -178,27 +170,7 @@ function hasSpreadOperator(context, node) {
178
170
* @param {ASTNode } node The AST node with the property.
179
171
* @return {string|undefined } the name of the property or undefined if not found
180
172
*/
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 ) {
202
174
const property = node . property ;
203
175
if ( property ) {
204
176
switch ( property . type ) {
@@ -225,21 +197,34 @@ function getPropertyName(node, context, utils, checkAsyncSafeLifeCycles) {
225
197
}
226
198
227
199
/**
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 }
231
206
*/
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 ) ;
243
228
}
244
229
245
230
module . exports = function usedPropTypesInstructions ( context , components , utils ) {
@@ -258,7 +243,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
258
243
let properties ;
259
244
switch ( node . type ) {
260
245
case 'MemberExpression' :
261
- name = getPropertyName ( node , context , utils , checkAsyncSafeLifeCycles ) ;
246
+ name = getPropertyName ( node ) ;
262
247
if ( name ) {
263
248
allNames = parentNames . concat ( name ) ;
264
249
if (
@@ -268,16 +253,16 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
268
253
) {
269
254
markPropTypesAsUsed ( node . parent , allNames ) ;
270
255
}
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
+ }
271
264
// Do not mark computed props as used.
272
265
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 ;
281
266
}
282
267
break ;
283
268
case 'ArrowFunctionExpression' :
@@ -293,31 +278,9 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
293
278
propParam . properties ;
294
279
break ;
295
280
}
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 ;
321
284
break ;
322
285
default :
323
286
throw new Error ( `${ node . type } ASTNodes are not handled by markPropTypesAsUsed` ) ;
@@ -334,15 +297,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
334
297
break ;
335
298
}
336
299
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 ;
346
301
usedPropTypes . push ( {
347
302
name,
348
303
allNames,
@@ -358,7 +313,8 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
358
313
}
359
314
const propName = ast . getKeyValue ( context , properties [ k ] ) ;
360
315
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 ) || { } ;
362
318
allNames = [ ] ;
363
319
while ( currentNode . property && currentNode . property . name !== 'props' ) {
364
320
allNames . unshift ( currentNode . property . name ) ;
@@ -436,19 +392,35 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
436
392
437
393
return {
438
394
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
+
440
400
// 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
+ }
447
410
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 ) ;
449
417
return ;
450
418
}
451
- markPropTypesAsUsed ( node ) ;
419
+
420
+ // let {firstname} = this.props
421
+ if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) ) {
422
+ markPropTypesAsUsed ( node . id ) ;
423
+ }
452
424
} ,
453
425
454
426
FunctionDeclaration : handleFunctionLikeExpressions ,
@@ -465,7 +437,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
465
437
} ,
466
438
467
439
MemberExpression ( node ) {
468
- if ( isPropTypesUsage ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
440
+ if ( isPropTypesUsageByMemberExpression ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
469
441
markPropTypesAsUsed ( node ) ;
470
442
}
471
443
} ,
0 commit comments