@@ -19,9 +19,28 @@ module.exports = Components.detect(function(context, components, utils) {
1919 var configuration = context . options [ 0 ] || { } ;
2020 var ignored = configuration . ignore || [ ] ;
2121 var customValidators = configuration . customValidators || [ ] ;
22+ // Used to track the type annotations in scope.
23+ // Necessary because babel's scopes do not track type annotations.
24+ var stack = null ;
2225
2326 var MISSING_MESSAGE = '\'{{name}}\' is missing in props validation' ;
2427
28+ /**
29+ * Helper for accessing the current scope in the stack.
30+ * @param {string } key The name of the identifier to access. If omitted, returns the full scope.
31+ * @param {ASTNode } value If provided sets the new value for the identifier.
32+ * @returns {Object|ASTNode } Either the whole scope or the ASTNode associated with the given identifier.
33+ */
34+ function typeScope ( key , value ) {
35+ if ( arguments . length === 0 ) {
36+ return stack [ stack . length - 1 ] ;
37+ } else if ( arguments . length === 1 ) {
38+ return stack [ stack . length - 1 ] [ key ] ;
39+ }
40+ stack [ stack . length - 1 ] [ key ] = value ;
41+ return value ;
42+ }
43+
2544 /**
2645 * Checks if we are using a prop
2746 * @param {ASTNode } node The AST node being checked.
@@ -36,6 +55,26 @@ module.exports = Components.detect(function(context, components, utils) {
3655 return isClassUsage || isStatelessFunctionUsage ;
3756 }
3857
58+ /**
59+ * Checks if we are declaring a `props` class property with a flow type annotation.
60+ * @param {ASTNode } node The AST node being checked.
61+ * @returns {Boolean } True if the node is a type annotated props declaration, false if not.
62+ */
63+ function isAnnotatedPropsDeclaration ( node ) {
64+ if ( node && node . type === 'ClassProperty' ) {
65+ var tokens = context . getFirstTokens ( node , 2 ) ;
66+ if (
67+ node . typeAnnotation && (
68+ tokens [ 0 ] . value === 'props' ||
69+ ( tokens [ 1 ] && tokens [ 1 ] . value === 'props' )
70+ )
71+ ) {
72+ return true ;
73+ }
74+ }
75+ return false ;
76+ }
77+
3978 /**
4079 * Checks if we are declaring a prop
4180 * @param {ASTNode } node The AST node being checked.
@@ -189,6 +228,10 @@ module.exports = Components.detect(function(context, components, utils) {
189228 * @return {string } the name of the key
190229 */
191230 function getKeyValue ( node ) {
231+ if ( node . type === 'ObjectTypeProperty' ) {
232+ var tokens = context . getFirstTokens ( node , 1 ) ;
233+ return tokens [ 0 ] . value ;
234+ }
192235 var key = node . key || node . argument ;
193236 return key . type === 'Identifier' ? key . name : key . value ;
194237 }
@@ -214,7 +257,7 @@ module.exports = Components.detect(function(context, components, utils) {
214257 /**
215258 * Creates the representation of the React propTypes for the component.
216259 * The representation is used to verify nested used properties.
217- * @param {ASTNode } value Node of the React.PropTypes for the desired propery
260+ * @param {ASTNode } value Node of the React.PropTypes for the desired property
218261 * @return {Object|Boolean } The representation of the declaration, true means
219262 * the property is declared without the need for further analysis.
220263 */
@@ -315,6 +358,65 @@ module.exports = Components.detect(function(context, components, utils) {
315358 return true ;
316359 }
317360
361+ /**
362+ * Creates the representation of the React props type annotation for the component.
363+ * The representation is used to verify nested used properties.
364+ * @param {ASTNode } annotation Type annotation for the props class property.
365+ * @return {Object|Boolean } The representation of the declaration, true means
366+ * the property is declared without the need for further analysis.
367+ */
368+ function buildTypeAnnotationDeclarationTypes ( annotation ) {
369+ switch ( annotation . type ) {
370+ case 'GenericTypeAnnotation' :
371+ if ( typeScope ( annotation . id . name ) ) {
372+ return buildTypeAnnotationDeclarationTypes ( typeScope ( annotation . id . name ) ) ;
373+ }
374+ return true ;
375+ case 'ObjectTypeAnnotation' :
376+ var shapeTypeDefinition = {
377+ type : 'shape' ,
378+ children : { }
379+ } ;
380+ iterateProperties ( annotation . properties , function ( childKey , childValue ) {
381+ shapeTypeDefinition . children [ childKey ] = buildTypeAnnotationDeclarationTypes ( childValue ) ;
382+ } ) ;
383+ return shapeTypeDefinition ;
384+ case 'UnionTypeAnnotation' :
385+ var unionTypeDefinition = {
386+ type : 'union' ,
387+ children : [ ]
388+ } ;
389+ for ( var i = 0 , j = annotation . types . length ; i < j ; i ++ ) {
390+ var type = buildTypeAnnotationDeclarationTypes ( annotation . types [ i ] ) ;
391+ // keep only complex type
392+ if ( type !== true ) {
393+ if ( type . children === true ) {
394+ // every child is accepted for one type, abort type analysis
395+ unionTypeDefinition . children = true ;
396+ return unionTypeDefinition ;
397+ }
398+ }
399+
400+ unionTypeDefinition . children . push ( type ) ;
401+ }
402+ if ( unionTypeDefinition . children . length === 0 ) {
403+ // no complex type found, simply accept everything
404+ return true ;
405+ }
406+ return unionTypeDefinition ;
407+ case 'ArrayTypeAnnotation' :
408+ return {
409+ type : 'object' ,
410+ children : {
411+ __ANY_KEY__ : buildTypeAnnotationDeclarationTypes ( annotation . elementType )
412+ }
413+ } ;
414+ default :
415+ // Unknown or accepts everything.
416+ return true ;
417+ }
418+ }
419+
318420 /**
319421 * Check if we are in a class constructor
320422 * @return {boolean } true if we are in a class constructor, false if not
@@ -488,6 +590,11 @@ module.exports = Components.detect(function(context, components, utils) {
488590 var ignorePropsValidation = false ;
489591
490592 switch ( propTypes && propTypes . type ) {
593+ case 'ObjectTypeAnnotation' :
594+ iterateProperties ( propTypes . properties , function ( key , value ) {
595+ declaredPropTypes [ key ] = buildTypeAnnotationDeclarationTypes ( value ) ;
596+ } ) ;
597+ break ;
491598 case 'ObjectExpression' :
492599 iterateProperties ( propTypes . properties , function ( key , value ) {
493600 declaredPropTypes [ key ] = buildReactDeclarationTypes ( value ) ;
@@ -567,16 +674,38 @@ module.exports = Components.detect(function(context, components, utils) {
567674 }
568675 }
569676
677+ /**
678+ * Resolve the type annotation for a given node.
679+ * Flow annotations are sometimes wrapped in outer `TypeAnnotation`
680+ * and `NullableTypeAnnotation` nodes which obscure the annotation we're
681+ * interested in.
682+ * This method also resolves type aliases where possible.
683+ *
684+ * @param {ASTNode } node The annotation or a node containing the type annotation.
685+ * @returns {ASTNode } The resolved type annotation for the node.
686+ */
687+ function resolveTypeAnnotation ( node ) {
688+ var annotation = node . typeAnnotation || node ;
689+ while ( annotation && ( annotation . type === 'TypeAnnotation' || annotation . type === 'NullableTypeAnnotation' ) ) {
690+ annotation = annotation . typeAnnotation ;
691+ }
692+ if ( annotation . type === 'GenericTypeAnnotation' && typeScope ( annotation . id . name ) ) {
693+ return typeScope ( annotation . id . name ) ;
694+ }
695+ return annotation ;
696+ }
697+
570698 // --------------------------------------------------------------------------
571699 // Public
572700 // --------------------------------------------------------------------------
573701
574702 return {
575703 ClassProperty : function ( node ) {
576- if ( ! isPropTypesDeclaration ( node ) ) {
577- return ;
704+ if ( isAnnotatedPropsDeclaration ( node ) ) {
705+ markPropTypesAsDeclared ( node , resolveTypeAnnotation ( node ) ) ;
706+ } else if ( isPropTypesDeclaration ( node ) ) {
707+ markPropTypesAsDeclared ( node , node . value ) ;
578708 }
579- markPropTypesAsDeclared ( node , node . value ) ;
580709 } ,
581710
582711 VariableDeclarator : function ( node ) {
@@ -643,7 +772,24 @@ module.exports = Components.detect(function(context, components, utils) {
643772 } ) ;
644773 } ,
645774
775+ TypeAlias : function ( node ) {
776+ typeScope ( node . id . name , node . right ) ;
777+ } ,
778+
779+ Program : function ( ) {
780+ stack = [ { } ] ;
781+ } ,
782+
783+ BlockStatement : function ( ) {
784+ stack . push ( Object . create ( typeScope ( ) ) ) ;
785+ } ,
786+
787+ 'BlockStatement:exit' : function ( ) {
788+ stack . pop ( ) ;
789+ } ,
790+
646791 'Program:exit' : function ( ) {
792+ stack = null ;
647793 var list = components . list ( ) ;
648794 // Report undeclared proptypes for all classes
649795 for ( var component in list ) {
0 commit comments