Skip to content

Commit 7e336aa

Browse files
authored
Merge pull request #1415 from jseminck/intersection-imported-type
Fix issues with IntersectionTypeAnnotation for prop-types and no-unused-prop-types
2 parents ad26580 + 09c4ed7 commit 7e336aa

File tree

4 files changed

+395
-99
lines changed

4 files changed

+395
-99
lines changed

lib/rules/no-unused-prop-types.js

Lines changed: 76 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,7 @@ module.exports = {
359359
value.callee.object &&
360360
hasCustomValidator(value.callee.object.name)
361361
) {
362-
return true;
362+
return {};
363363
}
364364

365365
if (
@@ -387,23 +387,20 @@ module.exports = {
387387
switch (callName) {
388388
case 'shape':
389389
if (skipShapeProps) {
390-
return true;
390+
return {};
391391
}
392392

393393
if (argument.type !== 'ObjectExpression') {
394394
// Invalid proptype or cannot analyse statically
395-
return true;
395+
return {};
396396
}
397397
const shapeTypeDefinition = {
398398
type: 'shape',
399399
children: []
400400
};
401401
iterateProperties(argument.properties, (childKey, childValue) => {
402402
const fullName = [parentName, childKey].join('.');
403-
let types = buildReactDeclarationTypes(childValue, fullName);
404-
if (types === true) {
405-
types = {};
406-
}
403+
const types = buildReactDeclarationTypes(childValue, fullName);
407404
types.fullName = fullName;
408405
types.name = childKey;
409406
types.node = childValue;
@@ -413,10 +410,7 @@ module.exports = {
413410
case 'arrayOf':
414411
case 'objectOf':
415412
const fullName = [parentName, '*'].join('.');
416-
let child = buildReactDeclarationTypes(argument, fullName);
417-
if (child === true) {
418-
child = {};
419-
}
413+
const child = buildReactDeclarationTypes(argument, fullName);
420414
child.fullName = fullName;
421415
child.name = '__ANY_KEY__';
422416
child.node = argument;
@@ -430,7 +424,7 @@ module.exports = {
430424
!argument.elements.length
431425
) {
432426
// Invalid proptype or cannot analyse statically
433-
return true;
427+
return {};
434428
}
435429
const unionTypeDefinition = {
436430
type: 'union',
@@ -439,7 +433,7 @@ module.exports = {
439433
for (let i = 0, j = argument.elements.length; i < j; i++) {
440434
const type = buildReactDeclarationTypes(argument.elements[i], parentName);
441435
// keep only complex type
442-
if (type !== true) {
436+
if (Object.keys(type).length > 0) {
443437
if (type.children === true) {
444438
// every child is accepted for one type, abort type analysis
445439
unionTypeDefinition.children = true;
@@ -451,7 +445,7 @@ module.exports = {
451445
}
452446
if (unionTypeDefinition.length === 0) {
453447
// no complex type found, simply accept everything
454-
return true;
448+
return {};
455449
}
456450
return unionTypeDefinition;
457451
case 'instanceOf':
@@ -462,19 +456,19 @@ module.exports = {
462456
};
463457
case 'oneOf':
464458
default:
465-
return true;
459+
return {};
466460
}
467461
}
468462
// Unknown property or accepts everything (any, object, ...)
469-
return true;
463+
return {};
470464
}
471465

472466
/**
473467
* Creates the representation of the React props type annotation for the component.
474468
* The representation is used to verify nested used properties.
475469
* @param {ASTNode} annotation Type annotation for the props class property.
476470
* @param {String} parentName Name of the parent prop node.
477-
* @return {Object|Boolean} The representation of the declaration, true means
471+
* @return {Object} The representation of the declaration, an empty object means
478472
* the property is declared without the need for further analysis.
479473
*/
480474
function buildTypeAnnotationDeclarationTypes(annotation, parentName) {
@@ -483,21 +477,18 @@ module.exports = {
483477
if (typeScope(annotation.id.name)) {
484478
return buildTypeAnnotationDeclarationTypes(typeScope(annotation.id.name), parentName);
485479
}
486-
return true;
480+
return {};
487481
case 'ObjectTypeAnnotation':
488482
if (skipShapeProps) {
489-
return true;
483+
return {};
490484
}
491485
const shapeTypeDefinition = {
492486
type: 'shape',
493487
children: []
494488
};
495489
iterateProperties(annotation.properties, (childKey, childValue) => {
496490
const fullName = [parentName, childKey].join('.');
497-
let types = buildTypeAnnotationDeclarationTypes(childValue, fullName);
498-
if (types === true) {
499-
types = {};
500-
}
491+
const types = buildTypeAnnotationDeclarationTypes(childValue, fullName);
501492
types.fullName = fullName;
502493
types.name = childKey;
503494
types.node = childValue;
@@ -512,7 +503,7 @@ module.exports = {
512503
for (let i = 0, j = annotation.types.length; i < j; i++) {
513504
const type = buildTypeAnnotationDeclarationTypes(annotation.types[i], parentName);
514505
// keep only complex type
515-
if (type !== true) {
506+
if (Object.keys(type).length > 0) {
516507
if (type.children === true) {
517508
// every child is accepted for one type, abort type analysis
518509
unionTypeDefinition.children = true;
@@ -523,16 +514,13 @@ module.exports = {
523514
unionTypeDefinition.children.push(type);
524515
}
525516
if (unionTypeDefinition.children.length === 0) {
526-
// no complex type found, simply accept everything
527-
return true;
517+
// no complex type found
518+
return {};
528519
}
529520
return unionTypeDefinition;
530521
case 'ArrayTypeAnnotation':
531522
const fullName = [parentName, '*'].join('.');
532-
let child = buildTypeAnnotationDeclarationTypes(annotation.elementType, fullName);
533-
if (child === true) {
534-
child = {};
535-
}
523+
const child = buildTypeAnnotationDeclarationTypes(annotation.elementType, fullName);
536524
child.fullName = fullName;
537525
child.name = '__ANY_KEY__';
538526
child.node = annotation;
@@ -542,7 +530,7 @@ module.exports = {
542530
};
543531
default:
544532
// Unknown or accepts everything.
545-
return true;
533+
return {};
546534
}
547535
}
548536

@@ -724,6 +712,60 @@ module.exports = {
724712
});
725713
}
726714

715+
/**
716+
* Marks all props found inside ObjectTypeAnnotaiton as declared.
717+
*
718+
* Modifies the declaredProperties object
719+
* @param {ASTNode} propTypes
720+
* @param {Object} declaredPropTypes
721+
* @returns {Boolean} True if propTypes should be ignored (e.g. when a type can't be resolved, when it is imported)
722+
*/
723+
function declarePropTypesForObjectTypeAnnotation(propTypes, declaredPropTypes) {
724+
let ignorePropsValidation = false;
725+
726+
iterateProperties(propTypes.properties, (key, value) => {
727+
if (!value) {
728+
ignorePropsValidation = true;
729+
return;
730+
}
731+
732+
const types = buildTypeAnnotationDeclarationTypes(value, key);
733+
types.fullName = key;
734+
types.name = key;
735+
types.node = value;
736+
declaredPropTypes.push(types);
737+
});
738+
739+
return ignorePropsValidation;
740+
}
741+
742+
/**
743+
* Marks all props found inside IntersectionTypeAnnotation as declared.
744+
* Since InterSectionTypeAnnotations can be nested, this handles recursively.
745+
*
746+
* Modifies the declaredPropTypes object
747+
* @param {ASTNode} propTypes
748+
* @param {Object} declaredPropTypes
749+
* @returns {Boolean} True if propTypes should be ignored (e.g. when a type can't be resolved, when it is imported)
750+
*/
751+
function declarePropTypesForIntersectionTypeAnnotation(propTypes, declaredPropTypes) {
752+
return propTypes.types.some(annotation => {
753+
if (annotation.type === 'ObjectTypeAnnotation') {
754+
return declarePropTypesForObjectTypeAnnotation(annotation, declaredPropTypes);
755+
}
756+
757+
const typeNode = typeScope(annotation.id.name);
758+
759+
if (!typeNode) {
760+
return true;
761+
} else if (typeNode.type === 'IntersectionTypeAnnotation') {
762+
return declarePropTypesForIntersectionTypeAnnotation(typeNode, declaredPropTypes);
763+
}
764+
765+
return declarePropTypesForObjectTypeAnnotation(typeNode, declaredPropTypes);
766+
});
767+
}
768+
727769
/**
728770
* Mark a prop type as declared
729771
* @param {ASTNode} node The AST node being checked.
@@ -736,31 +778,15 @@ module.exports = {
736778

737779
switch (propTypes && propTypes.type) {
738780
case 'ObjectTypeAnnotation':
739-
iterateProperties(propTypes.properties, (key, value) => {
740-
if (!value) {
741-
ignorePropsValidation = true;
742-
return;
743-
}
744-
let types = buildTypeAnnotationDeclarationTypes(value, key);
745-
if (types === true) {
746-
types = {};
747-
}
748-
types.fullName = key;
749-
types.name = key;
750-
types.node = value;
751-
declaredPropTypes.push(types);
752-
});
781+
ignorePropsValidation = declarePropTypesForObjectTypeAnnotation(propTypes, declaredPropTypes);
753782
break;
754783
case 'ObjectExpression':
755784
iterateProperties(propTypes.properties, (key, value) => {
756785
if (!value) {
757786
ignorePropsValidation = true;
758787
return;
759788
}
760-
let types = buildReactDeclarationTypes(value, key);
761-
if (types === true) {
762-
types = {};
763-
}
789+
const types = buildReactDeclarationTypes(value, key);
764790
types.fullName = key;
765791
types.name = key;
766792
types.node = value;
@@ -791,24 +817,7 @@ module.exports = {
791817
}
792818
break;
793819
case 'IntersectionTypeAnnotation':
794-
propTypes.types.forEach(annotation => {
795-
const propsType = typeScope(annotation.id.name);
796-
iterateProperties(propsType.properties, (key, value) => {
797-
if (!value) {
798-
ignorePropsValidation = true;
799-
return;
800-
}
801-
802-
let types = buildTypeAnnotationDeclarationTypes(value, key);
803-
if (types === true) {
804-
types = {};
805-
}
806-
types.fullName = key;
807-
types.name = key;
808-
types.node = value;
809-
declaredPropTypes.push(types);
810-
});
811-
});
820+
ignorePropsValidation = declarePropTypesForIntersectionTypeAnnotation(propTypes, declaredPropTypes);
812821
break;
813822
case null:
814823
break;

0 commit comments

Comments
 (0)