diff --git a/docs/rules/prop-types.md b/docs/rules/prop-types.md
index c705a30d2a..839c6094b4 100644
--- a/docs/rules/prop-types.md
+++ b/docs/rules/prop-types.md
@@ -109,6 +109,7 @@ This rule can take one argument to ignore some specific props during validation.
* `enabled`: for enabling the rule. 0=off, 1=warn, 2=error. Defaults to 0.
* `ignore`: optional array of props name to ignore during validation.
* `customValidators`: optional array of validators used for propTypes validation.
+* `skipUndeclared`: only error on components that have a propTypes block declared
### As for "exceptions"
diff --git a/lib/rules/prop-types.js b/lib/rules/prop-types.js
index 612ba943e5..d452df66cc 100644
--- a/lib/rules/prop-types.js
+++ b/lib/rules/prop-types.js
@@ -42,6 +42,9 @@ module.exports = {
items: {
type: 'string'
}
+ },
+ skipUndeclared: {
+ type: 'boolean'
}
},
additionalProperties: false
@@ -54,6 +57,7 @@ module.exports = {
var configuration = context.options[0] || {};
var ignored = configuration.ignore || [];
var customValidators = configuration.customValidators || [];
+ var skipUndeclared = configuration.skipUndeclared || false;
// Used to track the type annotations in scope.
// Necessary because babel's scopes do not track type annotations.
var stack = null;
@@ -134,7 +138,6 @@ module.exports = {
* @returns {Boolean} True if we are declaring a prop, false if not.
*/
function isPropTypesDeclaration(node) {
-
// Special case for class properties
// (babel-eslint does not expose property name so we have to rely on tokens)
if (node && node.type === 'ClassProperty') {
@@ -179,10 +182,12 @@ module.exports = {
* @returns {Boolean} True if the component must be validated, false if not.
*/
function mustBeValidated(component) {
+ var isSkippedByConfig = skipUndeclared && typeof component.declaredPropTypes === 'undefined';
return Boolean(
component &&
component.usedPropTypes &&
- !component.ignorePropsValidation
+ !component.ignorePropsValidation &&
+ !isSkippedByConfig
);
}
diff --git a/tests/lib/rules/prop-types.js b/tests/lib/rules/prop-types.js
index 511e845554..3a60db4a6b 100644
--- a/tests/lib/rules/prop-types.js
+++ b/tests/lib/rules/prop-types.js
@@ -1305,6 +1305,62 @@ ruleTester.run('prop-types', rule, {
'}'
].join('\n'),
parser: 'babel-eslint'
+ }, {
+ code: [
+ 'var Hello = React.createClass({',
+ ' render: function() {',
+ ' return
{this.props.name}
;',
+ ' }',
+ '});'
+ ].join('\n'),
+ options: [{skipUndeclared: true}],
+ parserOptions: parserOptions
+ }, {
+ code: [
+ 'var Hello = React.createClass({',
+ ' render: function() {',
+ ' return {this.props.name}
;',
+ ' }',
+ '});'
+ ].join('\n'),
+ options: [{skipUndeclared: true}],
+ parserOptions: parserOptions
+ }, {
+ code: [
+ 'class Hello extends React.Component {',
+ ' render() {',
+ ' return {this.props.name}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ options: [{skipUndeclared: true}],
+ parserOptions: parserOptions
+ }, {
+ code: [
+ 'var Hello = React.createClass({',
+ ' propTypes: {',
+ ' name: React.PropTypes.object.isRequired',
+ ' },',
+ ' render: function() {',
+ ' return {this.props.name}
;',
+ ' }',
+ '});'
+ ].join('\n'),
+ options: [{skipUndeclared: true}],
+ parserOptions: parserOptions
+ }, {
+ code: [
+ 'var Hello = React.createClass({',
+ ' propTypes: {',
+ ' name: React.PropTypes.object.isRequired',
+ ' },',
+ ' render: function() {',
+ ' return {this.props.name}
;',
+ ' }',
+ '});'
+ ].join('\n'),
+ options: [{skipUndeclared: false}],
+ parserOptions: parserOptions
}
],
@@ -2312,6 +2368,85 @@ ruleTester.run('prop-types', rule, {
column: 27,
type: 'Property'
}]
+ }, {
+ code: [
+ 'var Hello = React.createClass({',
+ ' propTypes: {},',
+ ' render: function() {',
+ ' return {this.props.firstname}
;',
+ ' }',
+ '});'
+ ].join('\n'),
+ options: [{skipUndeclared: true}],
+ parserOptions: parserOptions,
+ errors: [{
+ message: '\'firstname\' is missing in props validation',
+ line: 4,
+ column: 29
+ }]
+ }, {
+ code: [
+ 'var Hello = function(props) {',
+ ' return {props.firstname}
;',
+ '};',
+ 'Hello.propTypes = {}'
+ ].join('\n'),
+ options: [{skipUndeclared: true}],
+ parserOptions: parserOptions,
+ errors: [{
+ message: '\'firstname\' is missing in props validation',
+ line: 2,
+ column: 22
+ }]
+ }, {
+ code: [
+ 'class Hello extends React.Component {',
+ ' static get propTypes() {',
+ ' return {};',
+ ' }',
+ ' render() {',
+ ' return {this.props.firstname}
;',
+ ' }',
+ '}'
+ ].join('\n'),
+ options: [{skipUndeclared: true}],
+ parserOptions: parserOptions,
+ errors: [{
+ message: '\'firstname\' is missing in props validation',
+ line: 6,
+ column: 29
+ }]
+ }, {
+ code: [
+ 'class Hello extends React.Component {',
+ ' render() {',
+ ' return {this.props.firstname}
;',
+ ' }',
+ '}',
+ 'Hello.propTypes = {};'
+ ].join('\n'),
+ options: [{skipUndeclared: true}],
+ parserOptions: parserOptions,
+ errors: [{
+ message: '\'firstname\' is missing in props validation',
+ line: 3,
+ column: 29
+ }]
+ }, {
+ code: [
+ 'var Hello = React.createClass({',
+ ' render: function() {',
+ ' return {this.props.firstname}
;',
+ ' }',
+ '});'
+ ].join('\n'),
+ options: [{skipUndeclared: false}],
+ parserOptions: parserOptions,
+ errors: [{
+ message: '\'firstname\' is missing in props validation',
+ line: 3,
+ column: 29
+ }]
}
]
});