diff --git a/docs/rules/forbid-prop-types.md b/docs/rules/forbid-prop-types.md index 951285ce3b..3784eff356 100644 --- a/docs/rules/forbid-prop-types.md +++ b/docs/rules/forbid-prop-types.md @@ -44,7 +44,7 @@ class Component extends React.Component { ```js ... -"react/forbid-prop-types": [, { "forbid": [] }] +"react/forbid-prop-types": [, { "forbid": [], checkContextTypes: , checkChildContextTypes: }] ... ``` @@ -52,6 +52,14 @@ class Component extends React.Component { An array of strings, with the names of `PropTypes` keys that are forbidden. The default value for this option is `['any', 'array', 'object']`. +### `checkContextTypes` + +Whether or not to check `contextTypes` for forbidden prop types. The default value is false. + +### `checkChildContextTypes` + +Whether or not to check `childContextTypes` for forbidden prop types. The default value is false. + ## When not to use This rule is a formatting/documenting preference and not following it won't negatively affect the quality of your code. This rule encourages prop types that more specifically document their usage. diff --git a/lib/rules/forbid-prop-types.js b/lib/rules/forbid-prop-types.js index 6d03b21566..c2ec47c737 100644 --- a/lib/rules/forbid-prop-types.js +++ b/lib/rules/forbid-prop-types.js @@ -5,6 +5,7 @@ const variableUtil = require('../util/variable'); const propsUtil = require('../util/props'); +const astUtil = require('../util/ast'); // ------------------------------------------------------------------------------ // Constants @@ -32,6 +33,12 @@ module.exports = { items: { type: 'string' } + }, + checkContextTypes: { + type: 'boolean' + }, + checkChildContextTypes: { + type: 'boolean' } }, additionalProperties: true @@ -40,14 +47,29 @@ module.exports = { create: function(context) { const propWrapperFunctions = new Set(context.settings.propWrapperFunctions || []); + const configuration = context.options[0] || {}; + const checkContextTypes = configuration.checkContextTypes || false; + const checkChildContextTypes = configuration.checkChildContextTypes || false; function isForbidden(type) { - const configuration = context.options[0] || {}; - const forbid = configuration.forbid || DEFAULTS; return forbid.indexOf(type) >= 0; } + function shouldCheckContextTypes(node) { + if (checkContextTypes && propsUtil.isContextTypesDeclaration(node)) { + return true; + } + return false; + } + + function shouldCheckChildContextTypes(node) { + if (checkChildContextTypes && propsUtil.isChildContextTypesDeclaration(node)) { + return true; + } + return false; + } + /** * Find a variable by name in the current scope. * @param {string} name Name of the variable to look for. @@ -132,27 +154,55 @@ module.exports = { return { ClassProperty: function(node) { - if (!propsUtil.isPropTypesDeclaration(node)) { + if ( + !propsUtil.isPropTypesDeclaration(node) && + !shouldCheckContextTypes(node) && + !shouldCheckChildContextTypes(node) + ) { return; } checkNode(node.value); }, MemberExpression: function(node) { - if (!propsUtil.isPropTypesDeclaration(node)) { + if ( + !propsUtil.isPropTypesDeclaration(node) && + !shouldCheckContextTypes(node) && + !shouldCheckChildContextTypes(node) + ) { return; } checkNode(node.parent.right); }, + MethodDefinition: function(node) { + if ( + !propsUtil.isPropTypesDeclaration(node) && + !shouldCheckContextTypes(node) && + !shouldCheckChildContextTypes(node) + ) { + return; + } + + const returnStatement = astUtil.findReturnStatement(node); + + if (returnStatement && returnStatement.argument) { + checkNode(returnStatement.argument); + } + }, + ObjectExpression: function(node) { node.properties.forEach(property => { if (!property.key) { return; } - if (!propsUtil.isPropTypesDeclaration(property)) { + if ( + !propsUtil.isPropTypesDeclaration(property) && + !shouldCheckContextTypes(property) && + !shouldCheckChildContextTypes(property) + ) { return; } if (property.value.type === 'ObjectExpression') { diff --git a/lib/util/Components.js b/lib/util/Components.js index b25bcce3f6..5e78e664fb 100644 --- a/lib/util/Components.js +++ b/lib/util/Components.js @@ -9,6 +9,7 @@ const util = require('util'); const doctrine = require('doctrine'); const variableUtil = require('./variable'); const pragmaUtil = require('./pragma'); +const astUtil = require('./ast'); const usedPropTypesAreEquivalent = (propA, propB) => { if (propA.name === propB.name) { @@ -356,24 +357,7 @@ function componentRule(rule, context) { * * @param {ASTNode} ASTnode The AST node being checked */ - findReturnStatement: function(node) { - if ( - (!node.value || !node.value.body || !node.value.body.body) && - (!node.body || !node.body.body) - ) { - return false; - } - - const bodyNodes = (node.value ? node.value.body.body : node.body.body); - - let i = bodyNodes.length - 1; - for (; i >= 0; i--) { - if (bodyNodes[i].type === 'ReturnStatement') { - return bodyNodes[i]; - } - } - return false; - }, + findReturnStatement: astUtil.findReturnStatement, /** * Get the parent component node from the current scope diff --git a/lib/util/ast.js b/lib/util/ast.js index 2ab5cf4ba2..dc9ce7704f 100644 --- a/lib/util/ast.js +++ b/lib/util/ast.js @@ -35,7 +35,32 @@ function getComponentProperties(node) { } } +/** + * Find a return statment in the current node + * + * @param {ASTNode} ASTnode The AST node being checked + */ +function findReturnStatement(node) { + if ( + (!node.value || !node.value.body || !node.value.body.body) && + (!node.body || !node.body.body) + ) { + return false; + } + + const bodyNodes = (node.value ? node.value.body.body : node.body.body); + + let i = bodyNodes.length - 1; + for (; i >= 0; i--) { + if (bodyNodes[i].type === 'ReturnStatement') { + return bodyNodes[i]; + } + } + return false; +} + module.exports = { getPropertyName: getPropertyName, - getComponentProperties: getComponentProperties + getComponentProperties: getComponentProperties, + findReturnStatement: findReturnStatement }; diff --git a/lib/util/props.js b/lib/util/props.js index f2433978bf..f90648ccf2 100644 --- a/lib/util/props.js +++ b/lib/util/props.js @@ -20,6 +20,30 @@ function isPropTypesDeclaration(node) { return astUtil.getPropertyName(node) === 'propTypes'; } +/** + * Checks if the node passed in looks like a contextTypes declaration. + * @param {ASTNode} node The node to check. + * @returns {Boolean} `true` if the node is a contextTypes declaration, `false` if not + */ +function isContextTypesDeclaration(node) { + if (node && node.type === 'ClassProperty') { + // Flow support + if (node.typeAnnotation && node.key.name === 'context') { + return true; + } + } + return astUtil.getPropertyName(node) === 'contextTypes'; +} + +/** + * Checks if the node passed in looks like a childContextTypes declaration. + * @param {ASTNode} node The node to check. + * @returns {Boolean} `true` if the node is a childContextTypes declaration, `false` if not + */ +function isChildContextTypesDeclaration(node) { + return astUtil.getPropertyName(node) === 'childContextTypes'; +} + /** * Checks if the Identifier node passed in looks like a defaultProps declaration. * @param {ASTNode} node The node to check. Must be an Identifier node. @@ -41,6 +65,8 @@ function isRequiredPropType(propTypeExpression) { module.exports = { isPropTypesDeclaration: isPropTypesDeclaration, + isContextTypesDeclaration: isContextTypesDeclaration, + isChildContextTypesDeclaration: isChildContextTypesDeclaration, isDefaultPropsDeclaration: isDefaultPropsDeclaration, isRequiredPropType: isRequiredPropType }; diff --git a/tests/lib/rules/forbid-prop-types.js b/tests/lib/rules/forbid-prop-types.js index e6c7eb3b8a..f79703d3de 100644 --- a/tests/lib/rules/forbid-prop-types.js +++ b/tests/lib/rules/forbid-prop-types.js @@ -19,8 +19,6 @@ const parserOptions = { } }; -require('babel-eslint'); - // ----------------------------------------------------------------------------- // Tests // ----------------------------------------------------------------------------- @@ -190,169 +188,236 @@ ruleTester.run('forbid-prop-types', rule, { '}' ].join('\n'), parser: 'babel-eslint' - }], - - invalid: [{ + }, { + // Proptypes declared with a spread property + code: [ + 'class Test extends react.component {', + ' static get propTypes() {', + ' return {', + ' intl: React.propTypes.number,', + ' ...propTypes', + ' };', + ' };', + '}' + ].join('\n') + }, { code: [ 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.any', - ' },', + ' childContextTypes: externalPropTypes,', ' render: function() {', ' return
;', ' }', '});' ].join('\n'), - errors: [{ - message: ANY_ERROR_MESSAGE, - line: 3, - column: 5, - type: 'Property' - }] + options: [{checkContextTypes: true}] }, { code: [ 'var First = createReactClass({', - ' propTypes: {', - ' n: PropTypes.number', + ' childContextTypes: {', + ' s: PropTypes.string,', + ' n: PropTypes.number,', + ' i: PropTypes.instanceOf,', + ' b: PropTypes.bool', ' },', ' render: function() {', ' return
;', ' }', '});' ].join('\n'), - errors: [{ - message: NUMBER_ERROR_MESSAGE, - line: 3, - column: 5, - type: 'Property' - }], - options: [{ - forbid: ['number'] - }] + options: [{checkContextTypes: true}] }, { code: [ 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.any.isRequired', + ' childContextTypes: {', + ' a: PropTypes.array', ' },', ' render: function() {', ' return
;', ' }', '});' ].join('\n'), - errors: [{ - message: ANY_ERROR_MESSAGE, - line: 3, - column: 5, - type: 'Property' + options: [{ + forbid: ['any', 'object'], + checkContextTypes: true }] }, { code: [ 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.array', + ' childContextTypes: {', + ' o: PropTypes.object', ' },', ' render: function() {', ' return
;', ' }', '});' ].join('\n'), - errors: [{ - message: ARRAY_ERROR_MESSAGE, - line: 3, - column: 5, - type: 'Property' + options: [{ + forbid: ['any', 'array'], + checkContextTypes: true }] }, { code: [ 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.array.isRequired', + ' childContextTypes: {', + ' o: PropTypes.object,', ' },', ' render: function() {', ' return
;', ' }', '});' ].join('\n'), - errors: [{ - message: ARRAY_ERROR_MESSAGE, - line: 3, - column: 5, - type: 'Property' + options: [{ + forbid: ['any', 'array'], + checkContextTypes: true }] }, { code: [ - 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.object', - ' },', + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.childContextTypes = {', + ' a: PropTypes.string,', + ' b: PropTypes.string', + '};', + 'First.childContextTypes.justforcheck = PropTypes.string;' + ].join('\n'), + options: [{checkContextTypes: true}] + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.childContextTypes = {', + ' elem: PropTypes.instanceOf(HTMLElement)', + '};' + ].join('\n'), + options: [{checkContextTypes: true}] + }, { + code: [ + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello
;', + ' }', + '}', + 'Hello.childContextTypes = {', + ' "aria-controls": PropTypes.string', + '};' + ].join('\n'), + parser: 'babel-eslint', + options: [{checkContextTypes: true}] + }, { + // Invalid code, should not be validated + code: [ + 'class Component extends React.Component {', + ' childContextTypes: {', + ' a: PropTypes.any,', + ' c: PropTypes.any,', + ' b: PropTypes.any', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [{checkContextTypes: true}] + }, { + code: [ + 'var Hello = createReactClass({', ' render: function() {', + ' let { a, ...b } = obj;', + ' let c = { ...d };', ' return
;', ' }', '});' ].join('\n'), - errors: [{ - message: OBJECT_ERROR_MESSAGE, - line: 3, - column: 5, - type: 'Property' - }] + options: [{checkContextTypes: true}] }, { code: [ - 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.object.isRequired', + 'var Hello = createReactClass({', + ' childContextTypes: {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', ' },', ' render: function() {', ' return
;', ' }', '});' ].join('\n'), - errors: [{ - message: OBJECT_ERROR_MESSAGE, - line: 3, - column: 5, - type: 'Property' - }] + options: [{checkContextTypes: true}] + }, { + // Proptypes declared with a spread property + code: [ + 'class Test extends react.component {', + ' static childContextTypes = {', + ' intl: React.childContextTypes.number,', + ' ...childContextTypes', + ' };', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [{checkContextTypes: true}] + }, { + // Proptypes declared with a spread property + code: [ + 'class Test extends react.component {', + ' static get childContextTypes() {', + ' return {', + ' intl: React.childContextTypes.number,', + ' ...childContextTypes', + ' };', + ' };', + '}' + ].join('\n'), + options: [{checkContextTypes: true}] }, { code: [ 'var First = createReactClass({', - ' propTypes: {', - ' a: PropTypes.array,', - ' o: PropTypes.object', - ' },', + ' childContextTypes: externalPropTypes,', ' render: function() {', ' return
;', ' }', '});' ].join('\n'), - errors: 2 + options: [{checkChildContextTypes: true}] }, { code: [ 'var First = createReactClass({', - ' propTypes: {', - ' s: PropTypes.shape({,', - ' o: PropTypes.object', - ' })', + ' childContextTypes: {', + ' s: PropTypes.string,', + ' n: PropTypes.number,', + ' i: PropTypes.instanceOf,', + ' b: PropTypes.bool', ' },', ' render: function() {', ' return
;', ' }', '});' ].join('\n'), - errors: 1 + options: [{checkChildContextTypes: true}] }, { code: [ 'var First = createReactClass({', - ' propTypes: {', + ' childContextTypes: {', ' a: PropTypes.array', ' },', ' render: function() {', ' return
;', ' }', - '});', - 'var Second = createReactClass({', - ' propTypes: {', + '});' + ].join('\n'), + options: [{ + forbid: ['any', 'object'], + checkChildContextTypes: true + }] + }, { + code: [ + 'var First = createReactClass({', + ' childContextTypes: {', ' o: PropTypes.object', ' },', ' render: function() {', @@ -360,29 +425,39 @@ ruleTester.run('forbid-prop-types', rule, { ' }', '});' ].join('\n'), - errors: 2 + options: [{ + forbid: ['any', 'array'], + checkChildContextTypes: true + }] }, { code: [ - 'class First extends React.Component {', - ' render() {', + 'var First = createReactClass({', + ' childContextTypes: {', + ' o: PropTypes.object,', + ' },', + ' render: function() {', ' return
;', ' }', - '}', - 'First.propTypes = {', - ' a: PropTypes.array,', - ' o: PropTypes.object', - '};', - 'class Second extends React.Component {', + '});' + ].join('\n'), + options: [{ + forbid: ['any', 'array'], + checkChildContextTypes: true + }] + }, { + code: [ + 'class First extends React.Component {', ' render() {', ' return
;', ' }', '}', - 'Second.propTypes = {', - ' a: PropTypes.array,', - ' o: PropTypes.object', - '};' + 'First.childContextTypes = {', + ' a: PropTypes.string,', + ' b: PropTypes.string', + '};', + 'First.childContextTypes.justforcheck = PropTypes.string;' ].join('\n'), - errors: 4 + options: [{checkChildContextTypes: true}] }, { code: [ 'class First extends React.Component {', @@ -390,39 +465,32 @@ ruleTester.run('forbid-prop-types', rule, { ' return
;', ' }', '}', - 'First.propTypes = forbidExtraProps({', - ' a: PropTypes.array', - '});' - ].join('\n'), - errors: 1, - settings: { - propWrapperFunctions: ['forbidExtraProps'] - } - }, { - code: [ - 'import { forbidExtraProps } from "airbnb-prop-types";', - 'export const propTypes = {dpm: PropTypes.any};', - 'export default function Component() {}', - 'Component.propTypes = propTypes;' + 'First.childContextTypes = {', + ' elem: PropTypes.instanceOf(HTMLElement)', + '};' ].join('\n'), - errors: [{message: ANY_ERROR_MESSAGE}] + options: [{checkChildContextTypes: true}] }, { code: [ - 'import { forbidExtraProps } from "airbnb-prop-types";', - 'export const propTypes = {a: PropTypes.any};', - 'export default function Component() {}', - 'Component.propTypes = forbidExtraProps(propTypes);' + 'class Hello extends React.Component {', + ' render() {', + ' return
Hello
;', + ' }', + '}', + 'Hello.childContextTypes = {', + ' "aria-controls": PropTypes.string', + '};' ].join('\n'), - errors: [{message: ANY_ERROR_MESSAGE}], - settings: { - propWrapperFunctions: ['forbidExtraProps'] - } + parser: 'babel-eslint', + options: [{checkChildContextTypes: true}] }, { + // Invalid code, should not be validated code: [ 'class Component extends React.Component {', - ' static propTypes = {', - ' a: PropTypes.array,', - ' o: PropTypes.object', + ' childContextTypes: {', + ' a: PropTypes.any,', + ' c: PropTypes.any,', + ' b: PropTypes.any', ' };', ' render() {', ' return
;', @@ -430,28 +498,574 @@ ruleTester.run('forbid-prop-types', rule, { '}' ].join('\n'), parser: 'babel-eslint', - errors: 2 + options: [{checkChildContextTypes: true}] }, { code: [ - 'class Component extends React.Component {', - ' static propTypes = forbidExtraProps({', - ' a: PropTypes.array,', - ' o: PropTypes.object', - ' });', - ' render() {', + 'var Hello = createReactClass({', + ' render: function() {', + ' let { a, ...b } = obj;', + ' let c = { ...d };', ' return
;', ' }', - '}' + '});' + ].join('\n'), + options: [{checkChildContextTypes: true}] + }, { + code: [ + 'var Hello = createReactClass({', + ' childContextTypes: {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{checkChildContextTypes: true}] + }, { + // Proptypes declared with a spread property + code: [ + 'class Test extends react.component {', + ' static childContextTypes = {', + ' intl: React.childContextTypes.number,', + ' ...childContextTypes', + ' };', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [{checkChildContextTypes: true}] + }, { + // Proptypes declared with a spread property + code: [ + 'class Test extends react.component {', + ' static get childContextTypes() {', + ' return {', + ' intl: React.childContextTypes.number,', + ' ...childContextTypes', + ' };', + ' };', + '}' + ].join('\n'), + options: [{checkChildContextTypes: true}] + }], + + invalid: [{ + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.any', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' n: PropTypes.number', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: [{ + message: NUMBER_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }], + options: [{ + forbid: ['number'] + }] + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.any.isRequired', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.array', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: [{ + message: ARRAY_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.array.isRequired', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: [{ + message: ARRAY_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.object', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: [{ + message: OBJECT_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.object.isRequired', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: [{ + message: OBJECT_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: 2 + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' s: PropTypes.shape({,', + ' o: PropTypes.object', + ' })', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: 1 + }, { + code: [ + 'var First = createReactClass({', + ' propTypes: {', + ' a: PropTypes.array', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});', + 'var Second = createReactClass({', + ' propTypes: {', + ' o: PropTypes.object', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + errors: 2 + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = {', + ' a: PropTypes.array,', + ' o: PropTypes.object', + '};', + 'class Second extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'Second.propTypes = {', + ' a: PropTypes.array,', + ' o: PropTypes.object', + '};' + ].join('\n'), + errors: 4 + }, { + code: [ + 'class First extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'First.propTypes = forbidExtraProps({', + ' a: PropTypes.array', + '});' + ].join('\n'), + errors: 1, + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'import { forbidExtraProps } from "airbnb-prop-types";', + 'export const propTypes = {dpm: PropTypes.any};', + 'export default function Component() {}', + 'Component.propTypes = propTypes;' + ].join('\n'), + errors: [{message: ANY_ERROR_MESSAGE}] + }, { + code: [ + 'import { forbidExtraProps } from "airbnb-prop-types";', + 'export const propTypes = {a: PropTypes.any};', + 'export default function Component() {}', + 'Component.propTypes = forbidExtraProps(propTypes);' + ].join('\n'), + errors: [{message: ANY_ERROR_MESSAGE}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = {', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: 2 + }, { + code: [ + 'class Component extends React.Component {', + ' static get propTypes() {', + ' return {', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' };', + ' };', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + errors: 2 + }, { + code: [ + 'class Component extends React.Component {', + ' static propTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' });', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: 2, + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'class Component extends React.Component {', + ' static get propTypes() {', + ' return forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' });', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + errors: 2, + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'var Hello = createReactClass({', + ' propTypes: {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + forbid: ['instanceOf'] + }], + errors: 1 + }, { + code: [ + 'var object = PropTypes.object;', + 'var Hello = createReactClass({', + ' propTypes: {', + ' retailer: object,', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{ + forbid: ['object'] + }], + errors: 1 + }, { + code: [ + 'var First = createReactClass({', + ' contextTypes: {', + ' a: PropTypes.any', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{checkContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class Foo extends Component {', + ' static contextTypes = {', + ' a: PropTypes.any', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + options: [{checkContextTypes: true}], + parser: 'babel-eslint', + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class Foo extends Component {', + ' static get contextTypes() {', + ' return {', + ' a: PropTypes.any', + ' };', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + options: [{checkContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 4, + column: 7, + type: 'Property' + }] + }, { + code: [ + 'class Foo extends Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'Foo.contextTypes = {', + ' a: PropTypes.any', + '};' + ].join('\n'), + options: [{checkContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 7, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'function Foo(props) {', + ' return
;', + '}', + 'Foo.contextTypes = {', + ' a: PropTypes.any', + '};' + ].join('\n'), + options: [{checkContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 5, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'const Foo = (props) => {', + ' return
;', + '};', + 'Foo.contextTypes = {', + ' a: PropTypes.any', + '};' + ].join('\n'), + options: [{checkContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 5, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'class Component extends React.Component {', + ' static contextTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' });', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: 2, + options: [{checkContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'class Component extends React.Component {', + ' static get contextTypes() {', + ' return forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' });', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + errors: 2, + options: [{checkContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'class Component extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'Component.contextTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + '});' + ].join('\n'), + errors: 2, + options: [{checkContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'function Component(props) {', + ' return
;', + '}', + 'Component.contextTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + '});' + ].join('\n'), + errors: 2, + options: [{checkContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'const Component = (props) => {', + ' return
;', + '};', + 'Component.contextTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + '});' ].join('\n'), - parser: 'babel-eslint', errors: 2, + options: [{checkContextTypes: true}], settings: { propWrapperFunctions: ['forbidExtraProps'] } }, { code: [ 'var Hello = createReactClass({', - ' propTypes: {', + ' contextTypes: {', ' retailer: PropTypes.instanceOf(Map).isRequired,', ' requestRetailer: PropTypes.func.isRequired', ' },', @@ -461,15 +1075,291 @@ ruleTester.run('forbid-prop-types', rule, { '});' ].join('\n'), options: [{ - forbid: ['instanceOf'] + forbid: ['instanceOf'], + checkContextTypes: true }], errors: 1 }, { code: [ - 'var object = PropTypes.object;', + 'class Component extends React.Component {', + ' static contextTypes = {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [{ + forbid: ['instanceOf'], + checkContextTypes: true + }], + errors: 1 + }, { + code: [ + 'class Component extends React.Component {', + ' static get contextTypes() {', + ' return {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + ' };', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + options: [{ + forbid: ['instanceOf'], + checkContextTypes: true + }], + errors: 1 + }, { + code: [ + 'class Component extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'Component.contextTypes = {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + '}' + ].join('\n'), + options: [{ + forbid: ['instanceOf'], + checkContextTypes: true + }], + errors: 1 + }, { + code: [ + 'function Component(props) {', + ' return
;', + '}', + 'Component.contextTypes = {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + '}' + ].join('\n'), + options: [{ + forbid: ['instanceOf'], + checkContextTypes: true + }], + errors: 1 + }, { + code: [ + 'const Component = (props) => {', + ' return
;', + '};', + 'Component.contextTypes = {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + '}' + ].join('\n'), + options: [{ + forbid: ['instanceOf'], + checkContextTypes: true + }], + errors: 1 + }, { + code: [ + 'var First = createReactClass({', + ' childContextTypes: {', + ' a: PropTypes.any', + ' },', + ' render: function() {', + ' return
;', + ' }', + '});' + ].join('\n'), + options: [{checkChildContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class Foo extends Component {', + ' static childContextTypes = {', + ' a: PropTypes.any', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + options: [{checkChildContextTypes: true}], + parser: 'babel-eslint', + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 3, + column: 5, + type: 'Property' + }] + }, { + code: [ + 'class Foo extends Component {', + ' static get childContextTypes() {', + ' return {', + ' a: PropTypes.any', + ' };', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + options: [{checkChildContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 4, + column: 7, + type: 'Property' + }] + }, { + code: [ + 'class Foo extends Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'Foo.childContextTypes = {', + ' a: PropTypes.any', + '};' + ].join('\n'), + options: [{checkChildContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 7, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'function Foo(props) {', + ' return
;', + '}', + 'Foo.childContextTypes = {', + ' a: PropTypes.any', + '};' + ].join('\n'), + options: [{checkChildContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 5, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'const Foo = (props) => {', + ' return
;', + '};', + 'Foo.childContextTypes = {', + ' a: PropTypes.any', + '};' + ].join('\n'), + options: [{checkChildContextTypes: true}], + errors: [{ + message: ANY_ERROR_MESSAGE, + line: 5, + column: 3, + type: 'Property' + }] + }, { + code: [ + 'class Component extends React.Component {', + ' static childContextTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' });', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + errors: 2, + options: [{checkChildContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'class Component extends React.Component {', + ' static get childContextTypes() {', + ' return forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + ' });', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + errors: 2, + options: [{checkChildContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'class Component extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'Component.childContextTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + '});' + ].join('\n'), + errors: 2, + options: [{checkChildContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'function Component(props) {', + ' return
;', + '}', + 'Component.childContextTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + '});' + ].join('\n'), + errors: 2, + options: [{checkChildContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ + 'const Component = (props) => {', + ' return
;', + '};', + 'Component.childContextTypes = forbidExtraProps({', + ' a: PropTypes.array,', + ' o: PropTypes.object', + '});' + ].join('\n'), + errors: 2, + options: [{checkChildContextTypes: true}], + settings: { + propWrapperFunctions: ['forbidExtraProps'] + } + }, { + code: [ 'var Hello = createReactClass({', - ' propTypes: {', - ' retailer: object,', + ' childContextTypes: {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', ' },', ' render: function() {', ' return
;', @@ -477,7 +1367,73 @@ ruleTester.run('forbid-prop-types', rule, { '});' ].join('\n'), options: [{ - forbid: ['object'] + forbid: ['instanceOf'], + checkChildContextTypes: true + }], + errors: 1 + }, { + code: [ + 'class Component extends React.Component {', + ' static childContextTypes = {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + ' }', + ' render() {', + ' return
;', + ' }', + '}' + ].join('\n'), + parser: 'babel-eslint', + options: [{ + forbid: ['instanceOf'], + checkChildContextTypes: true + }], + errors: 1 + }, { + code: [ + 'class Component extends React.Component {', + ' render() {', + ' return
;', + ' }', + '}', + 'Component.childContextTypes = {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + '}' + ].join('\n'), + options: [{ + forbid: ['instanceOf'], + checkChildContextTypes: true + }], + errors: 1 + }, { + code: [ + 'function Component(props) {', + ' return
;', + '}', + 'Component.childContextTypes = {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + '}' + ].join('\n'), + options: [{ + forbid: ['instanceOf'], + checkChildContextTypes: true + }], + errors: 1 + }, { + code: [ + 'const Component = (props) => {', + ' return
;', + '};', + 'Component.childContextTypes = {', + ' retailer: PropTypes.instanceOf(Map).isRequired,', + ' requestRetailer: PropTypes.func.isRequired', + '}' + ].join('\n'), + options: [{ + forbid: ['instanceOf'], + checkChildContextTypes: true }], errors: 1 }]