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)) {