diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 42c5ccc37f697..a374ec06f7813 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -313,9 +313,11 @@ src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js * should not warn for valid values * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should should accept any value * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should fail for invalid argument * should support the arrayOf propTypes * should support arrayOf with complex types @@ -325,16 +327,19 @@ src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js * should not warn when passing an empty array * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should support components * should not support multiple components or scalar values * should be able to define a single child as label * should warn when passing no label and isRequired is set * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should warn for invalid instances * should not warn for valid values * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should warn for invalid values * should not warn for valid values * should not warn for iterables @@ -342,6 +347,7 @@ src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js * should not warn for null/undefined if not required * should warn for missing required values * should accept empty array for required props +* should warn if called manually in development * should fail for invalid argument * should support the objectOf propTypes * should support objectOf with complex types @@ -351,16 +357,19 @@ src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js * should not warn when passing an empty object * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should warn but not error for invalid argument * should warn for invalid values * should not warn for valid values * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should warn but not error for invalid argument * should warn if none of the types are valid * should not warn if one of the types are valid * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should warn for non objects * should not warn for empty values * should not warn for an empty object @@ -371,6 +380,7 @@ src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js * should warn for invalid key types * should be implicitly optional and not warn without values * should warn for missing required values +* should warn if called manually in development * should warn for non-symbol * should not warn for a polyfilled Symbol * should have been called with the right params diff --git a/src/isomorphic/classic/types/ReactPropTypes.js b/src/isomorphic/classic/types/ReactPropTypes.js index d12965be95b54..dcd0dbc82a7e2 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'); @@ -154,16 +155,42 @@ function PropTypeError(message) { PropTypeError.prototype = Error.prototype; 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 production with 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) { @@ -180,7 +207,13 @@ function createChainableTypeChecker(validate) { } return null; } else { - return validate(props, propName, componentName, location, propFullName); + return validate( + props, + propName, + componentName, + location, + propFullName, + ); } } @@ -191,7 +224,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) { @@ -238,7 +278,8 @@ function createArrayOfTypeChecker(typeChecker) { i, componentName, location, - `${propFullName}[${i}]` + `${propFullName}[${i}]`, + ReactPropTypesSecret ); if (error instanceof Error) { return error; @@ -329,7 +370,8 @@ function createObjectOfTypeChecker(typeChecker) { key, componentName, location, - `${propFullName}.${key}` + `${propFullName}.${key}`, + ReactPropTypesSecret ); if (error instanceof Error) { return error; @@ -351,7 +393,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; } @@ -401,7 +450,8 @@ function createShapeTypeChecker(shapeTypes) { key, componentName, location, - `${propFullName}.${key}` + `${propFullName}.${key}`, + ReactPropTypesSecret ); if (error) { return error; diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js index dbeaf93fcfc60..7cb5240fb8f15 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js @@ -15,6 +15,7 @@ var PropTypes; var React; var ReactFragment; var ReactTestUtils; +var ReactPropTypesSecret; var Component; var MyComponent; @@ -26,6 +27,8 @@ function typeCheckFail(declaration, value, message) { 'testProp', 'testComponent', 'prop', + null, + ReactPropTypesSecret ); expect(error instanceof Error).toBe(true); expect(error.message).toBe(message); @@ -42,6 +45,8 @@ function typeCheckFailRequiredValues(declaration) { 'testProp', 'testComponent', 'prop', + null, + ReactPropTypesSecret ); expect(error1 instanceof Error).toBe(true); expect(error1.message).toBe(specifiedButIsNullMsg); @@ -51,6 +56,8 @@ function typeCheckFailRequiredValues(declaration) { 'testProp', 'testComponent', 'prop', + null, + ReactPropTypesSecret ); expect(error2 instanceof Error).toBe(true); expect(error2.message).toBe(unspecifiedMsg); @@ -60,6 +67,8 @@ function typeCheckFailRequiredValues(declaration) { 'testProp', 'testComponent', 'prop', + null, + ReactPropTypesSecret ); expect(error3 instanceof Error).toBe(true); expect(error3.message).toBe(unspecifiedMsg); @@ -72,16 +81,38 @@ function typeCheckPass(declaration, value) { 'testProp', 'testComponent', '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', () => { beforeEach(() => { PropTypes = require('ReactPropTypes'); React = require('React'); ReactFragment = require('ReactFragment'); ReactTestUtils = require('ReactTestUtils'); + ReactPropTypesSecret = require('ReactPropTypesSecret'); }); describe('Primitive Types', () => { @@ -153,6 +184,52 @@ describe('ReactPropTypes', () => { it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.string.isRequired); }); + + it('should warn if called manually in development', () => { + 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', () => { @@ -171,6 +248,13 @@ describe('ReactPropTypes', () => { it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.any.isRequired); }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.any, null); + expectWarningInDevelopment(PropTypes.any.isRequired, null); + expectWarningInDevelopment(PropTypes.any.isRequired, undefined); + }); }); describe('ArrayOf Type', () => { @@ -258,6 +342,24 @@ describe('ReactPropTypes', () => { PropTypes.arrayOf(PropTypes.number).isRequired ); }); + + it('should warn if called manually in development', () => { + 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', () => { @@ -330,6 +432,18 @@ describe('ReactPropTypes', () => { it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.element.isRequired); }); + + it('should warn if called manually in development', () => { + 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', () => { @@ -404,6 +518,15 @@ describe('ReactPropTypes', () => { it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.instanceOf(String).isRequired); }); + + it('should warn if called manually in development', () => { + 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', () => { @@ -502,6 +625,16 @@ describe('ReactPropTypes', () => { it('should accept empty array for required props', () => { typeCheckPass(PropTypes.node.isRequired, []); }); + + it('should warn if called manually in development', () => { + 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', () => { @@ -604,6 +737,21 @@ describe('ReactPropTypes', () => { PropTypes.objectOf(PropTypes.number).isRequired ); }); + + it('should warn if called manually in development', () => { + 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', () => { @@ -660,6 +808,13 @@ describe('ReactPropTypes', () => { it('should warn for missing required values', () => { typeCheckFailRequiredValues(PropTypes.oneOf(['red', 'blue']).isRequired); }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), true); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), null); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), undefined); + }); }); describe('Union Types', () => { @@ -724,6 +879,23 @@ describe('ReactPropTypes', () => { PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired ); }); + + it('should warn if called manually in development', () => { + 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', () => { @@ -803,6 +975,21 @@ describe('ReactPropTypes', () => { PropTypes.shape({key: PropTypes.number}).isRequired ); }); + + it('should warn if called manually in development', () => { + 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', () => { diff --git a/src/renderers/dom/shared/utils/ReactControlledValuePropTypes.js b/src/renderers/dom/shared/utils/ReactControlledValuePropTypes.js index ae319c9687d73..fb77ebd7c0f81 100644 --- a/src/renderers/dom/shared/utils/ReactControlledValuePropTypes.js +++ b/src/renderers/dom/shared/utils/ReactControlledValuePropTypes.js @@ -12,6 +12,7 @@ 'use strict'; var React = require('React'); +var ReactPropTypesSecret = require('ReactPropTypesSecret'); var warning = require('warning'); @@ -79,6 +80,8 @@ var ReactControlledValuePropTypes = { propName, tagName, 'prop', + null, + ReactPropTypesSecret, ); } if (error instanceof Error && !(error.message in loggedTypeFailures)) { diff --git a/src/shared/types/ReactPropTypesSecret.js b/src/shared/types/ReactPropTypesSecret.js new file mode 100644 index 0000000000000..0f47ff2afb919 --- /dev/null +++ b/src/shared/types/ReactPropTypesSecret.js @@ -0,0 +1,17 @@ +/** + * 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. + * + * @flow + * @providesModule ReactPropTypesSecret + */ + +'use strict'; + +const ReactPropTypesSecret = 'SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED'; + +module.exports = ReactPropTypesSecret; diff --git a/src/shared/types/checkReactTypeSpec.js b/src/shared/types/checkReactTypeSpec.js index 3b4c3c35ce502..e274c3e9f586f 100644 --- a/src/shared/types/checkReactTypeSpec.js +++ b/src/shared/types/checkReactTypeSpec.js @@ -12,6 +12,7 @@ 'use strict'; var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames'); +var ReactPropTypesSecret = require('ReactPropTypesSecret'); var invariant = require('invariant'); var warning = require('warning'); @@ -74,7 +75,7 @@ function checkReactTypeSpec( ReactPropTypeLocationNames[location], typeSpecName ); - error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location); + error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret); } catch (ex) { error = ex; }