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 + }] } ] });