diff --git a/gulpfile.js b/gulpfile.js index 71fd70c9ca1..731c459a951 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -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( diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 5152468f42d..b24e98e993e 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -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 diff --git a/src/isomorphic/React.js b/src/isomorphic/React.js index cb2c5a8d9cb..1d2f1b6e8e2 100644 --- a/src/isomorphic/React.js +++ b/src/isomorphic/React.js @@ -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, diff --git a/src/isomorphic/classic/__tests__/ReactContextValidator-test.js b/src/isomorphic/classic/__tests__/ReactContextValidator-test.js index 08582a200dd..a5a04430aae 100644 --- a/src/isomorphic/classic/__tests__/ReactContextValidator-test.js +++ b/src/isomorphic/classic/__tests__/ReactContextValidator-test.js @@ -261,7 +261,7 @@ describe('ReactContextValidator', () => { ReactTestUtils.renderIntoDocument(); 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 **)' diff --git a/src/isomorphic/classic/class/ReactClass.js b/src/isomorphic/classic/class/ReactClass.js index bf2f425f8f3..a4efdd825a0 100644 --- a/src/isomorphic/classic/class/ReactClass.js +++ b/src/isomorphic/classic/class/ReactClass.js @@ -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 ); } diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js new file mode 100644 index 00000000000..f4efdd672b6 --- /dev/null +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -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; diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index b8146bfd86d..224ad49644a 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -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') { @@ -248,6 +250,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. @@ -261,6 +267,10 @@ var ReactElementValidator = { validatePropTypes(element); + if (__DEV__) { + ReactDebugCurrentFrame.element = null; + } + return element; }, @@ -301,10 +311,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; }, diff --git a/src/isomorphic/classic/types/ReactPropTypes.js b/src/isomorphic/classic/types/ReactPropTypes.js index dcd0dbc82a7..6f749170bc0 100644 --- a/src/isomorphic/classic/types/ReactPropTypes.js +++ b/src/isomorphic/classic/types/ReactPropTypes.js @@ -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\`.` ); } diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js index d482d3866df..8ed0aa07ec4 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js @@ -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 = } />; - ReactTestUtils.renderIntoDocument(instance); + var container = document.createElement('div'); + ReactDOM.render(} />, 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 = ; - ReactTestUtils.renderIntoDocument(instance); + var container = document.createElement('div'); + ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(1); }); @@ -1037,8 +1066,8 @@ describe('ReactPropTypes', () => { } }; - var instance = ; - ReactTestUtils.renderIntoDocument(instance); + var container = document.createElement('div'); + ReactDOM.render(, container); expect(spy.calls.count()).toBe(1); expect(spy.calls.argsFor(0)[1]).toBe('num'); @@ -1054,8 +1083,8 @@ describe('ReactPropTypes', () => { } }; - var instance = ; - ReactTestUtils.renderIntoDocument(instance); + var container = document.createElement('div'); + ReactDOM.render(, container); expect(spy.calls.count()).toBe(1); expect(spy.calls.argsFor(0)[1]).toBe('num'); @@ -1078,8 +1107,8 @@ describe('ReactPropTypes', () => { } }; - var instance = ; - ReactTestUtils.renderIntoDocument(instance); + var container = document.createElement('div'); + ReactDOM.render(, 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 = ; - ReactTestUtils.renderIntoDocument(instance); + var container = document.createElement('div'); + ReactDOM.render(, container); expectDev(console.error.calls.count()).toBe(0); } ); diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js index 076c1639349..8e6a868c9f9 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypesProduction-test.js @@ -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.' diff --git a/src/isomorphic/classic/types/checkPropTypes.js b/src/isomorphic/classic/types/checkPropTypes.js new file mode 100644 index 00000000000..23ebacaf09a --- /dev/null +++ b/src/isomorphic/classic/types/checkPropTypes.js @@ -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; diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index 8b7383458b2..836bc79eb42 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -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}; } diff --git a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js index 9a572a8f6f0..19ce8079f2c 100644 --- a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js @@ -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; } }, diff --git a/src/shared/types/ReactPropTypeLocationNames.js b/src/shared/types/ReactPropTypeLocationNames.js deleted file mode 100644 index 1b36b04cc89..00000000000 --- a/src/shared/types/ReactPropTypeLocationNames.js +++ /dev/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; diff --git a/src/shared/types/ReactPropTypeLocations.js b/src/shared/types/ReactPropTypeLocations.js deleted file mode 100644 index a2cb905e191..00000000000 --- a/src/shared/types/ReactPropTypeLocations.js +++ /dev/null @@ -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'; diff --git a/src/shared/types/checkReactTypeSpec.js b/src/shared/types/checkReactTypeSpec.js index e274c3e9f58..c7ed4d8167e 100644 --- a/src/shared/types/checkReactTypeSpec.js +++ b/src/shared/types/checkReactTypeSpec.js @@ -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;