mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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:
committed by
Dan Abramov
parent
5d31ebcf5f
commit
95ac239cf3
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
Reference in New Issue
Block a user