diff --git a/lib/rules/no-unused-prop-types.js b/lib/rules/no-unused-prop-types.js
index 64de1fbd25..3948d67398 100644
--- a/lib/rules/no-unused-prop-types.js
+++ b/lib/rules/no-unused-prop-types.js
@@ -146,7 +146,7 @@ module.exports = {
function mustBeValidated(component) {
return Boolean(
component &&
- !component.ignorePropsValidation
+ !component.ignoreUnusedPropTypesValidation
);
}
@@ -397,7 +397,7 @@ module.exports = {
const component = components.get(utils.getParentComponent());
const usedPropTypes = component && component.usedPropTypes || [];
- let ignorePropsValidation = component && component.ignorePropsValidation || false;
+ let ignoreUnusedPropTypesValidation = component && component.ignoreUnusedPropTypesValidation || false;
switch (type) {
case 'direct':
@@ -414,7 +414,7 @@ module.exports = {
case 'destructuring':
for (let k = 0, l = (properties || []).length; k < l; k++) {
if (hasSpreadOperator(properties[k]) || properties[k].computed) {
- ignorePropsValidation = true;
+ ignoreUnusedPropTypesValidation = true;
break;
}
const propName = getKeyValue(properties[k]);
@@ -441,7 +441,7 @@ module.exports = {
components.set(component ? component.node : node, {
usedPropTypes: usedPropTypes,
- ignorePropsValidation: ignorePropsValidation
+ ignoreUnusedPropTypesValidation: ignoreUnusedPropTypesValidation
});
}
diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js
index 239c58bcec..c35e811bb4 100644
--- a/lib/rules/prop-types.js
+++ b/lib/rules/prop-types.js
@@ -191,7 +191,7 @@ module.exports = {
return true;
}
// Consider every children as declared
- if (propType.children === true) {
+ if (propType.children === true || propType.containsSpread) {
return true;
}
if (propType.acceptedProperties) {
diff --git a/lib/util/propTypes.js b/lib/util/propTypes.js
index 88be549151..d58af934de 100644
--- a/lib/util/propTypes.js
+++ b/lib/util/propTypes.js
@@ -143,10 +143,10 @@ module.exports = function propTypesInstructions(context, components, utils) {
}
});
- // nested object type spread means we need to ignore/accept everything in this object
- if (containsObjectTypeSpread) {
- return {};
- }
+ // Mark if this shape has spread. We will know to consider all props from this shape as having propTypes,
+ // but still have the ability to detect unused children of this shape.
+ shapeTypeDefinition.containsSpread = containsObjectTypeSpread;
+
return shapeTypeDefinition;
},
@@ -669,7 +669,7 @@ module.exports = function propTypesInstructions(context, components, utils) {
JSXSpreadAttribute: function(node) {
const component = components.get(utils.getParentComponent());
components.set(component ? component.node : node, {
- ignorePropsValidation: true
+ ignoreUnusedPropTypesValidation: true
});
},
diff --git a/tests/lib/rules/no-unused-prop-types.js b/tests/lib/rules/no-unused-prop-types.js
index 61bff0fee9..e29b4888eb 100644
--- a/tests/lib/rules/no-unused-prop-types.js
+++ b/tests/lib/rules/no-unused-prop-types.js
@@ -1801,34 +1801,6 @@ ruleTester.run('no-unused-prop-types', rule, {
' bar: PropTypes.bool',
'};'
].join('\n')
- }, {
- code: [
- 'type Person = {',
- ' ...data,',
- ' lastname: string',
- '};',
- 'class Hello extends React.Component {',
- ' props: Person;',
- ' render () {',
- ' return
Hello {this.props.firstname}
;',
- ' }',
- '}'
- ].join('\n'),
- parser: 'babel-eslint'
- }, {
- code: [
- 'type Person = {|',
- ' ...data,',
- ' lastname: string',
- '|};',
- 'class Hello extends React.Component {',
- ' props: Person;',
- ' render () {',
- ' return Hello {this.props.firstname}
;',
- ' }',
- '}'
- ].join('\n'),
- parser: 'babel-eslint'
}, {
// The next two test cases are related to: https://github.com/yannickcr/eslint-plugin-react/issues/1183
code: [
@@ -2470,6 +2442,20 @@ ruleTester.run('no-unused-prop-types', rule, {
'}'
].join('\n'),
parser: 'babel-eslint'
+ }, {
+ code: [
+ 'const foo = {};',
+ 'class Hello extends React.Component {',
+ ' render() {',
+ ' const {firstname, lastname} = this.props.name;',
+ ' return {firstname} {lastname}
;',
+ ' }',
+ '}',
+ 'Hello.propTypes = {',
+ ' name: PropTypes.shape(foo)',
+ '};'
+ ].join('\n'),
+ parser: 'babel-eslint'
}, {
// issue #933
code: [
@@ -2913,6 +2899,38 @@ ruleTester.run('no-unused-prop-types', rule, {
}
`,
parser: 'babel-eslint'
+ }, {
+ code: [
+ 'import type {BasePerson} from \'./types\'',
+ 'type Props = {',
+ ' person: {',
+ ' ...$Exact,',
+ ' lastname: string',
+ ' }',
+ '};',
+ 'class Hello extends React.Component {',
+ ' props: Props;',
+ ' render () {',
+ ' return Hello {this.props.person.firstname}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint'
+ }, {
+ code: [
+ 'import BasePerson from \'./types\'',
+ 'class Hello extends React.Component {',
+ ' render () {',
+ ' return Hello {this.props.person.firstname}
;',
+ ' }',
+ '}',
+ 'Hello.propTypes = {',
+ ' person: ProTypes.shape({',
+ ' ...BasePerson,',
+ ' lastname: PropTypes.string',
+ ' })',
+ '};'
+ ].join('\n')
}
],
@@ -4475,6 +4493,48 @@ ruleTester.run('no-unused-prop-types', rule, {
errors: [{
message: '\'lastname\' PropType is defined but prop is never used'
}]
+ }, {
+ code: `
+ type Person = string;
+ class Hello extends React.Component<{ person: Person }> {
+ render () {
+ return ;
+ }
+ }
+ `,
+ settings: {react: {flowVersion: '0.53'}},
+ errors: [{
+ message: '\'person\' PropType is defined but prop is never used'
+ }],
+ parser: 'babel-eslint'
+ }, {
+ code: `
+ type Person = string;
+ class Hello extends React.Component {
+ render () {
+ return ;
+ }
+ }
+ `,
+ settings: {react: {flowVersion: '0.52'}},
+ errors: [{
+ message: '\'person\' PropType is defined but prop is never used'
+ }],
+ parser: 'babel-eslint'
+ }, {
+ code: `
+ function higherOrderComponent() {
+ return class extends React.Component {
+ render() {
+ return
;
+ }
+ }
+ }
+ `,
+ errors: [{
+ message: '\'foo\' PropType is defined but prop is never used'
+ }],
+ parser: 'babel-eslint'
}, {
// issue #1506
code: [
@@ -4665,6 +4725,198 @@ ruleTester.run('no-unused-prop-types', rule, {
}, {
message: '\'a.b.c\' PropType is defined but prop is never used'
}]
+ }, {
+ code: `
+ type Props = { foo: string }
+ function higherOrderComponent() {
+ return class extends React.Component {
+ render() {
+ return ;
+ }
+ }
+ }
+ `,
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'foo\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'type Person = {',
+ ' ...data,',
+ ' lastname: string',
+ '};',
+ 'class Hello extends React.Component {',
+ ' props: Person;',
+ ' render () {',
+ ' return Hello {this.props.firstname}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'type Person = {|',
+ ' ...data,',
+ ' lastname: string',
+ '|};',
+ 'class Hello extends React.Component {',
+ ' props: Person;',
+ ' render () {',
+ ' return Hello {this.props.firstname}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'type Person = {',
+ ' ...$Exact,',
+ ' lastname: string',
+ '};',
+ 'class Hello extends React.Component {',
+ ' props: Person;',
+ ' render () {',
+ ' return Hello {this.props.firstname}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'import type {Data} from \'./Data\'',
+ 'type Person = {',
+ ' ...Data,',
+ ' lastname: string',
+ '};',
+ 'class Hello extends React.Component {',
+ ' props: Person;',
+ ' render () {',
+ ' return Hello {this.props.bar}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'import type {Data} from \'some-libdef-like-flow-typed-provides\'',
+ 'type Person = {',
+ ' ...Data,',
+ ' lastname: string',
+ '};',
+ 'class Hello extends React.Component {',
+ ' props: Person;',
+ ' render () {',
+ ' return Hello {this.props.bar}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'type Person = {',
+ ' ...data,',
+ ' lastname: string',
+ '};',
+ 'class Hello extends React.Component {',
+ ' props: Person;',
+ ' render () {',
+ ' return Hello {this.props.firstname}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'type Person = {|',
+ ' ...data,',
+ ' lastname: string',
+ '|};',
+ 'class Hello extends React.Component {',
+ ' props: Person;',
+ ' render () {',
+ ' return Hello {this.props.firstname}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'class Hello extends React.Component {',
+ ' render () {',
+ ' return Hello {this.props.firstname}
;',
+ ' }',
+ '}',
+ 'Hello.propTypes = {',
+ ' ...BasePerson,',
+ ' lastname: PropTypes.string',
+ '};'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ errors: [{
+ message: '\'lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'import type {BasePerson} from \'./types\'',
+ 'type Props = {',
+ ' person: {',
+ ' ...$Exact,',
+ ' lastname: string',
+ ' }',
+ '};',
+ 'class Hello extends React.Component {',
+ ' props: Props;',
+ ' render () {',
+ ' return Hello {this.props.person.firstname}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ parser: 'babel-eslint',
+ options: [{skipShapeProps: false}],
+ errors: [{
+ message: '\'person.lastname\' PropType is defined but prop is never used'
+ }]
+ }, {
+ code: [
+ 'import BasePerson from \'./types\'',
+ 'class Hello extends React.Component {',
+ ' render () {',
+ ' return Hello {this.props.person.firstname}
;',
+ ' }',
+ '}',
+ 'Hello.propTypes = {',
+ ' person: ProTypes.shape({',
+ ' ...BasePerson,',
+ ' lastname: PropTypes.string',
+ ' })',
+ '};'
+ ].join('\n'),
+ options: [{skipShapeProps: false}],
+ errors: [{
+ message: '\'person.lastname\' PropType is defined but prop is never used'
+ }]
}
/* , {
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index a879f05434..bdc492f262 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -342,6 +342,51 @@ ruleTester.run('prop-types', rule, {
' ])',
'};'
].join('\n')
+ }, {
+ code: `
+ class Component extends React.Component {
+ render() {
+ return {this.props.foo.baz}
;
+ }
+ }
+ Component.propTypes = {
+ foo: PropTypes.oneOfType([
+ PropTypes.shape({
+ bar: PropTypes.string
+ }),
+ PropTypes.shape({
+ baz: PropTypes.string
+ })
+ ])
+ };
+ `
+ }, {
+ code: `
+ class Component extends React.Component {
+ render() {
+ return {this.props.foo.baz}
;
+ }
+ }
+ Component.propTypes = {
+ foo: PropTypes.oneOfType([
+ PropTypes.shape({
+ bar: PropTypes.string
+ }),
+ PropTypes.instanceOf(Baz)
+ ])
+ };
+ `
+ }, {
+ code: `
+ class Component extends React.Component {
+ render() {
+ return {this.props.foo.baz}
;
+ }
+ }
+ Component.propTypes = {
+ foo: PropTypes.oneOf(['bar', 'baz'])
+ };
+ `
}, {
code: [
'class Hello extends React.Component {',
@@ -478,6 +523,20 @@ ruleTester.run('prop-types', rule, {
'};'
].join('\n'),
parser: 'babel-eslint'
+ }, {
+ code: [
+ 'const foo = {};',
+ 'class Hello extends React.Component {',
+ ' render() {',
+ ' const {firstname, lastname} = this.props.name;',
+ ' return {firstname} {lastname}
;',
+ ' }',
+ '}',
+ 'Hello.propTypes = {',
+ ' name: PropTypes.shape(foo)',
+ '};'
+ ].join('\n'),
+ parser: 'babel-eslint'
}, {
code: [
'class Hello extends React.Component {',
@@ -1209,6 +1268,20 @@ ruleTester.run('prop-types', rule, {
'} & FieldProps'
].join('\n'),
parser: 'babel-eslint'
+ }, {
+ // Impossible intersection type
+ code: `
+ import React from 'react';
+ type Props = string & {
+ fullname: string
+ };
+ class Test extends React.PureComponent {
+ render() {
+ return Hello {this.props.fullname}
+ }
+ }
+ `,
+ parser: 'babel-eslint'
}, {
code: [
'Card.propTypes = {',
@@ -3760,6 +3833,25 @@ ruleTester.run('prop-types', rule, {
message: '\'bad\' is missing in props validation'
}],
parser: 'babel-eslint'
+ },
+ {
+ code: `
+ class Component extends React.Component {
+ render() {
+ return {this.props.foo.baz}
;
+ }
+ }
+ Component.propTypes = {
+ foo: PropTypes.oneOfType([
+ PropTypes.shape({
+ bar: PropTypes.string
+ })
+ ])
+ };
+ `,
+ errors: [{
+ message: '\'foo.baz\' is missing in props validation'
+ }]
}
]
});