@@ -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+ 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+
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,10 @@ 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+ const pushScope = propVariables . pushScope ;
275+ const popScope = propVariables . popScope ;
276+
233277 /**
234278 * Mark a prop type as used
235279 * @param {ASTNode } node The AST node being marked.
@@ -261,6 +305,14 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
261305 node . parent . id . parent = node . parent ; // patch for bug in eslint@4 in which ObjectPattern has no parent
262306 markPropTypesAsUsed ( node . parent . id , allNames ) ;
263307 }
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+ }
264316 // Do not mark computed props as used.
265317 type = name !== '__COMPUTED_PROP__' ? 'direct' : null ;
266318 }
@@ -314,6 +366,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
314366 const propName = ast . getKeyValue ( context , properties [ k ] ) ;
315367
316368 if ( propName ) {
369+ propVariables . set ( propName , parentNames . concat ( propName ) ) ;
317370 usedPropTypes . push ( {
318371 allNames : parentNames . concat ( [ propName ] ) ,
319372 name : propName ,
@@ -371,6 +424,7 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
371424 * FunctionDeclaration, or FunctionExpression
372425 */
373426 function handleFunctionLikeExpressions ( node ) {
427+ pushScope ( ) ;
374428 handleSetStateUpdater ( node ) ;
375429 markDestructuredFunctionArgumentsAsUsed ( node ) ;
376430 }
@@ -392,6 +446,11 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
392446
393447 return {
394448 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+
395454 // Only handles destructuring
396455 if ( node . id . type !== 'ObjectPattern' ) {
397456 return ;
@@ -400,14 +459,19 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
400459 // let {props: {firstname}} = this
401460 const propsProperty = node . id . properties . find ( property => (
402461 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' )
405463 ) ) ;
406- if ( propsProperty && node . init . type === 'ThisExpression ' ) {
464+ if ( node . init . type === 'ThisExpression' && propsProperty && propsProperty . value . type === 'ObjectPattern ' ) {
407465 markPropTypesAsUsed ( propsProperty . value ) ;
408466 return ;
409467 }
410468
469+ // let {props} = this
470+ if ( node . init . type === 'ThisExpression' && propsProperty && propsProperty . value . name === 'props' ) {
471+ propVariables . set ( 'props' , [ ] ) ;
472+ return ;
473+ }
474+
411475 // let {firstname} = props
412476 if (
413477 isCommonVariableNameForProps ( node . init . name ) &&
@@ -420,6 +484,12 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
420484 // let {firstname} = this.props
421485 if ( isThisDotProps ( node . init ) && isInClassComponent ( utils ) ) {
422486 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 ) ) ;
423493 }
424494 } ,
425495
@@ -429,6 +499,12 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
429499
430500 FunctionExpression : handleFunctionLikeExpressions ,
431501
502+ 'FunctionDeclaration:exit' : popScope ,
503+
504+ 'ArrowFunctionExpression:exit' : popScope ,
505+
506+ 'FunctionExpression:exit' : popScope ,
507+
432508 JSXSpreadAttribute ( node ) {
433509 const component = components . get ( utils . getParentComponent ( ) ) ;
434510 components . set ( component ? component . node : node , {
@@ -439,6 +515,11 @@ module.exports = function usedPropTypesInstructions(context, components, utils)
439515 MemberExpression ( node ) {
440516 if ( isPropTypesUsageByMemberExpression ( node , context , utils , checkAsyncSafeLifeCycles ) ) {
441517 markPropTypesAsUsed ( node ) ;
518+ return ;
519+ }
520+
521+ if ( propVariables . get ( node . object . name ) ) {
522+ markPropTypesAsUsed ( node , propVariables . get ( node . object . name ) ) ;
442523 }
443524 } ,
444525
0 commit comments