Extract the type checker into a separate module (#6851)

The type checker is now a separate module under `isomorphic/classic/types`
This commit is contained in:
Keyan Zhang
2016-05-23 17:12:13 -07:00
parent 799eae2faf
commit db6ac5c01c
9 changed files with 149 additions and 143 deletions
@@ -24,6 +24,10 @@ var ReactTestUtils;
var reactComponentExpect;
describe('ReactContextValidator', function() {
function normalizeCodeLocInfo(str) {
return str.replace(/\(at .+?:\d+\)/g, '(at **)');
}
beforeEach(function() {
jest.resetModuleRegistry();
@@ -146,9 +150,10 @@ describe('ReactContextValidator', function() {
ReactTestUtils.renderIntoDocument(<Component />);
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Failed Context Types: ' +
'Required context `foo` was not specified in `Component`.'
expect(normalizeCodeLocInfo(console.error.argsForCall[0][0])).toBe(
'Warning: Failed context type: ' +
'Required context `foo` was not specified in `Component`.\n' +
' in Component (at **)'
);
var ComponentInFooStringContext = React.createClass({
@@ -193,11 +198,12 @@ describe('ReactContextValidator', function() {
ReactTestUtils.renderIntoDocument(<ComponentInFooNumberContext fooValue={123} />);
expect(console.error.argsForCall.length).toBe(2);
expect(console.error.argsForCall[1][0]).toBe(
'Warning: Failed Context Types: ' +
expect(normalizeCodeLocInfo(console.error.argsForCall[1][0])).toBe(
'Warning: Failed context type: ' +
'Invalid context `foo` of type `number` supplied ' +
'to `Component`, expected `string`.' +
' Check the render method of `ComponentInFooNumberContext`.'
'to `Component`, expected `string`.\n' +
' in Component (at **)\n' +
' in ComponentInFooNumberContext (at **)'
);
});
@@ -221,18 +227,20 @@ describe('ReactContextValidator', function() {
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Failed Context Types: ' +
'Required child context `foo` was not specified in `Component`.'
expect(normalizeCodeLocInfo(console.error.argsForCall[0][0])).toBe(
'Warning: Failed childContext type: ' +
'Required child context `foo` was not specified in `Component`.\n' +
' in Component (at **)'
);
ReactTestUtils.renderIntoDocument(<Component testContext={{foo: 123}} />);
expect(console.error.argsForCall.length).toBe(2);
expect(console.error.argsForCall[1][0]).toBe(
'Warning: Failed Context Types: ' +
expect(normalizeCodeLocInfo(console.error.argsForCall[1][0])).toBe(
'Warning: Failed childContext type: ' +
'Invalid child context `foo` of type `number` ' +
'supplied to `Component`, expected `string`.'
'supplied to `Component`, expected `string`.\n' +
' in Component (at **)'
);
ReactTestUtils.renderIntoDocument(
@@ -21,12 +21,12 @@
var ReactCurrentOwner = require('ReactCurrentOwner');
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
var ReactElement = require('ReactElement');
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var checkReactTypeSpec = require('checkReactTypeSpec');
var canDefineProperty = require('canDefineProperty');
var getIteratorFn = require('getIteratorFn');
var invariant = require('invariant');
var warning = require('warning');
function getDeclarationErrorAddendum() {
@@ -46,8 +46,6 @@ function getDeclarationErrorAddendum() {
*/
var ownerHasKeyUseWarning = {};
var loggedTypeFailures = {};
function getCurrentComponentErrorInfo(parentType) {
var info = getDeclarationErrorAddendum();
@@ -152,66 +150,6 @@ function validateChildKeys(node, parentType) {
}
}
/**
* Assert that the props are valid
*
* @param {object} element
* @param {string} componentName Name of the component for error messages.
* @param {object} propTypes Map of prop name to a ReactPropType
* @param {string} location e.g. "prop", "context", "child context"
* @private
*/
function checkPropTypes(element, componentName, propTypes, location) {
var props = element.props;
for (var propName in propTypes) {
if (propTypes.hasOwnProperty(propName)) {
var error;
// Prop type validation may throw. In case they do, we don't want to
// fail the render phase where it didn't fail before. So we log it.
// After these have been cleaned up, we'll let them throw.
try {
// This is intentionally an invariant that gets caught. It's the same
// behavior as without this statement except with a better message.
invariant(
typeof propTypes[propName] === 'function',
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
'React.PropTypes.',
componentName || 'React class',
ReactPropTypeLocationNames[location],
propName
);
error = propTypes[propName](props, propName, componentName, location);
} catch (ex) {
error = ex;
}
warning(
!error || error instanceof Error,
'%s: type specification of %s `%s` is invalid; the type checker ' +
'function must return `null` or an `Error` but returned a %s. ' +
'You may have forgotten to pass an argument to the type checker ' +
'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +
'shape all require an argument).',
componentName || 'React class',
ReactPropTypeLocationNames[location],
propName,
typeof error
);
if (error instanceof Error && !(error.message in loggedTypeFailures)) {
// Only monitor this failure once because there tends to be a lot of the
// same error.
loggedTypeFailures[error.message] = true;
warning(
false,
'Failed propType: %s%s',
error.message,
ReactComponentTreeDevtool.getCurrentStackAddendum(element)
);
}
}
}
}
/**
* Given an element, validate that its props follow the propTypes definition,
* provided by the type.
@@ -225,11 +163,13 @@ function validatePropTypes(element) {
}
var name = componentClass.displayName || componentClass.name;
if (componentClass.propTypes) {
checkPropTypes(
element,
name,
checkReactTypeSpec(
componentClass.propTypes,
ReactPropTypeLocations.prop
element.props,
ReactPropTypeLocations.prop,
name,
element,
null
);
}
if (typeof componentClass.getDefaultProps === 'function') {
@@ -269,7 +269,7 @@ describe('ReactElementClone', function() {
ReactTestUtils.renderIntoDocument(React.createElement(GrandParent));
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `Component`, ' +
'expected `string`.\n' +
' in Component (created by GrandParent)\n' +
@@ -281,7 +281,7 @@ describe('ReactElementValidator', function() {
});
ReactTestUtils.renderIntoDocument(React.createElement(ParentComp));
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
'expected `string`.\n' +
' in MyComp (created by ParentComp)\n' +
@@ -360,7 +360,7 @@ describe('ReactElementValidator', function() {
expect(console.error.calls.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Required prop `prop` was not specified in `Component`.\n' +
' in Component'
);
@@ -385,7 +385,7 @@ describe('ReactElementValidator', function() {
expect(console.error.calls.length).toBe(1);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Required prop `prop` was not specified in `Component`.\n' +
' in Component'
);
@@ -412,13 +412,13 @@ describe('ReactElementValidator', function() {
expect(console.error.calls.length).toBe(2);
expect(console.error.argsForCall[0][0]).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Required prop `prop` was not specified in `Component`.\n' +
' in Component'
);
expect(console.error.argsForCall[1][0]).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Invalid prop `prop` of type `number` supplied to ' +
'`Component`, expected `string`.\n' +
' in Component'
@@ -867,7 +867,7 @@ describe('ReactPropTypes', function() {
instance = ReactTestUtils.renderIntoDocument(instance);
expect(spy.argsForCall.length).toBe(1);
expect(spy.argsForCall[0][1]).toBe('num');
expect(spy.argsForCall[0][1]).toBe('num');
});
it('should have received the validator\'s return value', function() {
@@ -894,7 +894,7 @@ describe('ReactPropTypes', function() {
expect(
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
).toBe(
'Warning: Failed propType: num must be 5!\n' +
'Warning: Failed prop type: num must be 5!\n' +
' in Component (at **)'
);
});
@@ -0,0 +1,93 @@
/**
* 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 checkReactTypeSpec
*/
'use strict';
var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool');
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
var invariant = require('invariant');
var warning = require('warning');
var loggedTypeFailures = {};
/**
* Assert that the values match with the type specs.
* Error messages are memorized and will only be shown once.
*
* @param {object} typeSpecs Map of name to a ReactPropType
* @param {object} values Runtime values that need to be type-checked
* @param {string} location e.g. "prop", "context", "child context"
* @param {string} componentName Name of the component for error messages.
* @param {?object} element The React element that is being type-checked
* @param {?number} debugID The React component instance that is being type-checked
* @private
*/
function checkReactTypeSpec(typeSpecs, values, location, componentName, element, debugID) {
for (var typeSpecName in typeSpecs) {
if (typeSpecs.hasOwnProperty(typeSpecName)) {
var error;
// Prop type validation may throw. In case they do, we don't want to
// fail the render phase where it didn't fail before. So we log it.
// After these have been cleaned up, we'll let them throw.
try {
// This is intentionally an invariant that gets caught. It's the same
// behavior as without this statement except with a better message.
invariant(
typeof typeSpecs[typeSpecName] === 'function',
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
'React.PropTypes.',
componentName || 'React class',
ReactPropTypeLocationNames[location],
typeSpecName
);
error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location);
} catch (ex) {
error = ex;
}
warning(
!error || error instanceof Error,
'%s: type specification of %s `%s` is invalid; the type checker ' +
'function must return `null` or an `Error` but returned a %s. ' +
'You may have forgotten to pass an argument to the type checker ' +
'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' +
'shape all require an argument).',
componentName || 'React class',
ReactPropTypeLocationNames[location],
typeSpecName,
typeof error
);
if (error instanceof Error && !(error.message in loggedTypeFailures)) {
// Only monitor this failure once because there tends to be a lot of the
// same error.
loggedTypeFailures[error.message] = true;
var componentStackInfo = '';
if (debugID !== null) {
componentStackInfo = ReactComponentTreeDevtool.getStackAddendumByID(debugID);
} else if (element !== null) {
componentStackInfo = ReactComponentTreeDevtool.getCurrentStackAddendum(element);
}
warning(
false,
'Failed %s type: %s%s',
location,
error.message,
componentStackInfo
);
}
}
}
}
module.exports = checkReactTypeSpec;
@@ -198,7 +198,7 @@ describe('ReactJSXElementValidator', function() {
expect(
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
'expected `string`.\n' +
' in MyComp (at **)\n' +
@@ -249,7 +249,7 @@ describe('ReactJSXElementValidator', function() {
expect(
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Required prop `prop` was not specified in `RequiredPropComponent`.\n' +
' in RequiredPropComponent (at **)'
);
@@ -264,7 +264,7 @@ describe('ReactJSXElementValidator', function() {
expect(
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Required prop `prop` was not specified in `RequiredPropComponent`.\n' +
' in RequiredPropComponent (at **)'
);
@@ -280,7 +280,7 @@ describe('ReactJSXElementValidator', function() {
expect(
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Required prop `prop` was not specified in `RequiredPropComponent`.\n' +
' in RequiredPropComponent (at **)'
);
@@ -288,7 +288,7 @@ describe('ReactJSXElementValidator', function() {
expect(
console.error.argsForCall[1][0].replace(/\(at .+?:\d+\)/g, '(at **)')
).toBe(
'Warning: Failed propType: ' +
'Warning: Failed prop type: ' +
'Invalid prop `prop` of type `number` supplied to ' +
'`RequiredPropComponent`, expected `string`.\n' +
' in RequiredPropComponent (at **)'
@@ -19,26 +19,16 @@ var ReactInstanceMap = require('ReactInstanceMap');
var ReactInstrumentation = require('ReactInstrumentation');
var ReactNodeTypes = require('ReactNodeTypes');
var ReactPropTypeLocations = require('ReactPropTypeLocations');
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
var ReactReconciler = require('ReactReconciler');
var ReactUpdateQueue = require('ReactUpdateQueue');
var checkReactTypeSpec = require('checkReactTypeSpec');
var emptyObject = require('emptyObject');
var invariant = require('invariant');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var warning = require('warning');
function getDeclarationErrorAddendum(component) {
var owner = component._currentElement._owner || null;
if (owner) {
var name = owner.getName();
if (name) {
return ' Check the render method of `' + name + '`.';
}
}
return '';
}
function StatelessComponent(Component) {
}
StatelessComponent.prototype.render = function() {
@@ -669,45 +659,20 @@ var ReactCompositeComponentMixin = {
/**
* Assert that the context types are valid
*
* @param {object} propTypes Map of context field to a ReactPropType
* @param {object} props
* @param {object} typeSpecs Map of context field to a ReactPropType
* @param {object} values Runtime values that need to be type-checked
* @param {string} location e.g. "prop", "context", "child context"
* @private
*/
_checkContextTypes: function(propTypes, props, location) {
var componentName = this.getName();
for (var propName in propTypes) {
if (propTypes.hasOwnProperty(propName)) {
var error;
try {
// This is intentionally an invariant that gets caught. It's the same
// behavior as without this statement except with a better message.
invariant(
typeof propTypes[propName] === 'function',
'%s: %s type `%s` is invalid; it must be a function, usually ' +
'from React.PropTypes.',
componentName || 'React class',
ReactPropTypeLocationNames[location],
propName
);
error = propTypes[propName](props, propName, componentName, location);
} catch (ex) {
error = ex;
}
if (error instanceof Error) {
// We may want to extend this logic for similar errors in
// top-level render calls, so I'm abstracting it away into
// a function to minimize refactoring in the future
var addendum = getDeclarationErrorAddendum(this);
warning(
false,
'Failed Context Types: %s%s',
error.message,
addendum
);
}
}
}
_checkContextTypes: function(typeSpecs, values, location) {
checkReactTypeSpec(
typeSpecs,
values,
location,
this.getName(),
null,
this._debugID
);
},
receiveComponent: function(nextElement, transaction, nextContext) {
@@ -178,7 +178,7 @@ describe('ReactStatelessComponent', function() {
expect(
console.error.argsForCall[0][0].replace(/\(at .+?:\d+\)/g, '(at **)')
).toBe(
'Warning: Failed propType: Invalid prop `test` of type `number` ' +
'Warning: Failed prop type: Invalid prop `test` of type `number` ' +
'supplied to `Child`, expected `string`.\n' +
' in Child (at **)'
);