@@ -15,6 +15,46 @@ const ast = require('./ast');
15
15
const LIFE_CYCLE_METHODS = [ 'componentWillReceiveProps' , 'shouldComponentUpdate' , 'componentWillUpdate' , 'componentDidUpdate' ] ;
16
16
const ASYNC_SAFE_LIFE_CYCLE_METHODS = [ 'getDerivedStateFromProps' , 'getSnapshotBeforeUpdate' , 'UNSAFE_componentWillReceiveProps' , 'UNSAFE_componentWillUpdate' ] ;
17
17
18
+ function createPropVariables ( ) {
19
+ /** @type {Map<string, string[]> } Maps the variable to its definition. `props.a.b` is stored as `['a', 'b']` */
20
+ let propVariables = new Map ( ) ;
21
+ let hasBeenWritten = false ;
22
+ const stack = [ { propVariables, hasBeenWritten} ] ;
23
+ return {
24
+ pushScope ( ) {
25
+ // popVariables is not copied until first write.
26
+ stack . push ( { propVariables, hasBeenWritten : false } ) ;
27
+ } ,
28
+ popScope ( ) {
29
+ stack . pop ( ) ;
30
+ propVariables = stack [ stack . length - 1 ] . propVariables ;
31
+ hasBeenWritten = stack [ stack . length - 1 ] . hasBeenWritten ;
32
+ } ,
33
+ /**
34
+ * Add a variable name to the current scope
35
+ * @param {string } name
36
+ * @param {string[] } allNames Example: `props.a.b` should be formatted as `['a', 'b']`
37
+ */
38
+ set ( name , allNames ) {
39
+ if ( ! hasBeenWritten ) {
40
+ // copy on write
41
+ propVariables = new Map ( propVariables ) ;
42
+ stack [ stack . length - 1 ] . propVariables = propVariables ;
43
+ stack [ stack . length - 1 ] . hasBeenWritten = true ;
44
+ }
45
+ return propVariables . set ( name , allNames ) ;
46
+ } ,
47
+ /**
48
+ * Get the definition of a variable.
49
+ * @param {string } name
50
+ * @returns {string[] } Example: `props.a.b` is represented by `['a', 'b']`
51
+ */
52
+ get ( name ) {
53
+ return propVariables . get ( name ) ;
54
+ }
55
+ } ;
56
+ }
57
+
18
58
/**
19
59
* Checks if the string is one of `props`, `nextProps`, or `prevProps`
20
60
* @param {string } name The AST node being checked.
@@ -230,6 +270,8 @@ function isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafe
230
270
module . exports = function usedPropTypesInstructions ( context , components , utils ) {
231
271
const checkAsyncSafeLifeCycles = versionUtil . testReactVersion ( context , '16.3.0' ) ;
232
272
273
+ const propVariables = createPropVariables ( ) ;
274
+
233
275
/**
234
276
* Mark a prop type as used
235
277
* @param {ASTNode } node The AST node being marked.
@@ -261,6 +303,14 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
261
303
node . parent . id . parent = node . parent ; // patch for bug in eslint@4 in which ObjectPattern has no parent
262
304
markPropTypesAsUsed ( node . parent . id , allNames ) ;
263
305
}
306
+
307
+ // const a = props.a
308
+ if (
309
+ node . parent . type === 'VariableDeclarator' &&
310
+ node . parent . id . type === 'Identifier'
311
+ ) {
312
+ propVariables . set ( node . parent . id . name , allNames ) ;
313
+ }
264
314
// Do not mark computed props as used.
265
315
type = name !== '__COMPUTED_PROP__' ? 'direct' : null ;
266
316
}
@@ -314,6 +364,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
314
364
const propName = ast . getKeyValue ( context , properties [ k ] ) ;
315
365
316
366
if ( propName ) {
367
+ propVariables . set ( propName , parentNames . concat ( [ propName ] ) ) ;
317
368
usedPropTypes . push ( {
318
369
allNames : parentNames . concat ( [ propName ] ) ,
319
370
name : propName ,
@@ -371,6 +422,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
371
422
* FunctionDeclaration, or FunctionExpression
372
423
*/
373
424
function handleFunctionLikeExpressions ( node ) {
425
+ propVariables . pushScope ( ) ;
374
426
handleSetStateUpdater ( node ) ;
375
427
markDestructuredFunctionArgumentsAsUsed ( node ) ;
376
428
}
@@ -392,6 +444,11 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
392
444
393
445
return {
394
446
VariableDeclarator ( node ) {
447
+ // let props = this.props
448
+ if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) && node . id . type === 'Identifier' ) {
449
+ propVariables . set ( node . id . name , [ ] ) ;
450
+ }
451
+
395
452
// Only handles destructuring
396
453
if ( node . id . type !== 'ObjectPattern' ) {
397
454
return ;
@@ -400,14 +457,19 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
400
457
// let {props: {firstname}} = this
401
458
const propsProperty = node . id . properties . find ( property => (
402
459
property . key &&
403
- ( property . key . name === 'props' || property . key . value === 'props' ) &&
404
- property . value . type === 'ObjectPattern'
460
+ ( property . key . name === 'props' || property . key . value === 'props' )
405
461
) ) ;
406
- if ( propsProperty && node . init . type === 'ThisExpression ' ) {
462
+ if ( node . init . type === 'ThisExpression' && propsProperty && propsProperty . value . type === 'ObjectPattern ' ) {
407
463
markPropTypesAsUsed ( propsProperty . value ) ;
408
464
return ;
409
465
}
410
466
467
+ // let {props} = this
468
+ if ( node . init . type === 'ThisExpression' && propsProperty && propsProperty . value . name === 'props' ) {
469
+ propVariables . set ( 'props' , [ ] ) ;
470
+ return ;
471
+ }
472
+
411
473
// let {firstname} = props
412
474
if (
413
475
isCommonVariableNameForProps ( node . init . name ) &&
@@ -420,6 +482,12 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
420
482
// let {firstname} = this.props
421
483
if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) ) {
422
484
markPropTypesAsUsed ( node . id ) ;
485
+ return ;
486
+ }
487
+
488
+ // let {firstname} = thing, where thing is defined by const thing = this.props.**.*
489
+ if ( propVariables . get ( node . init . name ) ) {
490
+ markPropTypesAsUsed ( node , propVariables . get ( node . init . name ) ) ;
423
491
}
424
492
} ,
425
493
@@ -429,6 +497,12 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
429
497
430
498
FunctionExpression : handleFunctionLikeExpressions ,
431
499
500
+ 'FunctionDeclaration:exit' : propVariables . popScope ,
501
+
502
+ 'ArrowFunctionExpression:exit' : propVariables . popScope ,
503
+
504
+ 'FunctionExpression:exit' : propVariables . popScope ,
505
+
432
506
JSXSpreadAttribute ( node ) {
433
507
const component = components . get ( utils . getParentComponent ( ) ) ;
434
508
components . set ( component ? component . node : node , {
@@ -439,6 +513,11 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
439
513
MemberExpression ( node ) {
440
514
if ( isPropTypesUsageByMemberExpression ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
441
515
markPropTypesAsUsed ( node ) ;
516
+ return ;
517
+ }
518
+
519
+ if ( propVariables . get ( node . object . name ) ) {
520
+ markPropTypesAsUsed ( node , propVariables . get ( node . object . name ) ) ;
442
521
}
443
522
} ,
444
523
0 commit comments