@@ -15,6 +15,46 @@ const ast = require('./ast');
1515const LIFE_CYCLE_METHODS = [ 'componentWillReceiveProps' , 'shouldComponentUpdate' , 'componentWillUpdate' , 'componentDidUpdate' ] ;
1616const ASYNC_SAFE_LIFE_CYCLE_METHODS = [ 'getDerivedStateFromProps' , 'getSnapshotBeforeUpdate' , 'UNSAFE_componentWillReceiveProps' , 'UNSAFE_componentWillUpdate' ] ;
1717
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+
1858/**
1959 * Checks if the string is one of `props`, `nextProps`, or `prevProps`
2060 * @param {string } name The AST node being checked.
@@ -230,6 +270,8 @@ function isPropTypesUsageByMemberExpression(node, context, utils, checkAsyncSafe
230270module . exports = function usedPropTypesInstructions ( context , components , utils ) {
231271 const checkAsyncSafeLifeCycles = versionUtil . testReactVersion ( context , '16.3.0' ) ;
232272
273+ const propVariables = createPropVariables ( ) ;
274+
233275 /**
234276 * Mark a prop type as used
235277 * @param {ASTNode } node The AST node being marked.
@@ -261,6 +303,14 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
261303 node . parent . id . parent = node . parent ; // patch for bug in eslint@4 in which ObjectPattern has no parent
262304 markPropTypesAsUsed ( node . parent . id , allNames ) ;
263305 }
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+ }
264314 // Do not mark computed props as used.
265315 type = name !== '__COMPUTED_PROP__' ? 'direct' : null ;
266316 }
@@ -314,6 +364,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
314364 const propName = ast . getKeyValue ( context , properties [ k ] ) ;
315365
316366 if ( propName ) {
367+ propVariables . set ( propName , parentNames . concat ( [ propName ] ) ) ;
317368 usedPropTypes . push ( {
318369 allNames : parentNames . concat ( [ propName ] ) ,
319370 name : propName ,
@@ -371,6 +422,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
371422 * FunctionDeclaration, or FunctionExpression
372423 */
373424 function handleFunctionLikeExpressions ( node ) {
425+ propVariables . pushScope ( ) ;
374426 handleSetStateUpdater ( node ) ;
375427 markDestructuredFunctionArgumentsAsUsed ( node ) ;
376428 }
@@ -392,6 +444,11 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
392444
393445 return {
394446 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+
395452 // Only handles destructuring
396453 if ( node . id . type !== 'ObjectPattern' ) {
397454 return ;
@@ -400,14 +457,19 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
400457 // let {props: {firstname}} = this
401458 const propsProperty = node . id . properties . find ( property => (
402459 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' )
405461 ) ) ;
406- if ( propsProperty && node . init . type === 'ThisExpression ' ) {
462+ if ( node . init . type === 'ThisExpression' && propsProperty && propsProperty . value . type === 'ObjectPattern ' ) {
407463 markPropTypesAsUsed ( propsProperty . value ) ;
408464 return ;
409465 }
410466
467+ // let {props} = this
468+ if ( node . init . type === 'ThisExpression' && propsProperty && propsProperty . value . name === 'props' ) {
469+ propVariables . set ( 'props' , [ ] ) ;
470+ return ;
471+ }
472+
411473 // let {firstname} = props
412474 if (
413475 isCommonVariableNameForProps ( node . init . name ) &&
@@ -420,6 +482,12 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
420482 // let {firstname} = this.props
421483 if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) ) {
422484 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 ) ) ;
423491 }
424492 } ,
425493
@@ -429,6 +497,12 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
429497
430498 FunctionExpression : handleFunctionLikeExpressions ,
431499
500+ 'FunctionDeclaration:exit' : propVariables . popScope ,
501+
502+ 'ArrowFunctionExpression:exit' : propVariables . popScope ,
503+
504+ 'FunctionExpression:exit' : propVariables . popScope ,
505+
432506 JSXSpreadAttribute ( node ) {
433507 const component = components . get ( utils . getParentComponent ( ) ) ;
434508 components . set ( component ? component . node : node , {
@@ -439,6 +513,11 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
439513 MemberExpression ( node ) {
440514 if ( isPropTypesUsageByMemberExpression ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
441515 markPropTypesAsUsed ( node ) ;
516+ return ;
517+ }
518+
519+ if ( propVariables . get ( node . object . name ) ) {
520+ markPropTypesAsUsed ( node , propVariables . get ( node . object . name ) ) ;
442521 }
443522 } ,
444523
0 commit comments