@samitouri / QOS-React / commits / db6ac5c01c

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