mirror of
https://github.com/hanzoai/react.git
synced 2026-09-24 17:02:27 +00:00
Merge pull request #9004 from acdlite/proptypescheck
API for checking external objects against React prop types
This commit is contained in:
@@ -130,7 +130,9 @@ var rendererSharedState = {
|
||||
React: 'react/lib/React',
|
||||
// Shared state
|
||||
ReactCurrentOwner: 'react/lib/ReactCurrentOwner',
|
||||
checkPropTypes: 'react/lib/checkPropTypes',
|
||||
ReactComponentTreeHook: 'react/lib/ReactComponentTreeHook',
|
||||
ReactDebugCurrentFrame: 'react/lib/ReactDebugCurrentFrame',
|
||||
};
|
||||
|
||||
var moduleMapReactDOM = Object.assign(
|
||||
|
||||
@@ -307,6 +307,8 @@ src/isomorphic/classic/element/__tests__/ReactElementValidator-test.js
|
||||
* does not blow up on key warning with undefined type
|
||||
|
||||
src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js
|
||||
* does not return a value from a validator
|
||||
* does not throw if validator throws
|
||||
* should warn for invalid strings
|
||||
* should fail date and regexp correctly
|
||||
* should not warn for valid values
|
||||
|
||||
@@ -21,6 +21,7 @@ var ReactVersion = require('ReactVersion');
|
||||
|
||||
var onlyChild = require('onlyChild');
|
||||
var warning = require('warning');
|
||||
var checkPropTypes = require('checkPropTypes');
|
||||
|
||||
var createElement = ReactElement.createElement;
|
||||
var createFactory = ReactElement.createFactory;
|
||||
@@ -71,6 +72,8 @@ var React = {
|
||||
cloneElement: cloneElement,
|
||||
isValidElement: ReactElement.isValidElement,
|
||||
|
||||
checkPropTypes: checkPropTypes,
|
||||
|
||||
// Classic
|
||||
|
||||
PropTypes: ReactPropTypes,
|
||||
|
||||
@@ -261,7 +261,7 @@ describe('ReactContextValidator', () => {
|
||||
ReactTestUtils.renderIntoDocument(<Component testContext={{bar: 123}} />);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe(
|
||||
'Warning: Failed childContext type: ' +
|
||||
'Warning: Failed child context type: ' +
|
||||
'The child context `foo` is marked as required in `Component`, but its ' +
|
||||
'value is `undefined`.\n' +
|
||||
' in Component (at **)'
|
||||
@@ -271,7 +271,7 @@ describe('ReactContextValidator', () => {
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(2);
|
||||
expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe(
|
||||
'Warning: Failed childContext type: ' +
|
||||
'Warning: Failed child context type: ' +
|
||||
'Invalid child context `foo` of type `number` ' +
|
||||
'supplied to `Component`, expected `string`.\n' +
|
||||
' in Component (at **)'
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
var ReactBaseClasses = require('ReactBaseClasses');
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
|
||||
var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
|
||||
|
||||
var emptyObject = require('emptyObject');
|
||||
@@ -22,8 +21,6 @@ var warning = require('warning');
|
||||
|
||||
var ReactComponent = ReactBaseClasses.Component;
|
||||
|
||||
import type { ReactPropTypeLocations } from 'ReactPropTypeLocations';
|
||||
|
||||
var MIXINS_KEY = 'mixins';
|
||||
|
||||
// Helper function to allow the creation of anonymous functions which do not
|
||||
@@ -330,7 +327,7 @@ var RESERVED_SPEC_KEYS = {
|
||||
validateTypeDef(
|
||||
Constructor,
|
||||
childContextTypes,
|
||||
'childContext'
|
||||
'child context'
|
||||
);
|
||||
}
|
||||
Constructor.childContextTypes = Object.assign(
|
||||
@@ -390,7 +387,7 @@ var RESERVED_SPEC_KEYS = {
|
||||
function validateTypeDef(
|
||||
Constructor,
|
||||
typeDef,
|
||||
location: ReactPropTypeLocations,
|
||||
location: string,
|
||||
) {
|
||||
for (var propName in typeDef) {
|
||||
if (typeDef.hasOwnProperty(propName)) {
|
||||
@@ -401,7 +398,7 @@ function validateTypeDef(
|
||||
'%s: %s type `%s` is invalid; it must be a function, usually from ' +
|
||||
'React.PropTypes.',
|
||||
Constructor.displayName || 'ReactClass',
|
||||
ReactPropTypeLocationNames[location],
|
||||
location,
|
||||
propName
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 ReactDebugCurrentFrame
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { DebugID } from 'ReactInstanceType';
|
||||
|
||||
const ReactDebugCurrentFrame = {};
|
||||
|
||||
if (__DEV__) {
|
||||
var {
|
||||
getStackAddendumByID,
|
||||
getStackAddendumByWorkInProgressFiber,
|
||||
getCurrentStackAddendum,
|
||||
} = require('ReactComponentTreeHook');
|
||||
|
||||
// Component that is being worked on
|
||||
ReactDebugCurrentFrame.current = (null : Fiber | DebugID | null);
|
||||
|
||||
// Element that is being cloned or created
|
||||
ReactDebugCurrentFrame.element = (null : *);
|
||||
|
||||
ReactDebugCurrentFrame.getStackAddendum = function() : string | null {
|
||||
let stack = null;
|
||||
const current = ReactDebugCurrentFrame.current;
|
||||
const element = ReactDebugCurrentFrame.element;
|
||||
if (current !== null) {
|
||||
if (typeof current === 'number') {
|
||||
// DebugID from Stack.
|
||||
const debugID = current;
|
||||
stack = getStackAddendumByID(debugID);
|
||||
} else if (typeof current.tag === 'number') {
|
||||
// This is a Fiber.
|
||||
// The stack will only be correct if this is a work in progress
|
||||
// version and we're calling it during reconciliation.
|
||||
const workInProgress = current;
|
||||
stack = getStackAddendumByWorkInProgressFiber(workInProgress);
|
||||
}
|
||||
} else if (element !== null) {
|
||||
stack = getCurrentStackAddendum(element);
|
||||
}
|
||||
return stack;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = ReactDebugCurrentFrame;
|
||||
@@ -27,7 +27,11 @@ var checkReactTypeSpec = require('checkReactTypeSpec');
|
||||
var canDefineProperty = require('canDefineProperty');
|
||||
var getComponentName = require('getComponentName');
|
||||
var getIteratorFn = require('getIteratorFn');
|
||||
var warning = require('warning');
|
||||
|
||||
if (__DEV__) {
|
||||
var warning = require('warning');
|
||||
var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame');
|
||||
}
|
||||
|
||||
function getDeclarationErrorAddendum() {
|
||||
if (ReactCurrentOwner.current) {
|
||||
@@ -181,9 +185,7 @@ function validatePropTypes(element) {
|
||||
componentClass.propTypes,
|
||||
element.props,
|
||||
'prop',
|
||||
name,
|
||||
element,
|
||||
null
|
||||
name
|
||||
);
|
||||
}
|
||||
if (typeof componentClass.getDefaultProps === 'function') {
|
||||
@@ -243,6 +245,10 @@ var ReactElementValidator = {
|
||||
return element;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFrame.element = element;
|
||||
}
|
||||
|
||||
// Skip key warning if the type isn't valid since our key validation logic
|
||||
// doesn't expect a non-string/function type and can throw confusing errors.
|
||||
// We don't want exception behavior to differ between dev and prod.
|
||||
@@ -256,6 +262,10 @@ var ReactElementValidator = {
|
||||
|
||||
validatePropTypes(element);
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFrame.element = null;
|
||||
}
|
||||
|
||||
return element;
|
||||
},
|
||||
|
||||
@@ -296,10 +306,16 @@ var ReactElementValidator = {
|
||||
|
||||
cloneElement: function(element, props, children) {
|
||||
var newElement = ReactElement.cloneElement.apply(this, arguments);
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFrame.element = newElement;
|
||||
}
|
||||
for (var i = 2; i < arguments.length; i++) {
|
||||
validateChildKeys(arguments[i], newElement.type);
|
||||
}
|
||||
validatePropTypes(newElement);
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFrame.element = null;
|
||||
}
|
||||
return newElement;
|
||||
},
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
'use strict';
|
||||
|
||||
var ReactElement = require('ReactElement');
|
||||
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
|
||||
var ReactPropTypesSecret = require('ReactPropTypesSecret');
|
||||
|
||||
var emptyFunction = require('emptyFunction');
|
||||
@@ -123,6 +122,7 @@ if (__DEV__) {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* inlined Object.is polyfill to avoid requiring consumers ship their own
|
||||
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
||||
@@ -192,16 +192,15 @@ function createChainableTypeChecker(validate) {
|
||||
}
|
||||
}
|
||||
if (props[propName] == null) {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
if (isRequired) {
|
||||
if (props[propName] === null) {
|
||||
return new PropTypeError(
|
||||
`The ${locationName} \`${propFullName}\` is marked as required ` +
|
||||
`The ${location} \`${propFullName}\` is marked as required ` +
|
||||
`in \`${componentName}\`, but its value is \`null\`.`
|
||||
);
|
||||
}
|
||||
return new PropTypeError(
|
||||
`The ${locationName} \`${propFullName}\` is marked as required in ` +
|
||||
`The ${location} \`${propFullName}\` is marked as required in ` +
|
||||
`\`${componentName}\`, but its value is \`undefined\`.`
|
||||
);
|
||||
}
|
||||
@@ -235,14 +234,13 @@ function createPrimitiveTypeChecker(expectedType) {
|
||||
var propValue = props[propName];
|
||||
var propType = getPropType(propValue);
|
||||
if (propType !== expectedType) {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
// `propValue` being instance of, say, date/regexp, pass the 'object'
|
||||
// check, but we can offer a more precise error message here rather than
|
||||
// 'of type `object`'.
|
||||
var preciseType = getPreciseType(propValue);
|
||||
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` of type ` +
|
||||
`Invalid ${location} \`${propFullName}\` of type ` +
|
||||
`\`${preciseType}\` supplied to \`${componentName}\`, expected ` +
|
||||
`\`${expectedType}\`.`
|
||||
);
|
||||
@@ -265,10 +263,9 @@ function createArrayOfTypeChecker(typeChecker) {
|
||||
}
|
||||
var propValue = props[propName];
|
||||
if (!Array.isArray(propValue)) {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
var propType = getPropType(propValue);
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` of type ` +
|
||||
`Invalid ${location} \`${propFullName}\` of type ` +
|
||||
`\`${propType}\` supplied to \`${componentName}\`, expected an array.`
|
||||
);
|
||||
}
|
||||
@@ -294,10 +291,9 @@ function createElementTypeChecker() {
|
||||
function validate(props, propName, componentName, location, propFullName) {
|
||||
var propValue = props[propName];
|
||||
if (!ReactElement.isValidElement(propValue)) {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
var propType = getPropType(propValue);
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` of type ` +
|
||||
`Invalid ${location} \`${propFullName}\` of type ` +
|
||||
`\`${propType}\` supplied to \`${componentName}\`, expected a single ReactElement.`
|
||||
);
|
||||
}
|
||||
@@ -309,11 +305,10 @@ function createElementTypeChecker() {
|
||||
function createInstanceTypeChecker(expectedClass) {
|
||||
function validate(props, propName, componentName, location, propFullName) {
|
||||
if (!(props[propName] instanceof expectedClass)) {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
var expectedClassName = expectedClass.name || ANONYMOUS;
|
||||
var actualClassName = getClassName(props[propName]);
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` of type ` +
|
||||
`Invalid ${location} \`${propFullName}\` of type ` +
|
||||
`\`${actualClassName}\` supplied to \`${componentName}\`, expected ` +
|
||||
`instance of \`${expectedClassName}\`.`
|
||||
);
|
||||
@@ -337,10 +332,9 @@ function createEnumTypeChecker(expectedValues) {
|
||||
}
|
||||
}
|
||||
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
var valuesString = JSON.stringify(expectedValues);
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` of value \`${propValue}\` ` +
|
||||
`Invalid ${location} \`${propFullName}\` of value \`${propValue}\` ` +
|
||||
`supplied to \`${componentName}\`, expected one of ${valuesString}.`
|
||||
);
|
||||
}
|
||||
@@ -357,9 +351,8 @@ function createObjectOfTypeChecker(typeChecker) {
|
||||
var propValue = props[propName];
|
||||
var propType = getPropType(propValue);
|
||||
if (propType !== 'object') {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` of type ` +
|
||||
`Invalid ${location} \`${propFullName}\` of type ` +
|
||||
`\`${propType}\` supplied to \`${componentName}\`, expected an object.`
|
||||
);
|
||||
}
|
||||
@@ -406,9 +399,8 @@ function createUnionTypeChecker(arrayOfTypeCheckers) {
|
||||
}
|
||||
}
|
||||
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
|
||||
`Invalid ${location} \`${propFullName}\` supplied to ` +
|
||||
`\`${componentName}\`.`
|
||||
);
|
||||
}
|
||||
@@ -418,9 +410,8 @@ function createUnionTypeChecker(arrayOfTypeCheckers) {
|
||||
function createNodeChecker() {
|
||||
function validate(props, propName, componentName, location, propFullName) {
|
||||
if (!isNode(props[propName])) {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
|
||||
`Invalid ${location} \`${propFullName}\` supplied to ` +
|
||||
`\`${componentName}\`, expected a ReactNode.`
|
||||
);
|
||||
}
|
||||
@@ -434,9 +425,8 @@ function createShapeTypeChecker(shapeTypes) {
|
||||
var propValue = props[propName];
|
||||
var propType = getPropType(propValue);
|
||||
if (propType !== 'object') {
|
||||
var locationName = ReactPropTypeLocationNames[location];
|
||||
return new PropTypeError(
|
||||
`Invalid ${locationName} \`${propFullName}\` of type \`${propType}\` ` +
|
||||
`Invalid ${location} \`${propFullName}\` of type \`${propType}\` ` +
|
||||
`supplied to \`${componentName}\`, expected \`object\`.`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -12,26 +12,48 @@
|
||||
'use strict';
|
||||
|
||||
var PropTypes;
|
||||
var checkPropTypes;
|
||||
var checkReactTypeSpec;
|
||||
var React;
|
||||
var ReactDOM;
|
||||
var ReactFragment;
|
||||
var ReactTestUtils;
|
||||
var ReactPropTypesSecret;
|
||||
|
||||
var Component;
|
||||
var MyComponent;
|
||||
|
||||
function typeCheckFail(declaration, value, message) {
|
||||
var props = {testProp: value};
|
||||
var error = declaration(
|
||||
props,
|
||||
'testProp',
|
||||
'testComponent',
|
||||
'prop',
|
||||
null,
|
||||
ReactPropTypesSecret
|
||||
);
|
||||
expect(error instanceof Error).toBe(true);
|
||||
expect(error.message).toBe(message);
|
||||
function resetWarningCache() {
|
||||
jest.resetModules();
|
||||
checkReactTypeSpec = require('checkReactTypeSpec');
|
||||
checkPropTypes = require('checkPropTypes');
|
||||
}
|
||||
|
||||
function getPropTypeWarningMessage(propTypes, object, componentName) {
|
||||
if (!console.error.calls) {
|
||||
spyOn(console, 'error');
|
||||
} else {
|
||||
console.error.calls.reset();
|
||||
}
|
||||
resetWarningCache();
|
||||
checkReactTypeSpec(propTypes, object, 'prop', 'testComponent');
|
||||
const callCount = console.error.calls.count();
|
||||
if (callCount > 1) {
|
||||
throw new Error('Too many warnings.');
|
||||
}
|
||||
const message = console.error.calls.argsFor(0)[0] || null;
|
||||
console.error.calls.reset();
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
function typeCheckFail(declaration, value, expectedMessage) {
|
||||
const propTypes = {
|
||||
testProp: declaration,
|
||||
};
|
||||
const props = {
|
||||
testProp: value,
|
||||
};
|
||||
const message = getPropTypeWarningMessage(propTypes, props, 'testComponent');
|
||||
expect(message).toContain(expectedMessage);
|
||||
}
|
||||
|
||||
function typeCheckFailRequiredValues(declaration) {
|
||||
@@ -39,52 +61,31 @@ function typeCheckFailRequiredValues(declaration) {
|
||||
'`testComponent`, but its value is `null`.';
|
||||
var unspecifiedMsg = 'The prop `testProp` is marked as required in ' +
|
||||
'`testComponent`, but its value is \`undefined\`.';
|
||||
var props1 = {testProp: null};
|
||||
var error1 = declaration(
|
||||
props1,
|
||||
'testProp',
|
||||
'testComponent',
|
||||
'prop',
|
||||
null,
|
||||
ReactPropTypesSecret
|
||||
);
|
||||
expect(error1 instanceof Error).toBe(true);
|
||||
expect(error1.message).toBe(specifiedButIsNullMsg);
|
||||
var props2 = {testProp: undefined};
|
||||
var error2 = declaration(
|
||||
props2,
|
||||
'testProp',
|
||||
'testComponent',
|
||||
'prop',
|
||||
null,
|
||||
ReactPropTypesSecret
|
||||
);
|
||||
expect(error2 instanceof Error).toBe(true);
|
||||
expect(error2.message).toBe(unspecifiedMsg);
|
||||
var props3 = {};
|
||||
var error3 = declaration(
|
||||
props3,
|
||||
'testProp',
|
||||
'testComponent',
|
||||
'prop',
|
||||
null,
|
||||
ReactPropTypesSecret
|
||||
);
|
||||
expect(error3 instanceof Error).toBe(true);
|
||||
expect(error3.message).toBe(unspecifiedMsg);
|
||||
|
||||
var propTypes = { testProp: declaration };
|
||||
|
||||
// Required prop is null
|
||||
var message1 = getPropTypeWarningMessage(propTypes, { testProp: null }, 'testComponent');
|
||||
expect(message1).toContain(specifiedButIsNullMsg);
|
||||
|
||||
// Required prop is undefined
|
||||
var message2 = getPropTypeWarningMessage(propTypes, { testProp: undefined }, 'testComponent');
|
||||
expect(message2).toContain(unspecifiedMsg);
|
||||
|
||||
// Required prop is not a member of props object
|
||||
var message3 = getPropTypeWarningMessage(propTypes, {}, 'testComponent');
|
||||
expect(message3).toContain(unspecifiedMsg);
|
||||
}
|
||||
|
||||
function typeCheckPass(declaration, value) {
|
||||
var props = {testProp: value};
|
||||
var error = declaration(
|
||||
props,
|
||||
'testProp',
|
||||
'testComponent',
|
||||
'prop',
|
||||
null,
|
||||
ReactPropTypesSecret
|
||||
);
|
||||
expect(error).toBe(null);
|
||||
const propTypes = {
|
||||
testProp: declaration,
|
||||
};
|
||||
const props = {
|
||||
testProp: value,
|
||||
};
|
||||
const message = getPropTypeWarningMessage(propTypes, props, 'testComponent');
|
||||
expect(message).toBe(null);
|
||||
}
|
||||
|
||||
function expectWarningInDevelopment(declaration, value) {
|
||||
@@ -110,9 +111,37 @@ describe('ReactPropTypes', () => {
|
||||
beforeEach(() => {
|
||||
PropTypes = require('ReactPropTypes');
|
||||
React = require('React');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactFragment = require('ReactFragment');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
ReactPropTypesSecret = require('ReactPropTypesSecret');
|
||||
resetWarningCache();
|
||||
});
|
||||
|
||||
describe('checkPropTypes', () => {
|
||||
it('does not return a value from a validator', () => {
|
||||
spyOn(console, 'error');
|
||||
const propTypes = {
|
||||
foo(props, propName, componentName) {
|
||||
return new Error('some error');
|
||||
},
|
||||
};
|
||||
const props = { foo: 'foo' };
|
||||
const returnValue = checkPropTypes(propTypes, props, 'prop', 'testComponent', null);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain('some error');
|
||||
expect(returnValue).toBe(undefined);
|
||||
});
|
||||
|
||||
it('does not throw if validator throws', () => {
|
||||
spyOn(console, 'error');
|
||||
const propTypes = {
|
||||
foo(props, propName, componentName) {
|
||||
throw new Error('some error');
|
||||
},
|
||||
};
|
||||
const props = { foo: 'foo' };
|
||||
const returnValue = checkPropTypes(propTypes, props, 'prop', 'testComponent', null);
|
||||
expect(console.error.calls.argsFor(0)[0]).toContain('some error');
|
||||
expect(returnValue).toBe(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Primitive Types', () => {
|
||||
@@ -409,8 +438,8 @@ describe('ReactPropTypes', () => {
|
||||
it('should be able to define a single child as label', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var instance = <Component label={<div />} />;
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component label={<div />} />, container);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
});
|
||||
@@ -418,8 +447,8 @@ describe('ReactPropTypes', () => {
|
||||
it('should warn when passing no label and isRequired is set', () => {
|
||||
spyOn(console, 'error');
|
||||
|
||||
var instance = <Component />;
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component />, container);
|
||||
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
});
|
||||
@@ -1037,8 +1066,8 @@ describe('ReactPropTypes', () => {
|
||||
}
|
||||
};
|
||||
|
||||
var instance = <Component num={5} />;
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component num={5} />, container);
|
||||
|
||||
expect(spy.calls.count()).toBe(1);
|
||||
expect(spy.calls.argsFor(0)[1]).toBe('num');
|
||||
@@ -1054,8 +1083,8 @@ describe('ReactPropTypes', () => {
|
||||
}
|
||||
};
|
||||
|
||||
var instance = <Component bla={5} />;
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component bla={5} />, container);
|
||||
|
||||
expect(spy.calls.count()).toBe(1);
|
||||
expect(spy.calls.argsFor(0)[1]).toBe('num');
|
||||
@@ -1078,8 +1107,8 @@ describe('ReactPropTypes', () => {
|
||||
}
|
||||
};
|
||||
|
||||
var instance = <Component num={6} />;
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component num={6} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(1);
|
||||
expect(
|
||||
console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)')
|
||||
@@ -1105,8 +1134,8 @@ describe('ReactPropTypes', () => {
|
||||
}
|
||||
};
|
||||
|
||||
var instance = <Component num={5} />;
|
||||
ReactTestUtils.renderIntoDocument(instance);
|
||||
var container = document.createElement('div');
|
||||
ReactDOM.render(<Component num={5} />, container);
|
||||
expectDev(console.error.calls.count()).toBe(0);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
describe('ReactPropTypesProduction', function() {
|
||||
var PropTypes;
|
||||
var React;
|
||||
var ReactPropTypeLocations;
|
||||
var ReactTestUtils;
|
||||
var oldProcess;
|
||||
|
||||
@@ -32,7 +31,6 @@ describe('ReactPropTypesProduction', function() {
|
||||
jest.resetModules();
|
||||
PropTypes = require('ReactPropTypes');
|
||||
React = require('React');
|
||||
ReactPropTypeLocations = require('ReactPropTypeLocations');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
});
|
||||
|
||||
@@ -48,7 +46,7 @@ describe('ReactPropTypesProduction', function() {
|
||||
props,
|
||||
'testProp',
|
||||
'testComponent',
|
||||
ReactPropTypeLocations.prop
|
||||
'prop'
|
||||
);
|
||||
}).toThrowError(
|
||||
'React.PropTypes type checking code is stripped in production.'
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* 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 checkPropTypes
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactPropTypesSecret = require('ReactPropTypesSecret');
|
||||
|
||||
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 {?Function} getStack Returns the component stack.
|
||||
* @private
|
||||
*/
|
||||
function checkPropTypes(typeSpecs, values, location, componentName, getStack) {
|
||||
if (__DEV__) {
|
||||
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',
|
||||
location,
|
||||
typeSpecName
|
||||
);
|
||||
error = typeSpecs[typeSpecName](values, typeSpecName, componentName, location, null, ReactPropTypesSecret);
|
||||
} 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',
|
||||
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 stack = getStack ? getStack() : '';
|
||||
|
||||
warning(
|
||||
false,
|
||||
'Failed %s type: %s%s',
|
||||
location,
|
||||
error.message,
|
||||
stack != null ? stack : '',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = checkPropTypes;
|
||||
@@ -34,6 +34,7 @@ const {
|
||||
|
||||
if (__DEV__) {
|
||||
var checkReactTypeSpec = require('checkReactTypeSpec');
|
||||
var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame');
|
||||
var warnedAboutMissingGetChildContext = {};
|
||||
}
|
||||
|
||||
@@ -91,7 +92,9 @@ exports.getMaskedContext = function(workInProgress : Fiber, unmaskedContext : Ob
|
||||
|
||||
if (__DEV__) {
|
||||
const name = getComponentName(workInProgress);
|
||||
checkReactTypeSpec(contextTypes, context, 'context', name, null, workInProgress);
|
||||
ReactDebugCurrentFrame.current = workInProgress;
|
||||
checkReactTypeSpec(contextTypes, context, 'context', name);
|
||||
ReactDebugCurrentFrame.current = null;
|
||||
}
|
||||
|
||||
// Cache unmasked context so we can avoid recreating masked context unless necessary.
|
||||
@@ -182,7 +185,9 @@ function processChildContext(fiber : Fiber, parentContext : Object, isReconcilin
|
||||
// assume anything about the given fiber. We won't pass it down if we aren't sure.
|
||||
// TODO: remove this hack when we delete unstable_renderSubtree in Fiber.
|
||||
const workInProgress = isReconciling ? fiber : null;
|
||||
checkReactTypeSpec(childContextTypes, childContext, 'childContext', name, null, workInProgress);
|
||||
ReactDebugCurrentFrame.current = workInProgress;
|
||||
checkReactTypeSpec(childContextTypes, childContext, 'child context', name);
|
||||
ReactDebugCurrentFrame.current = null;
|
||||
}
|
||||
return {...parentContext, ...childContext};
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ var ReactReconciler = require('ReactReconciler');
|
||||
|
||||
if (__DEV__) {
|
||||
var checkReactTypeSpec = require('checkReactTypeSpec');
|
||||
var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame');
|
||||
var warningAboutMissingGetChildContext = {};
|
||||
}
|
||||
|
||||
@@ -33,8 +34,6 @@ var shallowEqual = require('shallowEqual');
|
||||
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
|
||||
var warning = require('warning');
|
||||
|
||||
import type { ReactPropTypeLocations } from 'ReactPropTypeLocations';
|
||||
|
||||
function StatelessComponent(Component) {
|
||||
}
|
||||
StatelessComponent.prototype.render = function() {
|
||||
@@ -687,7 +686,7 @@ var ReactCompositeComponent = {
|
||||
this._checkContextTypes(
|
||||
Component.childContextTypes,
|
||||
childContext,
|
||||
'childContext'
|
||||
'child context'
|
||||
);
|
||||
}
|
||||
for (var name in childContext) {
|
||||
@@ -730,17 +729,17 @@ var ReactCompositeComponent = {
|
||||
_checkContextTypes: function(
|
||||
typeSpecs,
|
||||
values,
|
||||
location: ReactPropTypeLocations,
|
||||
location: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
ReactDebugCurrentFrame.current = this._debugID;
|
||||
checkReactTypeSpec(
|
||||
typeSpecs,
|
||||
values,
|
||||
location,
|
||||
this.getName(),
|
||||
null,
|
||||
this._debugID
|
||||
this.getName()
|
||||
);
|
||||
ReactDebugCurrentFrame.current = null;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @providesModule ReactPropTypeLocationNames
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { ReactPropTypeLocations } from 'ReactPropTypeLocations';
|
||||
|
||||
type NamesType = {[key: ReactPropTypeLocations]: string};
|
||||
|
||||
var ReactPropTypeLocationNames: NamesType = {};
|
||||
|
||||
if (__DEV__) {
|
||||
ReactPropTypeLocationNames = {
|
||||
prop: 'prop',
|
||||
context: 'context',
|
||||
childContext: 'child context',
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = ReactPropTypeLocationNames;
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @providesModule ReactPropTypeLocations
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
export type ReactPropTypeLocations =
|
||||
'prop' |
|
||||
'context' |
|
||||
'childContext';
|
||||
@@ -11,124 +11,22 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
|
||||
var ReactPropTypesSecret = require('ReactPropTypesSecret');
|
||||
var checkPropTypes = require('checkPropTypes');
|
||||
var { getStackAddendum } = require('ReactDebugCurrentFrame');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var warning = require('warning');
|
||||
|
||||
import type { ReactPropTypeLocations } from 'ReactPropTypeLocations';
|
||||
|
||||
var ReactComponentTreeHook;
|
||||
|
||||
if (
|
||||
typeof process !== 'undefined' &&
|
||||
process.env &&
|
||||
process.env.NODE_ENV === 'test'
|
||||
) {
|
||||
// Temporary hack.
|
||||
// Inline requires don't work well with Jest:
|
||||
// https://github.com/facebook/react/issues/7240
|
||||
// Remove the inline requires when we don't need them anymore:
|
||||
// https://github.com/facebook/react/pull/7178
|
||||
ReactComponentTreeHook = require('ReactComponentTreeHook');
|
||||
}
|
||||
|
||||
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} workInProgressOrDebugID The React component instance that is being type-checked
|
||||
* @private
|
||||
*/
|
||||
function checkReactTypeSpec(
|
||||
typeSpecs,
|
||||
values,
|
||||
location: ReactPropTypeLocations,
|
||||
componentName,
|
||||
element,
|
||||
// It is only safe to pass fiber if it is the work-in-progress version, and
|
||||
// only during reconciliation (begin and complete phase).
|
||||
workInProgressOrDebugID,
|
||||
location: string,
|
||||
componentName
|
||||
) {
|
||||
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, null, ReactPropTypesSecret);
|
||||
} 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 (__DEV__) {
|
||||
if (!ReactComponentTreeHook) {
|
||||
ReactComponentTreeHook = require('ReactComponentTreeHook');
|
||||
}
|
||||
if (workInProgressOrDebugID != null) {
|
||||
if (typeof workInProgressOrDebugID === 'number') {
|
||||
// DebugID from Stack.
|
||||
const debugID = workInProgressOrDebugID;
|
||||
componentStackInfo = ReactComponentTreeHook.getStackAddendumByID(debugID);
|
||||
} else if (typeof workInProgressOrDebugID.tag === 'number') {
|
||||
// This is a Fiber.
|
||||
// The stack will only be correct if this is a work in progress
|
||||
// version and we're calling it during reconciliation.
|
||||
const workInProgress = workInProgressOrDebugID;
|
||||
componentStackInfo = ReactComponentTreeHook.getStackAddendumByWorkInProgressFiber(workInProgress);
|
||||
}
|
||||
} else if (element !== null) {
|
||||
componentStackInfo = ReactComponentTreeHook.getCurrentStackAddendum(element);
|
||||
}
|
||||
}
|
||||
|
||||
warning(
|
||||
false,
|
||||
'Failed %s type: %s%s',
|
||||
location,
|
||||
error.message,
|
||||
componentStackInfo
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
checkPropTypes(
|
||||
typeSpecs,
|
||||
values,
|
||||
location,
|
||||
componentName,
|
||||
getStackAddendum
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = checkReactTypeSpec;
|
||||
|
||||
Reference in New Issue
Block a user