Warn if PropType function is called manually (#7132)

* Warn if PropType function is called in production

* Check if console is undefined before warning

* Randomize value of ReactPropTypesSecret

* Remove dev environment tests

* Rename typeCheckPass to productionWarningCheck

* Rename productionWarningCheck to expectWarningInProduction

* Call toString on Math.random()

* Rename test block for React type checks

* Make sure warning isnt emitted for failing props

* Cache warning by component and prop, warn in dev

* Pass ReactPropTypesSecret to internal checks

* Move tests to ReactPropTypes-test.js

* Update the warning message to include link

* Do not test warning for unions  with invalid args
This commit is contained in:
Brandon Dail
2016-07-05 14:02:50 -05:00
committed by Dan Abramov
parent 5d31ebcf5f
commit 95ac239cf3
6 changed files with 273 additions and 13 deletions
@@ -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);
}
+56 -7
View File
@@ -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;
@@ -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;
@@ -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, [<div />, <div />]);
expectWarningInDevelopment(PropTypes.element, <div />);
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, <div />);
});
});
describe('Symbol Type', function() {
@@ -12,6 +12,7 @@
'use strict';
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
var ReactPropTypesSecret = require('ReactPropTypesSecret');
var invariant = require('invariant');
var warning = require('warning');
@@ -48,7 +49,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;
}
@@ -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)) {