@samitouri / QOS-React / commits / 95ac239cf3

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

Brandon Dail committed Jul 5, 2016 at 14:02 UTC 95ac239cf3c527b70daeb433b7aeeaf9e41be9e5
6 files changed +273 -13
src/addons/link/__tests__/ReactLinkPropTypes-test.js
+7 -2
@@ -15,6 +15,7 @@ var emptyFunction = require('emptyFunction');
15 var LinkPropTypes = require('ReactLink').PropTypes;
16 var React = require('React');
17 var ReactPropTypeLocations = require('ReactPropTypeLocations');
18 +var ReactPropTypesSecret = require('ReactPropTypesSecret');
19
20 var invalidMessage = 'Invalid prop `testProp` supplied to `testComponent`.';
21 var requiredMessage =
@@ -26,7 +27,9 @@ function typeCheckFail(declaration, value, message) {
27 props,
28 'testProp',
29 'testComponent',
29 - ReactPropTypeLocations.prop
30 + ReactPropTypeLocations.prop,
31 + null,
32 + ReactPropTypesSecret
33 );
34 expect(error instanceof Error).toBe(true);
35 expect(error.message).toBe(message);
@@ -38,7 +41,9 @@ function typeCheckPass(declaration, value) {
41 props,
42 'testProp',
43 'testComponent',
41 - ReactPropTypeLocations.prop
44 + ReactPropTypeLocations.prop,
45 + null,
46 + ReactPropTypesSecret
47 );
48 expect(error).toBe(null);
49 }
src/isomorphic/classic/types/ReactPropTypes.js
+56 -7
@@ -13,6 +13,7 @@
13
14 var ReactElement = require('ReactElement');
15 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
16 +var ReactPropTypesSecret = require('ReactPropTypesSecret');
17
18 var emptyFunction = require('emptyFunction');
19 var getIteratorFn = require('getIteratorFn');
@@ -105,16 +106,41 @@ function is(x, y) {
106 /*eslint-enable no-self-compare*/
107
108 function createChainableTypeChecker(validate) {
109 + if (__DEV__) {
110 + var manualPropTypeCallCache = {};
111 + }
112 function checkType(
113 isRequired,
114 props,
115 propName,
116 componentName,
117 location,
114 - propFullName
118 + propFullName,
119 + secret
120 ) {
121 componentName = componentName || ANONYMOUS;
122 propFullName = propFullName || propName;
123 + if (__DEV__) {
124 + if (
125 + secret !== ReactPropTypesSecret &&
126 + typeof console !== 'undefined'
127 + ) {
128 + var cacheKey = `${componentName}:${propName}`;
129 + if (!manualPropTypeCallCache[cacheKey]) {
130 + warning(
131 + false,
132 + 'You are manually calling a React.PropTypes validation ' +
133 + 'function for the `%s` prop on `%s`. This is deprecated ' +
134 + 'and will not work in the next major version. You may be ' +
135 + 'seeing this warning due to a third-party PropTypes library. ' +
136 + 'See https://fb.me/react-warning-dont-call-proptypes for details.',
137 + propFullName,
138 + componentName
139 + );
140 + manualPropTypeCallCache[cacheKey] = true;
141 + }
142 + }
143 + }
144 if (props[propName] == null) {
145 var locationName = ReactPropTypeLocationNames[location];
146 if (isRequired) {
@@ -125,7 +151,13 @@ function createChainableTypeChecker(validate) {
151 }
152 return null;
153 } else {
128 - return validate(props, propName, componentName, location, propFullName);
154 + return validate(
155 + props,
156 + propName,
157 + componentName,
158 + location,
159 + propFullName,
160 + );
161 }
162 }
163
@@ -136,7 +168,14 @@ function createChainableTypeChecker(validate) {
168 }
169
170 function createPrimitiveTypeChecker(expectedType) {
139 - function validate(props, propName, componentName, location, propFullName) {
171 + function validate(
172 + props,
173 + propName,
174 + componentName,
175 + location,
176 + propFullName,
177 + secret
178 + ) {
179 var propValue = props[propName];
180 var propType = getPropType(propValue);
181 if (propType !== expectedType) {
@@ -183,7 +222,8 @@ function createArrayOfTypeChecker(typeChecker) {
222 i,
223 componentName,
224 location,
186 - `${propFullName}[${i}]`
225 + `${propFullName}[${i}]`,
226 + ReactPropTypesSecret
227 );
228 if (error instanceof Error) {
229 return error;
@@ -272,7 +312,8 @@ function createObjectOfTypeChecker(typeChecker) {
312 key,
313 componentName,
314 location,
275 - `${propFullName}.${key}`
315 + `${propFullName}.${key}`,
316 + ReactPropTypesSecret
317 );
318 if (error instanceof Error) {
319 return error;
@@ -294,7 +335,14 @@ function createUnionTypeChecker(arrayOfTypeCheckers) {
335 for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
336 var checker = arrayOfTypeCheckers[i];
337 if (
297 - checker(props, propName, componentName, location, propFullName) == null
338 + checker(
339 + props,
340 + propName,
341 + componentName,
342 + location,
343 + propFullName,
344 + ReactPropTypesSecret
345 + ) == null
346 ) {
347 return null;
348 }
@@ -344,7 +392,8 @@ function createShapeTypeChecker(shapeTypes) {
392 key,
393 componentName,
394 location,
347 - `${propFullName}.${key}`
395 + `${propFullName}.${key}`,
396 + ReactPropTypesSecret
397 );
398 if (error) {
399 return error;
src/isomorphic/classic/types/ReactPropTypesSecret.js new
+18
@@ -0,0 +1,18 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactPropTypesSecret
10 + */
11 +
12 +
13 +'use strict';
14 +
15 +
16 +const ReactPropTypesSecret = '__REACT_PROP_TYPES_SECRET__' + Math.random().toString();
17 +
18 +module.exports = ReactPropTypesSecret;
src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js
+186 -2
@@ -16,6 +16,7 @@ var React;
16 var ReactFragment;
17 var ReactPropTypeLocations;
18 var ReactTestUtils;
19 +var ReactPropTypesSecret;
20
21 var Component;
22 var MyComponent;
@@ -28,7 +29,9 @@ function typeCheckFail(declaration, value, message) {
29 props,
30 'testProp',
31 'testComponent',
31 - ReactPropTypeLocations.prop
32 + ReactPropTypeLocations.prop,
33 + null,
34 + ReactPropTypesSecret
35 );
36 expect(error instanceof Error).toBe(true);
37 expect(error.message).toBe(message);
@@ -40,11 +43,32 @@ function typeCheckPass(declaration, value) {
43 props,
44 'testProp',
45 'testComponent',
43 - ReactPropTypeLocations.prop
46 + ReactPropTypeLocations.prop,
47 + null,
48 + ReactPropTypesSecret
49 );
50 expect(error).toBe(null);
51 }
52
53 +function expectWarningInDevelopment(declaration, value) {
54 + var props = {testProp: value};
55 + var propName = 'testProp' + Math.random().toString();
56 + var componentName = 'testComponent' + Math.random().toString();
57 + for (var i = 0; i < 3; i ++) {
58 + declaration(
59 + props,
60 + propName,
61 + componentName,
62 + 'prop'
63 + );
64 + }
65 + expect(console.error.calls.count()).toBe(1);
66 + expect(console.error.calls.argsFor(0)[0]).toContain(
67 + 'You are manually calling a React.PropTypes validation '
68 + );
69 + console.error.calls.reset();
70 +}
71 +
72 describe('ReactPropTypes', function() {
73 beforeEach(function() {
74 PropTypes = require('ReactPropTypes');
@@ -52,6 +76,7 @@ describe('ReactPropTypes', function() {
76 ReactFragment = require('ReactFragment');
77 ReactPropTypeLocations = require('ReactPropTypeLocations');
78 ReactTestUtils = require('ReactTestUtils');
79 + ReactPropTypesSecret = require('ReactPropTypesSecret');
80 });
81
82 describe('Primitive Types', function() {
@@ -124,6 +149,54 @@ describe('ReactPropTypes', function() {
149 typeCheckFail(PropTypes.string.isRequired, null, requiredMessage);
150 typeCheckFail(PropTypes.string.isRequired, undefined, requiredMessage);
151 });
152 +
153 + it('should warn if called manually in development', function() {
154 + spyOn(console, 'error');
155 + expectWarningInDevelopment(PropTypes.array, /please/);
156 + expectWarningInDevelopment(PropTypes.array, []);
157 + expectWarningInDevelopment(PropTypes.array.isRequired, /please/);
158 + expectWarningInDevelopment(PropTypes.array.isRequired, []);
159 + expectWarningInDevelopment(PropTypes.array.isRequired, null);
160 + expectWarningInDevelopment(PropTypes.array.isRequired, undefined);
161 + expectWarningInDevelopment(PropTypes.bool, []);
162 + expectWarningInDevelopment(PropTypes.bool, true);
163 + expectWarningInDevelopment(PropTypes.bool.isRequired, []);
164 + expectWarningInDevelopment(PropTypes.bool.isRequired, true);
165 + expectWarningInDevelopment(PropTypes.bool.isRequired, null);
166 + expectWarningInDevelopment(PropTypes.bool.isRequired, undefined);
167 + expectWarningInDevelopment(PropTypes.func, false);
168 + expectWarningInDevelopment(PropTypes.func, function() {});
169 + expectWarningInDevelopment(PropTypes.func.isRequired, false);
170 + expectWarningInDevelopment(PropTypes.func.isRequired, function() {});
171 + expectWarningInDevelopment(PropTypes.func.isRequired, null);
172 + expectWarningInDevelopment(PropTypes.func.isRequired, undefined);
173 + expectWarningInDevelopment(PropTypes.number, function() {});
174 + expectWarningInDevelopment(PropTypes.number, 42);
175 + expectWarningInDevelopment(PropTypes.number.isRequired, function() {});
176 + expectWarningInDevelopment(PropTypes.number.isRequired, 42);
177 + expectWarningInDevelopment(PropTypes.number.isRequired, null);
178 + expectWarningInDevelopment(PropTypes.number.isRequired, undefined);
179 + expectWarningInDevelopment(PropTypes.string, 0);
180 + expectWarningInDevelopment(PropTypes.string, 'foo');
181 + expectWarningInDevelopment(PropTypes.string.isRequired, 0);
182 + expectWarningInDevelopment(PropTypes.string.isRequired, 'foo');
183 + expectWarningInDevelopment(PropTypes.string.isRequired, null);
184 + expectWarningInDevelopment(PropTypes.string.isRequired, undefined);
185 + expectWarningInDevelopment(PropTypes.symbol, 0);
186 + expectWarningInDevelopment(PropTypes.symbol, Symbol('Foo'));
187 + expectWarningInDevelopment(PropTypes.symbol.isRequired, 0);
188 + expectWarningInDevelopment(PropTypes.symbol.isRequired, Symbol('Foo'));
189 + expectWarningInDevelopment(PropTypes.symbol.isRequired, null);
190 + expectWarningInDevelopment(PropTypes.symbol.isRequired, undefined);
191 + expectWarningInDevelopment(PropTypes.object, '');
192 + expectWarningInDevelopment(PropTypes.object, {foo: 'bar'});
193 + expectWarningInDevelopment(PropTypes.object.isRequired, '');
194 + expectWarningInDevelopment(PropTypes.object.isRequired, {foo: 'bar'});
195 + expectWarningInDevelopment(PropTypes.object.isRequired, null);
196 + expectWarningInDevelopment(PropTypes.object.isRequired, undefined);
197 + });
198 +
199 +
200 });
201
202 describe('Any type', function() {
@@ -143,6 +216,14 @@ describe('ReactPropTypes', function() {
216 typeCheckFail(PropTypes.any.isRequired, null, requiredMessage);
217 typeCheckFail(PropTypes.any.isRequired, undefined, requiredMessage);
218 });
219 +
220 + it('should warn if called manually in development', function() {
221 + spyOn(console, 'error');
222 + expectWarningInDevelopment(PropTypes.any, null);
223 + expectWarningInDevelopment(PropTypes.any.isRequired, null);
224 + expectWarningInDevelopment(PropTypes.any.isRequired, undefined);
225 + });
226 +
227 });
228
229 describe('ArrayOf Type', function() {
@@ -237,6 +318,24 @@ describe('ReactPropTypes', function() {
318 requiredMessage
319 );
320 });
321 +
322 + it('should warn if called manually in development', function() {
323 + spyOn(console, 'error');
324 + expectWarningInDevelopment(
325 + PropTypes.arrayOf({ foo: PropTypes.string }),
326 + { foo: 'bar' }
327 + );
328 + expectWarningInDevelopment(
329 + PropTypes.arrayOf(PropTypes.number),
330 + [1, 2, 'b']
331 + );
332 + expectWarningInDevelopment(
333 + PropTypes.arrayOf(PropTypes.number),
334 + {'0': 'maybe-array', length: 1}
335 + );
336 + expectWarningInDevelopment(PropTypes.arrayOf(PropTypes.number).isRequired, null);
337 + expectWarningInDevelopment(PropTypes.arrayOf(PropTypes.number).isRequired, undefined);
338 + });
339 });
340
341 describe('Component Type', function() {
@@ -292,6 +391,18 @@ describe('ReactPropTypes', function() {
391 typeCheckFail(PropTypes.element.isRequired, null, requiredMessage);
392 typeCheckFail(PropTypes.element.isRequired, undefined, requiredMessage);
393 });
394 +
395 + it('should warn if called manually in development', function() {
396 + spyOn(console, 'error');
397 + expectWarningInDevelopment(PropTypes.element, [<div />, <div />]);
398 + expectWarningInDevelopment(PropTypes.element, <div />);
399 + expectWarningInDevelopment(PropTypes.element, 123);
400 + expectWarningInDevelopment(PropTypes.element, 'foo');
401 + expectWarningInDevelopment(PropTypes.element, false);
402 + expectWarningInDevelopment(PropTypes.element.isRequired, null);
403 + expectWarningInDevelopment(PropTypes.element.isRequired, undefined);
404 + });
405 +
406 });
407
408 describe('Instance Types', function() {
@@ -371,6 +482,15 @@ describe('ReactPropTypes', function() {
482 PropTypes.instanceOf(String).isRequired, undefined, requiredMessage
483 );
484 });
485 +
486 + it('should warn if called manually in development', function() {
487 + spyOn(console, 'error');
488 + expectWarningInDevelopment(PropTypes.instanceOf(Date), {});
489 + expectWarningInDevelopment(PropTypes.instanceOf(Date), new Date());
490 + expectWarningInDevelopment(PropTypes.instanceOf(Date).isRequired, {});
491 + expectWarningInDevelopment(PropTypes.instanceOf(Date).isRequired, new Date());
492 + });
493 +
494 });
495
496 describe('React Component Types', function() {
@@ -478,6 +598,16 @@ describe('ReactPropTypes', function() {
598 it('should accept empty array for required props', function() {
599 typeCheckPass(PropTypes.node.isRequired, []);
600 });
601 +
602 + it('should warn if called manually in development', function() {
603 + spyOn(console, 'error');
604 + expectWarningInDevelopment(PropTypes.node, 'node');
605 + expectWarningInDevelopment(PropTypes.node, {});
606 + expectWarningInDevelopment(PropTypes.node.isRequired, 'node');
607 + expectWarningInDevelopment(PropTypes.node.isRequired, undefined);
608 + expectWarningInDevelopment(PropTypes.node.isRequired, undefined);
609 + });
610 +
611 });
612
613 describe('ObjectOf Type', function() {
@@ -587,6 +717,21 @@ describe('ReactPropTypes', function() {
717 requiredMessage
718 );
719 });
720 +
721 + it('should warn if called manually in development', function() {
722 + spyOn(console, 'error');
723 + expectWarningInDevelopment(
724 + PropTypes.objectOf({ foo: PropTypes.string }),
725 + { foo: 'bar' }
726 + );
727 + expectWarningInDevelopment(
728 + PropTypes.objectOf(PropTypes.number),
729 + {a: 1, b: 2, c: 'b'}
730 + );
731 + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), [1, 2]);
732 + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), null);
733 + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), undefined);
734 + });
735 });
736
737 describe('OneOf Types', function() {
@@ -652,6 +797,13 @@ describe('ReactPropTypes', function() {
797 requiredMessage
798 );
799 });
800 +
801 + it('should warn if called manually in development', function() {
802 + spyOn(console, 'error');
803 + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), true);
804 + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), null);
805 + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), undefined);
806 + });
807 });
808
809 describe('Union Types', function() {
@@ -723,6 +875,23 @@ describe('ReactPropTypes', function() {
875 requiredMessage
876 );
877 });
878 +
879 + it('should warn if called manually in development', function() {
880 + spyOn(console, 'error');
881 + expectWarningInDevelopment(
882 + PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
883 + []
884 + );
885 + expectWarningInDevelopment(
886 + PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
887 + null
888 + );
889 + expectWarningInDevelopment(
890 + PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
891 + undefined
892 + );
893 + });
894 +
895 });
896
897 describe('Shape Types', function() {
@@ -807,6 +976,21 @@ describe('ReactPropTypes', function() {
976 requiredMessage
977 );
978 });
979 +
980 + it('should warn if called manually in development', function() {
981 + spyOn(console, 'error');
982 + expectWarningInDevelopment(PropTypes.shape({}), 'some string');
983 + expectWarningInDevelopment(PropTypes.shape({ foo: PropTypes.number }), { foo: 42 });
984 + expectWarningInDevelopment(
985 + PropTypes.shape({key: PropTypes.number}).isRequired,
986 + null
987 + );
988 + expectWarningInDevelopment(
989 + PropTypes.shape({key: PropTypes.number}).isRequired,
990 + undefined
991 + );
992 + expectWarningInDevelopment(PropTypes.element, <div />);
993 + });
994 });
995
996 describe('Symbol Type', function() {
src/isomorphic/classic/types/checkReactTypeSpec.js
+2 -1
@@ -12,6 +12,7 @@
12 'use strict';
13
14 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
15 +var ReactPropTypesSecret = require('ReactPropTypesSecret');
16
17 var invariant = require('invariant');
18 var warning = require('warning');
@@ -48,7 +49,7 @@ function checkReactTypeSpec(typeSpecs, values, location, componentName, element,
49 ReactPropTypeLocationNames[location],
50 typeSpecName
51 );
51 - error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location);
52 + error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);
53 } catch (ex) {
54 error = ex;
55 }
src/renderers/dom/client/wrappers/LinkedValueUtils.js
+4 -1
@@ -13,6 +13,7 @@
13
14 var ReactPropTypes = require('ReactPropTypes');
15 var ReactPropTypeLocations = require('ReactPropTypeLocations');
16 +var ReactPropTypesSecret = require('ReactPropTypesSecret');
17
18 var invariant = require('invariant');
19 var warning = require('warning');
@@ -109,7 +110,9 @@ var LinkedValueUtils = {
110 props,
111 propName,
112 tagName,
112 - ReactPropTypeLocations.prop
113 + ReactPropTypeLocations.prop,
114 + null,
115 + ReactPropTypesSecret
116 );
117 }
118 if (error instanceof Error && !(error.message in loggedTypeFailures)) {