@@ -19,9 +19,28 @@ module.exports = Components.detect(function(context, components, utils) {
19
19
var configuration = context . options [ 0 ] || { } ;
20
20
var ignored = configuration . ignore || [ ] ;
21
21
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 ;
22
25
23
26
var MISSING_MESSAGE = '\'{{name}}\' is missing in props validation' ;
24
27
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
+
25
44
/**
26
45
* Checks if we are using a prop
27
46
* @param {ASTNode } node The AST node being checked.
@@ -36,6 +55,26 @@ module.exports = Components.detect(function(context, components, utils) {
36
55
return isClassUsage || isStatelessFunctionUsage ;
37
56
}
38
57
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
+
39
78
/**
40
79
* Checks if we are declaring a prop
41
80
* @param {ASTNode } node The AST node being checked.
@@ -189,6 +228,10 @@ module.exports = Components.detect(function(context, components, utils) {
189
228
* @return {string } the name of the key
190
229
*/
191
230
function getKeyValue ( node ) {
231
+ if ( node . type === 'ObjectTypeProperty' ) {
232
+ var tokens = context . getFirstTokens ( node , 1 ) ;
233
+ return tokens [ 0 ] . value ;
234
+ }
192
235
var key = node . key || node . argument ;
193
236
return key . type === 'Identifier' ? key . name : key . value ;
194
237
}
@@ -214,7 +257,7 @@ module.exports = Components.detect(function(context, components, utils) {
214
257
/**
215
258
* Creates the representation of the React propTypes for the component.
216
259
* 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
218
261
* @return {Object|Boolean } The representation of the declaration, true means
219
262
* the property is declared without the need for further analysis.
220
263
*/
@@ -315,6 +358,65 @@ module.exports = Components.detect(function(context, components, utils) {
315
358
return true ;
316
359
}
317
360
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
+
318
420
/**
319
421
* Check if we are in a class constructor
320
422
* @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) {
488
590
var ignorePropsValidation = false ;
489
591
490
592
switch ( propTypes && propTypes . type ) {
593
+ case 'ObjectTypeAnnotation' :
594
+ iterateProperties ( propTypes . properties , function ( key , value ) {
595
+ declaredPropTypes [ key ] = buildTypeAnnotationDeclarationTypes ( value ) ;
596
+ } ) ;
597
+ break ;
491
598
case 'ObjectExpression' :
492
599
iterateProperties ( propTypes . properties , function ( key , value ) {
493
600
declaredPropTypes [ key ] = buildReactDeclarationTypes ( value ) ;
@@ -567,16 +674,38 @@ module.exports = Components.detect(function(context, components, utils) {
567
674
}
568
675
}
569
676
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
+
570
698
// --------------------------------------------------------------------------
571
699
// Public
572
700
// --------------------------------------------------------------------------
573
701
574
702
return {
575
703
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 ) ;
578
708
}
579
- markPropTypesAsDeclared ( node , node . value ) ;
580
709
} ,
581
710
582
711
VariableDeclarator : function ( node ) {
@@ -643,7 +772,24 @@ module.exports = Components.detect(function(context, components, utils) {
643
772
} ) ;
644
773
} ,
645
774
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
+
646
791
'Program:exit' : function ( ) {
792
+ stack = null ;
647
793
var list = components . list ( ) ;
648
794
// Report undeclared proptypes for all classes
649
795
for ( var component in list ) {
0 commit comments