@@ -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
+ Object . assign ( stack [ stack . length - 1 ] , { propVariables, hasBeenWritten : true } ) ;
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,10 @@ 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
+ const pushScope = propVariables . pushScope ;
275
+ const popScope = propVariables . popScope ;
276
+
233
277
/**
234
278
* Mark a prop type as used
235
279
* @param {ASTNode } node The AST node being marked.
@@ -261,6 +305,14 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
261
305
node . parent . id . parent = node . parent ; // patch for bug in eslint@4 in which ObjectPattern has no parent
262
306
markPropTypesAsUsed ( node . parent . id , allNames ) ;
263
307
}
308
+
309
+ // const a = props.a
310
+ if (
311
+ node . parent . type === 'VariableDeclarator' &&
312
+ node . parent . id . type === 'Identifier'
313
+ ) {
314
+ propVariables . set ( node . parent . id . name , allNames ) ;
315
+ }
264
316
// Do not mark computed props as used.
265
317
type = name !== '__COMPUTED_PROP__' ? 'direct' : null ;
266
318
}
@@ -314,6 +366,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
314
366
const propName = ast . getKeyValue ( context , properties [ k ] ) ;
315
367
316
368
if ( propName ) {
369
+ propVariables . set ( propName , parentNames . concat ( propName ) ) ;
317
370
usedPropTypes . push ( {
318
371
allNames : parentNames . concat ( [ propName ] ) ,
319
372
name : propName ,
@@ -371,6 +424,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
371
424
* FunctionDeclaration, or FunctionExpression
372
425
*/
373
426
function handleFunctionLikeExpressions ( node ) {
427
+ pushScope ( ) ;
374
428
handleSetStateUpdater ( node ) ;
375
429
markDestructuredFunctionArgumentsAsUsed ( node ) ;
376
430
}
@@ -392,6 +446,11 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
392
446
393
447
return {
394
448
VariableDeclarator ( node ) {
449
+ // let props = this.props
450
+ if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) && node . id . type === 'Identifier' ) {
451
+ propVariables . set ( node . id . name , [ ] ) ;
452
+ }
453
+
395
454
// Only handles destructuring
396
455
if ( node . id . type !== 'ObjectPattern' ) {
397
456
return ;
@@ -400,14 +459,19 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
400
459
// let {props: {firstname}} = this
401
460
const propsProperty = node . id . properties . find ( property => (
402
461
property . key &&
403
- ( property . key . name === 'props' || property . key . value === 'props' ) &&
404
- property . value . type === 'ObjectPattern'
462
+ ( property . key . name === 'props' || property . key . value === 'props' )
405
463
) ) ;
406
- if ( propsProperty && node . init . type === 'ThisExpression ' ) {
464
+ if ( node . init . type === 'ThisExpression' && propsProperty && propsProperty . value . type === 'ObjectPattern ' ) {
407
465
markPropTypesAsUsed ( propsProperty . value ) ;
408
466
return ;
409
467
}
410
468
469
+ // let {props} = this
470
+ if ( node . init . type === 'ThisExpression' && propsProperty && propsProperty . value . name === 'props' ) {
471
+ propVariables . set ( 'props' , [ ] ) ;
472
+ return ;
473
+ }
474
+
411
475
// let {firstname} = props
412
476
if (
413
477
isCommonVariableNameForProps ( node . init . name ) &&
@@ -420,6 +484,12 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
420
484
// let {firstname} = this.props
421
485
if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) ) {
422
486
markPropTypesAsUsed ( node . id ) ;
487
+ return ;
488
+ }
489
+
490
+ // let {firstname} = thing, where thing is defined by const thing = this.props.**.*
491
+ if ( propVariables . get ( node . init . name ) ) {
492
+ markPropTypesAsUsed ( node , propVariables . get ( node . init . name ) ) ;
423
493
}
424
494
} ,
425
495
@@ -429,6 +499,12 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
429
499
430
500
FunctionExpression : handleFunctionLikeExpressions ,
431
501
502
+ 'FunctionDeclaration:exit' : popScope ,
503
+
504
+ 'ArrowFunctionExpression:exit' : popScope ,
505
+
506
+ 'FunctionExpression:exit' : popScope ,
507
+
432
508
JSXSpreadAttribute ( node ) {
433
509
const component = components . get ( utils . getParentComponent ( ) ) ;
434
510
components . set ( component ? component . node : node , {
@@ -439,6 +515,11 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
439
515
MemberExpression ( node ) {
440
516
if ( isPropTypesUsageByMemberExpression ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
441
517
markPropTypesAsUsed ( node ) ;
518
+ return ;
519
+ }
520
+
521
+ if ( propVariables . get ( node . object . name ) ) {
522
+ markPropTypesAsUsed ( node , propVariables . get ( node . object . name ) ) ;
442
523
}
443
524
} ,
444
525
0 commit comments