diff --git a/src/addons/link/__tests__/ReactLinkPropTypes-test.js b/src/addons/link/__tests__/ReactLinkPropTypes-test.js index 6ade1377703..7171c7f5532 100644 --- a/src/addons/link/__tests__/ReactLinkPropTypes-test.js +++ b/src/addons/link/__tests__/ReactLinkPropTypes-test.js @@ -15,6 +15,7 @@ var emptyFunction = require('emptyFunction'); var LinkPropTypes = require('ReactLink').PropTypes; var React = require('React'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); +var ReactPropTypesSecret = require('ReactPropTypesSecret'); var invalidMessage = 'Invalid prop `testProp` supplied to `testComponent`.'; var requiredMessage = @@ -26,7 +27,9 @@ function typeCheckFail(declaration, value, message) { props, 'testProp', 'testComponent', - ReactPropTypeLocations.prop + ReactPropTypeLocations.prop, + null, + ReactPropTypesSecret ); expect(error instanceof Error).toBe(true); expect(error.message).toBe(message); @@ -38,7 +41,9 @@ function typeCheckPass(declaration, value) { props, 'testProp', 'testComponent', - ReactPropTypeLocations.prop + ReactPropTypeLocations.prop, + null, + ReactPropTypesSecret ); expect(error).toBe(null); } diff --git a/src/isomorphic/classic/types/ReactPropTypes.js b/src/isomorphic/classic/types/ReactPropTypes.js index 9b419cc7695..ec060fbde79 100644 --- a/src/isomorphic/classic/types/ReactPropTypes.js +++ b/src/isomorphic/classic/types/ReactPropTypes.js @@ -13,6 +13,7 @@ var ReactElement = require('ReactElement'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); +var ReactPropTypesSecret = require('ReactPropTypesSecret'); var emptyFunction = require('emptyFunction'); var getIteratorFn = require('getIteratorFn'); @@ -105,16 +106,41 @@ function is(x, y) { /*eslint-enable no-self-compare*/ function createChainableTypeChecker(validate) { + if (__DEV__) { + var manualPropTypeCallCache = {}; + } function checkType( isRequired, props, propName, componentName, location, - propFullName + propFullName, + secret ) { componentName = componentName || ANONYMOUS; propFullName = propFullName || propName; + if (__DEV__) { + if ( + secret !== ReactPropTypesSecret && + typeof console !== 'undefined' + ) { + var cacheKey = `${componentName}:${propName}`; + if (!manualPropTypeCallCache[cacheKey]) { + warning( + false, + 'You are manually calling a React.PropTypes validation ' + + 'function for the `%s` prop on `%s`. This is deprecated ' + + 'and will not work in the next major version. You may be ' + + 'seeing this warning due to a third-party PropTypes library. ' + + 'See https://fb.me/react-warning-dont-call-proptypes for details.', + propFullName, + componentName + ); + manualPropTypeCallCache[cacheKey] = true; + } + } + } if (props[propName] == null) { var locationName = ReactPropTypeLocationNames[location]; if (isRequired) { @@ -125,7 +151,13 @@ function createChainableTypeChecker(validate) { } return null; } else { - return validate(props, propName, componentName, location, propFullName); + return validate( + props, + propName, + componentName, + location, + propFullName, + ); } } @@ -136,7 +168,14 @@ function createChainableTypeChecker(validate) { } function createPrimitiveTypeChecker(expectedType) { - function validate(props, propName, componentName, location, propFullName) { + function validate( + props, + propName, + componentName, + location, + propFullName, + secret + ) { var propValue = props[propName]; var propType = getPropType(propValue); if (propType !== expectedType) { @@ -183,7 +222,8 @@ function createArrayOfTypeChecker(typeChecker) { i, componentName, location, - `${propFullName}[${i}]` + `${propFullName}[${i}]`, + ReactPropTypesSecret ); if (error instanceof Error) { return error; @@ -272,7 +312,8 @@ function createObjectOfTypeChecker(typeChecker) { key, componentName, location, - `${propFullName}.${key}` + `${propFullName}.${key}`, + ReactPropTypesSecret ); if (error instanceof Error) { return error; @@ -294,7 +335,14 @@ function createUnionTypeChecker(arrayOfTypeCheckers) { for (var i = 0; i < arrayOfTypeCheckers.length; i++) { var checker = arrayOfTypeCheckers[i]; if ( - checker(props, propName, componentName, location, propFullName) == null + checker( + props, + propName, + componentName, + location, + propFullName, + ReactPropTypesSecret + ) == null ) { return null; } @@ -344,7 +392,8 @@ function createShapeTypeChecker(shapeTypes) { key, componentName, location, - `${propFullName}.${key}` + `${propFullName}.${key}`, + ReactPropTypesSecret ); if (error) { return error; diff --git a/src/isomorphic/classic/types/ReactPropTypesSecret.js b/src/isomorphic/classic/types/ReactPropTypesSecret.js new file mode 100644 index 00000000000..1b9b06195e8 --- /dev/null +++ b/src/isomorphic/classic/types/ReactPropTypesSecret.js @@ -0,0 +1,18 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ReactPropTypesSecret + */ + + +'use strict'; + + +const ReactPropTypesSecret = '__REACT_PROP_TYPES_SECRET__' + Math.random().toString(); + +module.exports = ReactPropTypesSecret; diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js index 4e787857c76..b11179a11f5 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js @@ -16,6 +16,7 @@ var React; var ReactFragment; var ReactPropTypeLocations; var ReactTestUtils; +var ReactPropTypesSecret; var Component; var MyComponent; @@ -28,7 +29,9 @@ function typeCheckFail(declaration, value, message) { props, 'testProp', 'testComponent', - ReactPropTypeLocations.prop + ReactPropTypeLocations.prop, + null, + ReactPropTypesSecret ); expect(error instanceof Error).toBe(true); expect(error.message).toBe(message); @@ -40,11 +43,32 @@ function typeCheckPass(declaration, value) { props, 'testProp', 'testComponent', - ReactPropTypeLocations.prop + ReactPropTypeLocations.prop, + null, + ReactPropTypesSecret ); expect(error).toBe(null); } +function expectWarningInDevelopment(declaration, value) { + var props = {testProp: value}; + var propName = 'testProp' + Math.random().toString(); + var componentName = 'testComponent' + Math.random().toString(); + for (var i = 0; i < 3; i ++) { + declaration( + props, + propName, + componentName, + 'prop' + ); + } + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'You are manually calling a React.PropTypes validation ' + ); + console.error.calls.reset(); +} + describe('ReactPropTypes', function() { beforeEach(function() { PropTypes = require('ReactPropTypes'); @@ -52,6 +76,7 @@ describe('ReactPropTypes', function() { ReactFragment = require('ReactFragment'); ReactPropTypeLocations = require('ReactPropTypeLocations'); ReactTestUtils = require('ReactTestUtils'); + ReactPropTypesSecret = require('ReactPropTypesSecret'); }); describe('Primitive Types', function() { @@ -124,6 +149,54 @@ describe('ReactPropTypes', function() { typeCheckFail(PropTypes.string.isRequired, null, requiredMessage); typeCheckFail(PropTypes.string.isRequired, undefined, requiredMessage); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.array, /please/); + expectWarningInDevelopment(PropTypes.array, []); + expectWarningInDevelopment(PropTypes.array.isRequired, /please/); + expectWarningInDevelopment(PropTypes.array.isRequired, []); + expectWarningInDevelopment(PropTypes.array.isRequired, null); + expectWarningInDevelopment(PropTypes.array.isRequired, undefined); + expectWarningInDevelopment(PropTypes.bool, []); + expectWarningInDevelopment(PropTypes.bool, true); + expectWarningInDevelopment(PropTypes.bool.isRequired, []); + expectWarningInDevelopment(PropTypes.bool.isRequired, true); + expectWarningInDevelopment(PropTypes.bool.isRequired, null); + expectWarningInDevelopment(PropTypes.bool.isRequired, undefined); + expectWarningInDevelopment(PropTypes.func, false); + expectWarningInDevelopment(PropTypes.func, function() {}); + expectWarningInDevelopment(PropTypes.func.isRequired, false); + expectWarningInDevelopment(PropTypes.func.isRequired, function() {}); + expectWarningInDevelopment(PropTypes.func.isRequired, null); + expectWarningInDevelopment(PropTypes.func.isRequired, undefined); + expectWarningInDevelopment(PropTypes.number, function() {}); + expectWarningInDevelopment(PropTypes.number, 42); + expectWarningInDevelopment(PropTypes.number.isRequired, function() {}); + expectWarningInDevelopment(PropTypes.number.isRequired, 42); + expectWarningInDevelopment(PropTypes.number.isRequired, null); + expectWarningInDevelopment(PropTypes.number.isRequired, undefined); + expectWarningInDevelopment(PropTypes.string, 0); + expectWarningInDevelopment(PropTypes.string, 'foo'); + expectWarningInDevelopment(PropTypes.string.isRequired, 0); + expectWarningInDevelopment(PropTypes.string.isRequired, 'foo'); + expectWarningInDevelopment(PropTypes.string.isRequired, null); + expectWarningInDevelopment(PropTypes.string.isRequired, undefined); + expectWarningInDevelopment(PropTypes.symbol, 0); + expectWarningInDevelopment(PropTypes.symbol, Symbol('Foo')); + expectWarningInDevelopment(PropTypes.symbol.isRequired, 0); + expectWarningInDevelopment(PropTypes.symbol.isRequired, Symbol('Foo')); + expectWarningInDevelopment(PropTypes.symbol.isRequired, null); + expectWarningInDevelopment(PropTypes.symbol.isRequired, undefined); + expectWarningInDevelopment(PropTypes.object, ''); + expectWarningInDevelopment(PropTypes.object, {foo: 'bar'}); + expectWarningInDevelopment(PropTypes.object.isRequired, ''); + expectWarningInDevelopment(PropTypes.object.isRequired, {foo: 'bar'}); + expectWarningInDevelopment(PropTypes.object.isRequired, null); + expectWarningInDevelopment(PropTypes.object.isRequired, undefined); + }); + + }); describe('Any type', function() { @@ -143,6 +216,14 @@ describe('ReactPropTypes', function() { typeCheckFail(PropTypes.any.isRequired, null, requiredMessage); typeCheckFail(PropTypes.any.isRequired, undefined, requiredMessage); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.any, null); + expectWarningInDevelopment(PropTypes.any.isRequired, null); + expectWarningInDevelopment(PropTypes.any.isRequired, undefined); + }); + }); describe('ArrayOf Type', function() { @@ -237,6 +318,24 @@ describe('ReactPropTypes', function() { requiredMessage ); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment( + PropTypes.arrayOf({ foo: PropTypes.string }), + { foo: 'bar' } + ); + expectWarningInDevelopment( + PropTypes.arrayOf(PropTypes.number), + [1, 2, 'b'] + ); + expectWarningInDevelopment( + PropTypes.arrayOf(PropTypes.number), + {'0': 'maybe-array', length: 1} + ); + expectWarningInDevelopment(PropTypes.arrayOf(PropTypes.number).isRequired, null); + expectWarningInDevelopment(PropTypes.arrayOf(PropTypes.number).isRequired, undefined); + }); }); describe('Component Type', function() { @@ -292,6 +391,18 @@ describe('ReactPropTypes', function() { typeCheckFail(PropTypes.element.isRequired, null, requiredMessage); typeCheckFail(PropTypes.element.isRequired, undefined, requiredMessage); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.element, [
, ]); + expectWarningInDevelopment(PropTypes.element, ); + expectWarningInDevelopment(PropTypes.element, 123); + expectWarningInDevelopment(PropTypes.element, 'foo'); + expectWarningInDevelopment(PropTypes.element, false); + expectWarningInDevelopment(PropTypes.element.isRequired, null); + expectWarningInDevelopment(PropTypes.element.isRequired, undefined); + }); + }); describe('Instance Types', function() { @@ -371,6 +482,15 @@ describe('ReactPropTypes', function() { PropTypes.instanceOf(String).isRequired, undefined, requiredMessage ); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.instanceOf(Date), {}); + expectWarningInDevelopment(PropTypes.instanceOf(Date), new Date()); + expectWarningInDevelopment(PropTypes.instanceOf(Date).isRequired, {}); + expectWarningInDevelopment(PropTypes.instanceOf(Date).isRequired, new Date()); + }); + }); describe('React Component Types', function() { @@ -478,6 +598,16 @@ describe('ReactPropTypes', function() { it('should accept empty array for required props', function() { typeCheckPass(PropTypes.node.isRequired, []); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.node, 'node'); + expectWarningInDevelopment(PropTypes.node, {}); + expectWarningInDevelopment(PropTypes.node.isRequired, 'node'); + expectWarningInDevelopment(PropTypes.node.isRequired, undefined); + expectWarningInDevelopment(PropTypes.node.isRequired, undefined); + }); + }); describe('ObjectOf Type', function() { @@ -587,6 +717,21 @@ describe('ReactPropTypes', function() { requiredMessage ); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment( + PropTypes.objectOf({ foo: PropTypes.string }), + { foo: 'bar' } + ); + expectWarningInDevelopment( + PropTypes.objectOf(PropTypes.number), + {a: 1, b: 2, c: 'b'} + ); + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), [1, 2]); + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), null); + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), undefined); + }); }); describe('OneOf Types', function() { @@ -652,6 +797,13 @@ describe('ReactPropTypes', function() { requiredMessage ); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), true); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), null); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), undefined); + }); }); describe('Union Types', function() { @@ -723,6 +875,23 @@ describe('ReactPropTypes', function() { requiredMessage ); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + [] + ); + expectWarningInDevelopment( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + null + ); + expectWarningInDevelopment( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + undefined + ); + }); + }); describe('Shape Types', function() { @@ -807,6 +976,21 @@ describe('ReactPropTypes', function() { requiredMessage ); }); + + it('should warn if called manually in development', function() { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.shape({}), 'some string'); + expectWarningInDevelopment(PropTypes.shape({ foo: PropTypes.number }), { foo: 42 }); + expectWarningInDevelopment( + PropTypes.shape({key: PropTypes.number}).isRequired, + null + ); + expectWarningInDevelopment( + PropTypes.shape({key: PropTypes.number}).isRequired, + undefined + ); + expectWarningInDevelopment(PropTypes.element, ); + }); }); describe('Symbol Type', function() { diff --git a/src/isomorphic/classic/types/checkReactTypeSpec.js b/src/isomorphic/classic/types/checkReactTypeSpec.js index 3b119b395fe..6914d1d1d62 100644 --- a/src/isomorphic/classic/types/checkReactTypeSpec.js +++ b/src/isomorphic/classic/types/checkReactTypeSpec.js @@ -13,6 +13,7 @@ var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); +var ReactPropTypesSecret = require('ReactPropTypesSecret'); var invariant = require('invariant'); var warning = require('warning'); @@ -49,7 +50,7 @@ function checkReactTypeSpec(typeSpecs, values, location, componentName, element, ReactPropTypeLocationNames[location], typeSpecName ); - error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location); + error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret); } catch (ex) { error = ex; } diff --git a/src/renderers/dom/client/wrappers/LinkedValueUtils.js b/src/renderers/dom/client/wrappers/LinkedValueUtils.js index ae3afb98d23..d13630d6f56 100644 --- a/src/renderers/dom/client/wrappers/LinkedValueUtils.js +++ b/src/renderers/dom/client/wrappers/LinkedValueUtils.js @@ -13,6 +13,7 @@ var ReactPropTypes = require('ReactPropTypes'); var ReactPropTypeLocations = require('ReactPropTypeLocations'); +var ReactPropTypesSecret = require('ReactPropTypesSecret'); var invariant = require('invariant'); var warning = require('warning'); @@ -109,7 +110,9 @@ var LinkedValueUtils = { props, propName, tagName, - ReactPropTypeLocations.prop + ReactPropTypeLocations.prop, + null, + ReactPropTypesSecret ); } if (error instanceof Error && !(error.message in loggedTypeFailures)) {