Extract the type checker into a separate module (#6851)
The type checker is now a separate module under `isomorphic/classic/types`
Keyan Zhang committed
May 23, 2016 at 17:12 UTC
db6ac5c01c4ad669db7ca264bc81ae5b3d6dfa01
9 files changed
+149
-143
src/isomorphic/classic/__tests__/ReactContextValidator-test.js
+21
-13
@@ -24,6 +24,10 @@ var ReactTestUtils;
24
var reactComponentExpect;
25
26
describe('ReactContextValidator', function() {
27
+ function normalizeCodeLocInfo(str) {
28
+ return str.replace(/\(at .+?:\d+\)/g, '(at **)');
29
+ }
30
+
31
beforeEach(function() {
32
jest.resetModuleRegistry();
33
@@ -146,9 +150,10 @@ describe('ReactContextValidator', function() {
150
ReactTestUtils.renderIntoDocument(<Component />);
151
152
expect(console.error.argsForCall.length).toBe(1);
149
- expect(console.error.argsForCall[0][0]).toBe(
150
- 'Warning: Failed Context Types: ' +
151
- 'Required context `foo` was not specified in `Component`.'
153
+ expect(normalizeCodeLocInfo(console.error.argsForCall[0][0])).toBe(
154
+ 'Warning: Failed context type: ' +
155
+ 'Required context `foo` was not specified in `Component`.\n' +
156
+ ' in Component (at **)'
157
);
158
159
var ComponentInFooStringContext = React.createClass({
@@ -193,11 +198,12 @@ describe('ReactContextValidator', function() {
198
ReactTestUtils.renderIntoDocument(<ComponentInFooNumberContext fooValue={123} />);
199
200
expect(console.error.argsForCall.length).toBe(2);
196
- expect(console.error.argsForCall[1][0]).toBe(
197
- 'Warning: Failed Context Types: ' +
201
+ expect(normalizeCodeLocInfo(console.error.argsForCall[1][0])).toBe(
202
+ 'Warning: Failed context type: ' +
203
'Invalid context `foo` of type `number` supplied ' +
199
- 'to `Component`, expected `string`.' +
200
- ' Check the render method of `ComponentInFooNumberContext`.'
204
+ 'to `Component`, expected `string`.\n' +
205
+ ' in Component (at **)\n' +
206
+ ' in ComponentInFooNumberContext (at **)'
207
);
208
});
209
@@ -221,18 +227,20 @@ describe('ReactContextValidator', function() {
227
228
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
229
expect(console.error.argsForCall.length).toBe(1);
224
- expect(console.error.argsForCall[0][0]).toBe(
225
- 'Warning: Failed Context Types: ' +
226
- 'Required child context `foo` was not specified in `Component`.'
230
+ expect(normalizeCodeLocInfo(console.error.argsForCall[0][0])).toBe(
231
+ 'Warning: Failed childContext type: ' +
232
+ 'Required child context `foo` was not specified in `Component`.\n' +
233
+ ' in Component (at **)'
234
);
235
236
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
237
238
expect(console.error.argsForCall.length).toBe(2);
232
- expect(console.error.argsForCall[1][0]).toBe(
233
- 'Warning: Failed Context Types: ' +
239
+ expect(normalizeCodeLocInfo(console.error.argsForCall[1][0])).toBe(
240
+ 'Warning: Failed childContext type: ' +
241
'Invalid child context `foo` of type `number` ' +
235
- 'supplied to `Component`, expected `string`.'
242
+ 'supplied to `Component`, expected `string`.\n' +
243
+ ' in Component (at **)'
244
);
245
246
ReactTestUtils.renderIntoDocument(
src/isomorphic/classic/element/ReactElementValidator.js
+8
-68
@@ -21,12 +21,12 @@
21
var ReactCurrentOwner = require('ReactCurrentOwner');
22
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
23
var ReactElement = require('ReactElement');
24
-var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
24
var ReactPropTypeLocations = require('ReactPropTypeLocations');
25
26
+var checkReactTypeSpec = require('checkReactTypeSpec');
27
+
28
var canDefineProperty = require('canDefineProperty');
29
var getIteratorFn = require('getIteratorFn');
29
-var invariant = require('invariant');
30
var warning = require('warning');
31
32
function getDeclarationErrorAddendum() {
@@ -46,8 +46,6 @@ function getDeclarationErrorAddendum() {
46
*/
47
var ownerHasKeyUseWarning = {};
48
49
-var loggedTypeFailures = {};
50
-
49
function getCurrentComponentErrorInfo(parentType) {
50
var info = getDeclarationErrorAddendum();
51
@@ -152,66 +150,6 @@ function validateChildKeys(node, parentType) {
150
}
151
}
152
155
-/**
156
- * Assert that the props are valid
157
- *
158
- * @param {object} element
159
- * @param {string} componentName Name of the component for error messages.
160
- * @param {object} propTypes Map of prop name to a ReactPropType
161
- * @param {string} location e.g. "prop", "context", "child context"
162
- * @private
163
- */
164
-function checkPropTypes(element, componentName, propTypes, location) {
165
- var props = element.props;
166
- for (var propName in propTypes) {
167
- if (propTypes.hasOwnProperty(propName)) {
168
- var error;
169
- // Prop type validation may throw. In case they do, we don't want to
170
- // fail the render phase where it didn't fail before. So we log it.
171
- // After these have been cleaned up, we'll let them throw.
172
- try {
173
- // This is intentionally an invariant that gets caught. It's the same
174
- // behavior as without this statement except with a better message.
175
- invariant(
176
- typeof propTypes[propName] === 'function',
177
- '%s: %s type `%s` is invalid; it must be a function, usually from ' +
178
- 'React.PropTypes.',
179
- componentName || 'React class',
180
- ReactPropTypeLocationNames[location],
181
- propName
182
- );
183
- error = propTypes[propName](props, propName, componentName, location);
184
- } catch (ex) {
185
- error = ex;
186
- }
187
- warning(
188
- !error || error instanceof Error,
189
- '%s: type specification of %s `%s` is invalid; the type checker ' +
190
- 'function must return `null` or an `Error` but returned a %s. ' +
191
- 'You may have forgotten to pass an argument to the type checker ' +
192
- 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +
193
- 'shape all require an argument).',
194
- componentName || 'React class',
195
- ReactPropTypeLocationNames[location],
196
- propName,
197
- typeof error
198
- );
199
- if (error instanceof Error && !(error.message in loggedTypeFailures)) {
200
- // Only monitor this failure once because there tends to be a lot of the
201
- // same error.
202
- loggedTypeFailures[error.message] = true;
203
-
204
- warning(
205
- false,
206
- 'Failed propType: %s%s',
207
- error.message,
208
- ReactComponentTreeDevtool.getCurrentStackAddendum(element)
209
- );
210
- }
211
- }
212
- }
213
-}
214
-
153
/**
154
* Given an element, validate that its props follow the propTypes definition,
155
* provided by the type.
@@ -225,11 +163,13 @@ function validatePropTypes(element) {
163
}
164
var name = componentClass.displayName || componentClass.name;
165
if (componentClass.propTypes) {
228
- checkPropTypes(
229
- element,
230
- name,
166
+ checkReactTypeSpec(
167
componentClass.propTypes,
232
- ReactPropTypeLocations.prop
168
+ element.props,
169
+ ReactPropTypeLocations.prop,
170
+ name,
171
+ element,
172
+ null
173
);
174
}
175
if (typeof componentClass.getDefaultProps === 'function') {
src/isomorphic/classic/element/__tests__/ReactElementClone-test.js
+1
-1
@@ -269,7 +269,7 @@ describe('ReactElementClone', function() {
269
ReactTestUtils.renderIntoDocument(React.createElement(GrandParent));
270
expect(console.error.argsForCall.length).toBe(1);
271
expect(console.error.argsForCall[0][0]).toBe(
272
- 'Warning: Failed propType: ' +
272
+ 'Warning: Failed prop type: ' +
273
'Invalid prop `color` of type `number` supplied to `Component`, ' +
274
'expected `string`.\n' +
275
' in Component (created by GrandParent)\n' +
src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
+5
-5
@@ -281,7 +281,7 @@ describe('ReactElementValidator', function() {
281
});
282
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
283
expect(console.error.argsForCall[0][0]).toBe(
284
- 'Warning: Failed propType: ' +
284
+ 'Warning: Failed prop type: ' +
285
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
286
'expected `string`.\n' +
287
' in MyComp (created by ParentComp)\n' +
@@ -360,7 +360,7 @@ describe('ReactElementValidator', function() {
360
361
expect(console.error.calls.length).toBe(1);
362
expect(console.error.argsForCall[0][0]).toBe(
363
- 'Warning: Failed propType: ' +
363
+ 'Warning: Failed prop type: ' +
364
'Required prop `prop` was not specified in `Component`.\n' +
365
' in Component'
366
);
@@ -385,7 +385,7 @@ describe('ReactElementValidator', function() {
385
386
expect(console.error.calls.length).toBe(1);
387
expect(console.error.argsForCall[0][0]).toBe(
388
- 'Warning: Failed propType: ' +
388
+ 'Warning: Failed prop type: ' +
389
'Required prop `prop` was not specified in `Component`.\n' +
390
' in Component'
391
);
@@ -412,13 +412,13 @@ describe('ReactElementValidator', function() {
412
413
expect(console.error.calls.length).toBe(2);
414
expect(console.error.argsForCall[0][0]).toBe(
415
- 'Warning: Failed propType: ' +
415
+ 'Warning: Failed prop type: ' +
416
'Required prop `prop` was not specified in `Component`.\n' +
417
' in Component'
418
);
419
420
expect(console.error.argsForCall[1][0]).toBe(
421
- 'Warning: Failed propType: ' +
421
+ 'Warning: Failed prop type: ' +
422
'Invalid prop `prop` of type `number` supplied to ' +
423
'`Component`, expected `string`.\n' +
424
' in Component'
src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js
+2
-2
@@ -867,7 +867,7 @@ describe('ReactPropTypes', function() {
867
instance = ReactTestUtils.renderIntoDocument(instance);
868
869
expect(spy.argsForCall.length).toBe(1);
870
- expect(spy.argsForCall[0][1]).toBe('num');
870
+ expect(spy.argsForCall[0][1]).toBe('num');
871
});
872
873
it('should have received the validator\'s return value', function() {
@@ -894,7 +894,7 @@ describe('ReactPropTypes', function() {
894
expect(
895
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
896
).toBe(
897
- 'Warning: Failed propType: num must be 5!\n' +
897
+ 'Warning: Failed prop type: num must be 5!\n' +
898
' in Component (at **)'
899
);
900
});
src/isomorphic/classic/types/checkReactTypeSpec.js
new
+93
@@ -0,0 +1,93 @@
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 checkReactTypeSpec
10
+ */
11
+
12
+'use strict';
13
+
14
+var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
15
+var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
16
+
17
+var invariant = require('invariant');
18
+var warning = require('warning');
19
+
20
+var loggedTypeFailures = {};
21
+
22
+/**
23
+ * Assert that the values match with the type specs.
24
+ * Error messages are memorized and will only be shown once.
25
+ *
26
+ * @param {object} typeSpecs Map of name to a ReactPropType
27
+ * @param {object} values Runtime values that need to be type-checked
28
+ * @param {string} location e.g. "prop", "context", "child context"
29
+ * @param {string} componentName Name of the component for error messages.
30
+ * @param {?object} element The React element that is being type-checked
31
+ * @param {?number} debugID The React component instance that is being type-checked
32
+ * @private
33
+ */
34
+function checkReactTypeSpec(typeSpecs, values, location, componentName, element, debugID) {
35
+ for (var typeSpecName in typeSpecs) {
36
+ if (typeSpecs.hasOwnProperty(typeSpecName)) {
37
+ var error;
38
+ // Prop type validation may throw. In case they do, we don't want to
39
+ // fail the render phase where it didn't fail before. So we log it.
40
+ // After these have been cleaned up, we'll let them throw.
41
+ try {
42
+ // This is intentionally an invariant that gets caught. It's the same
43
+ // behavior as without this statement except with a better message.
44
+ invariant(
45
+ typeof typeSpecs[typeSpecName] === 'function',
46
+ '%s: %s type `%s` is invalid; it must be a function, usually from ' +
47
+ 'React.PropTypes.',
48
+ componentName || 'React class',
49
+ ReactPropTypeLocationNames[location],
50
+ typeSpecName
51
+ );
52
+ error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location);
53
+ } catch (ex) {
54
+ error = ex;
55
+ }
56
+ warning(
57
+ !error || error instanceof Error,
58
+ '%s: type specification of %s `%s` is invalid; the type checker ' +
59
+ 'function must return `null` or an `Error` but returned a %s. ' +
60
+ 'You may have forgotten to pass an argument to the type checker ' +
61
+ 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +
62
+ 'shape all require an argument).',
63
+ componentName || 'React class',
64
+ ReactPropTypeLocationNames[location],
65
+ typeSpecName,
66
+ typeof error
67
+ );
68
+ if (error instanceof Error && !(error.message in loggedTypeFailures)) {
69
+ // Only monitor this failure once because there tends to be a lot of the
70
+ // same error.
71
+ loggedTypeFailures[error.message] = true;
72
+
73
+ var componentStackInfo = '';
74
+
75
+ if (debugID !== null) {
76
+ componentStackInfo = ReactComponentTreeDevtool.getStackAddendumByID(debugID);
77
+ } else if (element !== null) {
78
+ componentStackInfo = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
79
+ }
80
+
81
+ warning(
82
+ false,
83
+ 'Failed %s type: %s%s',
84
+ location,
85
+ error.message,
86
+ componentStackInfo
87
+ );
88
+ }
89
+ }
90
+ }
91
+}
92
+
93
+module.exports = checkReactTypeSpec;
src/isomorphic/modern/element/__tests__/ReactJSXElementValidator-test.js
+5
-5
@@ -198,7 +198,7 @@ describe('ReactJSXElementValidator', function() {
198
expect(
199
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
200
).toBe(
201
- 'Warning: Failed propType: ' +
201
+ 'Warning: Failed prop type: ' +
202
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
203
'expected `string`.\n' +
204
' in MyComp (at **)\n' +
@@ -249,7 +249,7 @@ describe('ReactJSXElementValidator', function() {
249
expect(
250
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
251
).toBe(
252
- 'Warning: Failed propType: ' +
252
+ 'Warning: Failed prop type: ' +
253
'Required prop `prop` was not specified in `RequiredPropComponent`.\n' +
254
' in RequiredPropComponent (at **)'
255
);
@@ -264,7 +264,7 @@ describe('ReactJSXElementValidator', function() {
264
expect(
265
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
266
).toBe(
267
- 'Warning: Failed propType: ' +
267
+ 'Warning: Failed prop type: ' +
268
'Required prop `prop` was not specified in `RequiredPropComponent`.\n' +
269
' in RequiredPropComponent (at **)'
270
);
@@ -280,7 +280,7 @@ describe('ReactJSXElementValidator', function() {
280
expect(
281
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
282
).toBe(
283
- 'Warning: Failed propType: ' +
283
+ 'Warning: Failed prop type: ' +
284
'Required prop `prop` was not specified in `RequiredPropComponent`.\n' +
285
' in RequiredPropComponent (at **)'
286
);
@@ -288,7 +288,7 @@ describe('ReactJSXElementValidator', function() {
288
expect(
289
console.error.argsForCall[1][0].replace(/\(at .+?:\d+\)/g, '(at **)')
290
).toBe(
291
- 'Warning: Failed propType: ' +
291
+ 'Warning: Failed prop type: ' +
292
'Invalid prop `prop` of type `number` supplied to ' +
293
'`RequiredPropComponent`, expected `string`.\n' +
294
' in RequiredPropComponent (at **)'
src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
+13
-48
@@ -19,26 +19,16 @@ var ReactInstanceMap = require('ReactInstanceMap');
19
var ReactInstrumentation = require('ReactInstrumentation');
20
var ReactNodeTypes = require('ReactNodeTypes');
21
var ReactPropTypeLocations = require('ReactPropTypeLocations');
22
-var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
22
var ReactReconciler = require('ReactReconciler');
23
var ReactUpdateQueue = require('ReactUpdateQueue');
24
25
+var checkReactTypeSpec = require('checkReactTypeSpec');
26
+
27
var emptyObject = require('emptyObject');
28
var invariant = require('invariant');
29
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
30
var warning = require('warning');
31
31
-function getDeclarationErrorAddendum(component) {
32
- var owner = component._currentElement._owner || null;
33
- if (owner) {
34
- var name = owner.getName();
35
- if (name) {
36
- return ' Check the render method of `' + name + '`.';
37
- }
38
- }
39
- return '';
40
-}
41
-
32
function StatelessComponent(Component) {
33
}
34
StatelessComponent.prototype.render = function() {
@@ -669,45 +659,20 @@ var ReactCompositeComponentMixin = {
659
/**
660
* Assert that the context types are valid
661
*
672
- * @param {object} propTypes Map of context field to a ReactPropType
673
- * @param {object} props
662
+ * @param {object} typeSpecs Map of context field to a ReactPropType
663
+ * @param {object} values Runtime values that need to be type-checked
664
* @param {string} location e.g. "prop", "context", "child context"
665
* @private
666
*/
677
- _checkContextTypes: function(propTypes, props, location) {
678
- var componentName = this.getName();
679
- for (var propName in propTypes) {
680
- if (propTypes.hasOwnProperty(propName)) {
681
- var error;
682
- try {
683
- // This is intentionally an invariant that gets caught. It's the same
684
- // behavior as without this statement except with a better message.
685
- invariant(
686
- typeof propTypes[propName] === 'function',
687
- '%s: %s type `%s` is invalid; it must be a function, usually ' +
688
- 'from React.PropTypes.',
689
- componentName || 'React class',
690
- ReactPropTypeLocationNames[location],
691
- propName
692
- );
693
- error = propTypes[propName](props, propName, componentName, location);
694
- } catch (ex) {
695
- error = ex;
696
- }
697
- if (error instanceof Error) {
698
- // We may want to extend this logic for similar errors in
699
- // top-level render calls, so I'm abstracting it away into
700
- // a function to minimize refactoring in the future
701
- var addendum = getDeclarationErrorAddendum(this);
702
- warning(
703
- false,
704
- 'Failed Context Types: %s%s',
705
- error.message,
706
- addendum
707
- );
708
- }
709
- }
710
- }
667
+ _checkContextTypes: function(typeSpecs, values, location) {
668
+ checkReactTypeSpec(
669
+ typeSpecs,
670
+ values,
671
+ location,
672
+ this.getName(),
673
+ null,
674
+ this._debugID
675
+ );
676
},
677
678
receiveComponent: function(nextElement, transaction, nextContext) {
src/renderers/shared/stack/reconciler/__tests__/ReactStatelessComponent-test.js
+1
-1
@@ -178,7 +178,7 @@ describe('ReactStatelessComponent', function() {
178
expect(
179
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
180
).toBe(
181
- 'Warning: Failed propType: Invalid prop `test` of type `number` ' +
181
+ 'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
182
'supplied to `Child`, expected `string`.\n' +
183
' in Child (at **)'
184
);