diff --git a/scripts/fiber/tests-passing-except-dev.txt b/scripts/fiber/tests-passing-except-dev.txt index e95c7f7c7741e..457b16d592062 100644 --- a/scripts/fiber/tests-passing-except-dev.txt +++ b/scripts/fiber/tests-passing-except-dev.txt @@ -1,5 +1,2 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js -* gives source code refs for unknown prop warning (ssr) -* gives source code refs for unknown prop warning for exact elements (ssr) * gives source code refs for unknown prop warning for exact elements in composition (ssr) -* should suggest property name if available (ssr) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 7006c289b0658..10ca5eaaffd6c 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -853,10 +853,13 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js * should warn about props that are no longer supported * should warn about props that are no longer supported (ssr) * gives source code refs for unknown prop warning +* gives source code refs for unknown prop warning (ssr) * gives source code refs for unknown prop warning for update render * gives source code refs for unknown prop warning for exact elements +* gives source code refs for unknown prop warning for exact elements (ssr) * gives source code refs for unknown prop warning for exact elements in composition * should suggest property name if available +* should suggest property name if available (ssr) * renders innerHTML and preserves whitespace * render and then updates innerHTML and preserves whitespace diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 580a5571f4bb0..48e314a551aef 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -381,7 +381,11 @@ const bundles = [ label: 'shallow-renderer', manglePropertiesOnProd: false, name: 'react-test-renderer/shallow', - paths: ['src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js'], + paths: [ + 'src/renderers/shared/**/*.js', + 'src/renderers/testing/**/*.js', + 'src/shared/**/*.js', + ], }, /******* React Noop Renderer (used only for fixtures/fiber-debugger) *******/ diff --git a/src/isomorphic/children/traverseAllChildren.js b/src/isomorphic/children/traverseAllChildren.js index 5aa0acf31aa48..3b763c8ce1e67 100644 --- a/src/isomorphic/children/traverseAllChildren.js +++ b/src/isomorphic/children/traverseAllChildren.js @@ -24,7 +24,7 @@ var REACT_ELEMENT_TYPE = 0xeac7; if (__DEV__) { - var {getCurrentStackAddendum} = require('ReactComponentTreeHook'); + var {getStackAddendum} = require('ReactDebugCurrentFrame'); } var SEPARATOR = '.'; @@ -171,7 +171,7 @@ function traverseAllChildrenImpl( 'Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.%s', - getCurrentStackAddendum(), + getStackAddendum(), ); didWarnAboutMaps = true; } @@ -196,7 +196,7 @@ function traverseAllChildrenImpl( addendum = ' If you meant to render a collection of children, use an array ' + 'instead.' + - getCurrentStackAddendum(); + getStackAddendum(); } var childrenString = '' + children; invariant( diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index 38dcf3c73d59d..fad26ea155693 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -12,46 +12,18 @@ 'use strict'; -import type {Fiber} from 'ReactFiber'; -import type {DebugID} from 'ReactInstanceType'; - const ReactDebugCurrentFrame = {}; if (__DEV__) { - var { - getStackAddendumByID, - getCurrentStackAddendum, - } = require('ReactComponentTreeHook'); - var { - getStackAddendumByWorkInProgressFiber, - } = require('ReactFiberComponentTreeHook'); - // Component that is being worked on - ReactDebugCurrentFrame.current = (null: Fiber | DebugID | null); - - // Element that is being cloned or created - ReactDebugCurrentFrame.element = (null: *); + ReactDebugCurrentFrame.getCurrentStack = (null: null | (() => string | 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); + const impl = ReactDebugCurrentFrame.getCurrentStack; + if (impl) { + return impl(); } - return stack; + return null; }; } diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index 6fe05d889e5f6..8aa8b240b4ec5 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -21,14 +21,42 @@ var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactElement = require('ReactElement'); -var getComponentName = require('getComponentName'); - if (__DEV__) { var checkPropTypes = require('prop-types/checkPropTypes'); var lowPriorityWarning = require('lowPriorityWarning'); var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame'); var warning = require('fbjs/lib/warning'); - var {getCurrentStackAddendum} = require('ReactComponentTreeHook'); + var describeComponentFrame = require('describeComponentFrame'); + var getComponentName = require('getComponentName'); + + var currentlyValidatingElement = null; + + var getDisplayName = function(element: ?ReactElement): string { + if (element == null) { + return '#empty'; + } else if (typeof element === 'string' || typeof element === 'number') { + return '#text'; + } else if (typeof element.type === 'string') { + return element.type; + } else { + return element.type.displayName || element.type.name || 'Unknown'; + } + }; + + var getStackAddendum = function(): string { + var stack = ''; + if (currentlyValidatingElement) { + var name = getDisplayName(currentlyValidatingElement); + var owner = currentlyValidatingElement._owner; + stack += describeComponentFrame( + name, + currentlyValidatingElement._source, + owner && getComponentName(owner), + ); + } + stack += ReactDebugCurrentFrame.getStackAddendum() || ''; + return stack; + }; } var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; @@ -115,14 +143,16 @@ function validateExplicitKey(element, parentType) { childOwner = ` It was passed a child from ${getComponentName(element._owner)}.`; } + currentlyValidatingElement = element; warning( false, 'Each child in an array or iterator should have a unique "key" prop.' + '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, - getCurrentStackAddendum(element), + getStackAddendum(), ); + currentlyValidatingElement = null; } /** @@ -193,13 +223,9 @@ function validatePropTypes(element) { : componentClass.propTypes; if (propTypes) { - checkPropTypes( - propTypes, - element.props, - 'prop', - name, - ReactDebugCurrentFrame.getStackAddendum, - ); + currentlyValidatingElement = element; + checkPropTypes(propTypes, element.props, 'prop', name, getStackAddendum); + currentlyValidatingElement = null; } if (typeof componentClass.getDefaultProps === 'function') { warning( @@ -235,7 +261,7 @@ var ReactElementValidator = { info += getDeclarationErrorAddendum(); } - info += getCurrentStackAddendum(); + info += ReactDebugCurrentFrame.getStackAddendum() || ''; warning( false, @@ -255,10 +281,6 @@ 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. @@ -272,10 +294,6 @@ var ReactElementValidator = { validatePropTypes(element); - if (__DEV__) { - ReactDebugCurrentFrame.element = null; - } - return element; }, @@ -306,16 +324,10 @@ 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/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js index 41799f7cb6f99..731d335bfa0c6 100644 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ b/src/isomorphic/hooks/ReactComponentTreeHook.js @@ -13,17 +13,12 @@ 'use strict'; var ReactCurrentOwner = require('ReactCurrentOwner'); -var { - getStackAddendumByWorkInProgressFiber, - describeComponentFrame, -} = require('ReactFiberComponentTreeHook'); var invariant = require('fbjs/lib/invariant'); var warning = require('fbjs/lib/warning'); -var getComponentName = require('getComponentName'); +var describeComponentFrame = require('describeComponentFrame'); import type {ReactElement, Source} from 'ReactElementType'; import type {DebugID} from 'ReactInstanceType'; -import type {Fiber} from 'ReactFiber'; function isNative(fn) { // Based on isNative() from Lodash @@ -313,26 +308,15 @@ var ReactComponentTreeHook = { return item ? item.isMounted : false; }, - getCurrentStackAddendum(topElement: ?ReactElement): string { + getCurrentStackAddendum(): string { var info = ''; - if (topElement) { - var name = getDisplayName(topElement); - var owner = topElement._owner; - info += describeComponentFrame( - name, - topElement._source, - owner && getComponentName(owner), - ); - } - var currentOwner = ReactCurrentOwner.current; if (currentOwner) { - if (typeof currentOwner.tag === 'number') { - const workInProgress = ((currentOwner: any): Fiber); - // Safe because if current owner exists, we are reconciling, - // and it is guaranteed to be the work-in-progress version. - info += getStackAddendumByWorkInProgressFiber(workInProgress); - } else if (typeof currentOwner._debugID === 'number') { + invariant( + typeof currentOwner.tag !== 'number', + 'Fiber owners should not show up in Stack stack traces.', + ); + if (typeof currentOwner._debugID === 'number') { info += ReactComponentTreeHook.getStackAddendumByID( currentOwner._debugID, ); diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.js b/src/renderers/__tests__/ReactComponentTreeHook-test.js index 5f0a5c904d166..c7ca3644ecd8c 100644 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.js +++ b/src/renderers/__tests__/ReactComponentTreeHook-test.js @@ -20,6 +20,7 @@ describe('ReactComponentTreeHook', () => { var ReactDOMServer; var ReactInstanceMap; var ReactComponentTreeHook; + var ReactDebugCurrentFiber; var ReactComponentTreeTestUtils; beforeEach(() => { @@ -29,6 +30,7 @@ describe('ReactComponentTreeHook', () => { ReactDOM = require('react-dom'); ReactDOMServer = require('react-dom/server'); ReactInstanceMap = require('ReactInstanceMap'); + ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); ReactComponentTreeHook = require('ReactComponentTreeHook'); ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); }); @@ -37,7 +39,9 @@ describe('ReactComponentTreeHook', () => { describe('stack addenda', () => { it('gets created', () => { function getAddendum(element) { - var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element); + var addendum = ReactDOMFeatureFlags.useFiber + ? ReactDebugCurrentFiber.getCurrentFiberStackAddendum() || '' + : ReactComponentTreeHook.getCurrentStackAddendum(); return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); } @@ -47,23 +51,23 @@ describe('ReactComponentTreeHook', () => { Object.defineProperty(Anon, 'name', { value: null, }); - function Orange() { - return null; - } + // function Orange() { + // return null; + // } expectDev(getAddendum()).toBe(''); - expectDev(getAddendum(
)).toBe('\n in div (at **)'); - expectDev(getAddendum()).toBe('\n in Unknown (at **)'); - expectDev(getAddendum()).toBe('\n in Orange (at **)'); - expectDev(getAddendum(React.createElement(Orange))).toBe( - '\n in Orange', - ); + // expectDev(getAddendum(
)).toBe('\n in div (at **)'); + // expectDev(getAddendum()).toBe('\n in Unknown (at **)'); + // expectDev(getAddendum()).toBe('\n in Orange (at **)'); + // expectDev(getAddendum(React.createElement(Orange))).toBe( + // '\n in Orange', + // ); var renders = 0; - var rOwnedByQ; + //var rOwnedByQ; function Q() { - return (rOwnedByQ = React.createElement(R)); + return /*rOwnedByQ =*/ React.createElement(R); } function R() { return
; @@ -82,15 +86,15 @@ describe('ReactComponentTreeHook', () => { '\n in Q (at **)', ); expectDev(getAddendum()).toBe( - '\n in span (at **)' + - '\n in S (at **)' + + // '\n in span (at **)' + + '\n in S (at **)' + '\n in div (at **)' + '\n in R (created by Q)' + '\n in Q (at **)', ); expectDev(getAddendum(React.createElement('span'))).toBe( - '\n in span (created by S)' + - '\n in S (at **)' + + // '\n in span (created by S)' + + '\n in S (at **)' + '\n in div (at **)' + '\n in R (created by Q)' + '\n in Q (at **)', @@ -103,7 +107,7 @@ describe('ReactComponentTreeHook', () => { expectDev(renders).toBe(2); // Make sure owner is fetched for the top element too. - expectDev(getAddendum(rOwnedByQ)).toBe('\n in R (created by Q)'); + // expectDev(getAddendum(rOwnedByQ)).toBe('\n in R (created by Q)'); }); // These are features and regression tests that only affect diff --git a/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js b/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js index 7eaf0bc1ac3b5..686089e21cd06 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMInvalidARIAHook.js @@ -12,7 +12,6 @@ 'use strict'; var DOMProperty = require('DOMProperty'); -var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var warning = require('fbjs/lib/warning'); @@ -20,7 +19,10 @@ var warnedProperties = {}; var rARIA = new RegExp('^(aria)-[' + DOMProperty.ATTRIBUTE_NAME_CHAR + ']*$'); if (__DEV__) { - var {ReactComponentTreeHook} = require('ReactGlobalSharedState'); + var { + ReactComponentTreeHook, + ReactDebugCurrentFrame, + } = require('ReactGlobalSharedState'); var {getStackAddendumByID} = ReactComponentTreeHook; } @@ -29,8 +31,9 @@ function getStackAddendum(debugID) { // This can only happen on Stack return getStackAddendumByID(debugID); } else { - // This can only happen on Fiber - return ReactDebugCurrentFiber.getCurrentFiberStackAddendum(); + // This can only happen on Fiber / Server + var stack = ReactDebugCurrentFrame.getStackAddendum(); + return stack != null ? stack : ''; } } diff --git a/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js b/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js index 6b722b48641af..d0e27d212e14c 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMNullInputValuePropHook.js @@ -11,11 +11,13 @@ 'use strict'; -var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); var warning = require('fbjs/lib/warning'); if (__DEV__) { - var {ReactComponentTreeHook} = require('ReactGlobalSharedState'); + var { + ReactComponentTreeHook, + ReactDebugCurrentFrame, + } = require('ReactGlobalSharedState'); var {getStackAddendumByID} = ReactComponentTreeHook; } @@ -26,8 +28,9 @@ function getStackAddendum(debugID) { // This can only happen on Stack return getStackAddendumByID(debugID); } else { - // This can only happen on Fiber - return ReactDebugCurrentFiber.getCurrentFiberStackAddendum(); + // This can only happen on Fiber / Server + var stack = ReactDebugCurrentFrame.getStackAddendum(); + return stack != null ? stack : ''; } } diff --git a/src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js b/src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js index 083ae3019e465..58096bfea22dd 100644 --- a/src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js +++ b/src/renderers/dom/shared/hooks/ReactDOMUnknownPropertyHook.js @@ -13,18 +13,24 @@ var DOMProperty = require('DOMProperty'); var EventPluginRegistry = require('EventPluginRegistry'); -var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); -var {ReactComponentTreeHook} = require('ReactGlobalSharedState'); var warning = require('fbjs/lib/warning'); +if (__DEV__) { + var { + ReactComponentTreeHook, + ReactDebugCurrentFrame, + } = require('ReactGlobalSharedState'); + var {getStackAddendumByID} = ReactComponentTreeHook; +} + function getStackAddendum(debugID) { if (debugID != null) { // This can only happen on Stack - return ReactComponentTreeHook.getStackAddendumByID(debugID); + return getStackAddendumByID(debugID); } else { // This can only happen on Fiber / Server - var stack = ReactDebugCurrentFiber.getCurrentFiberStackAddendum(); + var stack = ReactDebugCurrentFrame.getStackAddendum(); return stack != null ? stack : ''; } } diff --git a/src/renderers/shared/fiber/ReactDebugCurrentFiber.js b/src/renderers/shared/fiber/ReactDebugCurrentFiber.js index 8e6f9c727c66b..dd36659201f24 100644 --- a/src/renderers/shared/fiber/ReactDebugCurrentFiber.js +++ b/src/renderers/shared/fiber/ReactDebugCurrentFiber.js @@ -16,6 +16,8 @@ import type {Fiber} from 'ReactFiber'; type LifeCyclePhase = 'render' | 'getChildContext'; +var {ReactDebugCurrentFrame} = require('ReactGlobalSharedState'); + if (__DEV__) { var getComponentName = require('getComponentName'); var { @@ -49,10 +51,23 @@ function getCurrentFiberStackAddendum(): string | null { return null; } +function resetCurrentFiber() { + ReactDebugCurrentFrame.getCurrentStack = null; + ReactDebugCurrentFiber.current = null; + ReactDebugCurrentFiber.phase = null; +} + +function setCurrentFiber(fiber: Fiber | null, phase: LifeCyclePhase | null) { + ReactDebugCurrentFrame.getCurrentStack = getCurrentFiberStackAddendum; + ReactDebugCurrentFiber.current = fiber; + ReactDebugCurrentFiber.phase = phase; +} + var ReactDebugCurrentFiber = { current: (null: Fiber | null), phase: (null: LifeCyclePhase | null), - + resetCurrentFiber, + setCurrentFiber, getCurrentFiberOwnerName, getCurrentFiberStackAddendum, }; diff --git a/src/renderers/shared/fiber/ReactFiberBeginWork.js b/src/renderers/shared/fiber/ReactFiberBeginWork.js index 32f91e7d178c0..a2883cf7ddb46 100644 --- a/src/renderers/shared/fiber/ReactFiberBeginWork.js +++ b/src/renderers/shared/fiber/ReactFiberBeginWork.js @@ -217,9 +217,9 @@ module.exports = function( if (__DEV__) { ReactCurrentOwner.current = workInProgress; - ReactDebugCurrentFiber.phase = 'render'; + ReactDebugCurrentFiber.setCurrentFiber(workInProgress, 'render'); nextChildren = fn(nextProps, context); - ReactDebugCurrentFiber.phase = null; + ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); } else { nextChildren = fn(nextProps, context); } @@ -286,9 +286,9 @@ module.exports = function( ReactCurrentOwner.current = workInProgress; let nextChildren; if (__DEV__) { - ReactDebugCurrentFiber.phase = 'render'; + ReactDebugCurrentFiber.setCurrentFiber(workInProgress, 'render'); nextChildren = instance.render(); - ReactDebugCurrentFiber.phase = null; + ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); } else { nextChildren = instance.render(); } @@ -725,7 +725,7 @@ module.exports = function( } if (__DEV__) { - ReactDebugCurrentFiber.current = workInProgress; + ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); } switch (workInProgress.tag) { diff --git a/src/renderers/shared/fiber/ReactFiberCompleteWork.js b/src/renderers/shared/fiber/ReactFiberCompleteWork.js index 74ddba49a777e..c733f036933da 100644 --- a/src/renderers/shared/fiber/ReactFiberCompleteWork.js +++ b/src/renderers/shared/fiber/ReactFiberCompleteWork.js @@ -187,7 +187,7 @@ module.exports = function( renderPriority: PriorityLevel, ): Fiber | null { if (__DEV__) { - ReactDebugCurrentFiber.current = workInProgress; + ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); } // Get the latest props. diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index 8a2c606409ceb..6ec37396f0841 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -26,7 +26,6 @@ const {createCursor, pop, push} = require('ReactFiberStack'); if (__DEV__) { var ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); - var {ReactDebugCurrentFrame} = require('ReactGlobalSharedState'); var {startPhaseTimer, stopPhaseTimer} = require('ReactDebugFiberPerf'); var warnedAboutMissingGetChildContext = {}; } @@ -92,15 +91,15 @@ exports.getMaskedContext = function( if (__DEV__) { const name = getComponentName(workInProgress) || 'Unknown'; - ReactDebugCurrentFrame.current = workInProgress; + ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); checkPropTypes( contextTypes, context, 'context', name, - ReactDebugCurrentFrame.getStackAddendum, + ReactDebugCurrentFiber.getCurrentFiberStackAddendum, ); - ReactDebugCurrentFrame.current = null; + ReactDebugCurrentFiber.resetCurrentFiber(); } // Cache unmasked context so we can avoid recreating masked context unless necessary. @@ -181,11 +180,11 @@ function processChildContext( let childContext; if (__DEV__) { - ReactDebugCurrentFiber.phase = 'getChildContext'; + ReactDebugCurrentFiber.setCurrentFiber(fiber, 'getChildContext'); startPhaseTimer(fiber, 'getChildContext'); childContext = instance.getChildContext(); stopPhaseTimer(); - ReactDebugCurrentFiber.phase = null; + ReactDebugCurrentFiber.resetCurrentFiber(); } else { childContext = instance.getChildContext(); } @@ -205,15 +204,15 @@ function processChildContext( // 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; - ReactDebugCurrentFrame.current = workInProgress; + ReactDebugCurrentFiber.setCurrentFiber(workInProgress, null); checkPropTypes( childContextTypes, childContext, 'child context', name, - ReactDebugCurrentFrame.getStackAddendum, + ReactDebugCurrentFiber.getCurrentFiberStackAddendum, ); - ReactDebugCurrentFrame.current = null; + ReactDebugCurrentFiber.resetCurrentFiber(); } return {...parentContext, ...childContext}; diff --git a/src/renderers/shared/fiber/ReactFiberScheduler.js b/src/renderers/shared/fiber/ReactFiberScheduler.js index 998613be999e2..59799225b6b90 100644 --- a/src/renderers/shared/fiber/ReactFiberScheduler.js +++ b/src/renderers/shared/fiber/ReactFiberScheduler.js @@ -322,7 +322,7 @@ module.exports = function( function commitAllHostEffects() { while (nextEffect !== null) { if (__DEV__) { - ReactDebugCurrentFiber.current = nextEffect; + ReactDebugCurrentFiber.setCurrentFiber(nextEffect, null); recordEffect(); } @@ -383,7 +383,7 @@ module.exports = function( } if (__DEV__) { - ReactDebugCurrentFiber.current = null; + ReactDebugCurrentFiber.resetCurrentFiber(); } } @@ -711,7 +711,7 @@ module.exports = function( ReactCurrentOwner.current = null; if (__DEV__) { - ReactDebugCurrentFiber.current = null; + ReactDebugCurrentFiber.resetCurrentFiber(); } return next; @@ -740,7 +740,7 @@ module.exports = function( ReactCurrentOwner.current = null; if (__DEV__) { - ReactDebugCurrentFiber.current = null; + ReactDebugCurrentFiber.resetCurrentFiber(); } return next; @@ -1024,8 +1024,7 @@ module.exports = function( // It is no longer valid because we exited the user code. ReactCurrentOwner.current = null; if (__DEV__) { - ReactDebugCurrentFiber.current = null; - ReactDebugCurrentFiber.phase = null; + ReactDebugCurrentFiber.resetCurrentFiber(); } // It is no longer valid because this unit of work failed. nextUnitOfWork = null; diff --git a/src/renderers/shared/server/ReactPartialRenderer.js b/src/renderers/shared/server/ReactPartialRenderer.js index 0aded2bbf07f0..7e4c36ab36cdc 100644 --- a/src/renderers/shared/server/ReactPartialRenderer.js +++ b/src/renderers/shared/server/ReactPartialRenderer.js @@ -42,6 +42,49 @@ if (__DEV__) { validateInputPropertes(type, props); validateUnknownPropertes(type, props); }; + + var describeComponentFrame = require('describeComponentFrame'); + var describeStackFrame = function( + frame: { + tag?: string, + children: Array<*>, + childIndex: number, + }, + ): string { + var element = frame.children[frame.childIndex - 1]; + if (!element) { + return ''; + } + var source = element._source; + var type = element.type; + var name = typeof type === 'string' + ? type + : typeof type === 'function' ? type.displayName || type.name : null; + var ownerName = null; + return describeComponentFrame(name, source, ownerName); + }; + + var {ReactDebugCurrentFrame} = require('ReactGlobalSharedState'); + var currentDebugStack = null; + var setCurrentDebugStack = function(stack) { + currentDebugStack = stack; + ReactDebugCurrentFrame.getCurrentStack = getStackAddendum; + }; + var resetCurrentDebugStack = function() { + currentDebugStack = null; + ReactDebugCurrentFrame.getCurrentStack = null; + }; + var getStackAddendum = function(): null | string { + if (currentDebugStack === null) { + return null; + } + let stack = ''; + let debugStack = currentDebugStack; + for (let i = debugStack.length - 1; i >= 0; i--) { + stack += describeStackFrame(debugStack[i]); + } + return stack; + }; } var didWarnDefaultInputValue = false; @@ -145,13 +188,7 @@ function maskContext(type, context) { function checkContextTypes(typeSpecs, values, location: string) { if (__DEV__) { - checkPropTypes( - typeSpecs, - values, - location, - 'Component', - () => '', // ReactDebugCurrentFrame.getStackAddendum, - ); + checkPropTypes(typeSpecs, values, location, 'Component', getStackAddendum); } } @@ -374,7 +411,13 @@ class ReactDOMServerRenderer { continue; } var child = frame.children[frame.childIndex++]; + if (__DEV__) { + setCurrentDebugStack(this.stack); + } out += this.render(child, frame.context); + if (__DEV__) { + resetCurrentDebugStack(); + } } return out; } diff --git a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js index 434f67a4fbef3..527219740b900 100644 --- a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js @@ -23,6 +23,7 @@ var {ReactCurrentOwner} = require('ReactGlobalSharedState'); if (__DEV__) { var {ReactDebugCurrentFrame} = require('ReactGlobalSharedState'); + var ReactDebugCurrentStack = require('ReactDebugCurrentStack'); var warningAboutMissingGetChildContext = {}; } @@ -403,6 +404,8 @@ var ReactCompositeComponent = { ) { if (__DEV__) { ReactCurrentOwner.current = this; + ReactDebugCurrentFrame.getCurrentStack = + ReactDebugCurrentStack.getStackAddendum; try { return this._constructComponentWithoutOwner( doConstruct, @@ -412,6 +415,7 @@ var ReactCompositeComponent = { ); } finally { ReactCurrentOwner.current = null; + ReactDebugCurrentFrame.getCurrentStack = null; } } else { return this._constructComponentWithoutOwner( @@ -746,15 +750,15 @@ var ReactCompositeComponent = { */ _checkContextTypes: function(typeSpecs, values, location: string) { if (__DEV__) { - ReactDebugCurrentFrame.current = this._debugID; + ReactDebugCurrentStack.current = this._debugID; checkPropTypes( typeSpecs, values, location, this.getName(), - ReactDebugCurrentFrame.getStackAddendum, + ReactDebugCurrentStack.getStackAddendum, ); - ReactDebugCurrentFrame.current = null; + ReactDebugCurrentStack.current = null; } }, @@ -1246,10 +1250,17 @@ var ReactCompositeComponent = { this._compositeType !== ReactCompositeComponentTypes.StatelessFunctional ) { ReactCurrentOwner.current = this; + if (__DEV__) { + ReactDebugCurrentFrame.getCurrentStack = + ReactDebugCurrentStack.getStackAddendum; + } try { renderedElement = this._renderValidatedComponentWithoutOwnerOrContext(); } finally { ReactCurrentOwner.current = null; + if (__DEV__) { + ReactDebugCurrentFrame.getCurrentStack = null; + } } } else { renderedElement = this._renderValidatedComponentWithoutOwnerOrContext(); diff --git a/src/renderers/shared/stack/reconciler/ReactDebugCurrentStack.js b/src/renderers/shared/stack/reconciler/ReactDebugCurrentStack.js new file mode 100644 index 0000000000000..c7d4c7a312ada --- /dev/null +++ b/src/renderers/shared/stack/reconciler/ReactDebugCurrentStack.js @@ -0,0 +1,38 @@ +/** + * 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 ReactDebugCurrentStack + * @flow + */ + +'use strict'; + +import type {DebugID} from 'ReactInstanceType'; + +const ReactDebugCurrentStack = {}; + +if (__DEV__) { + var {ReactComponentTreeHook} = require('ReactGlobalSharedState'); + var {getStackAddendumByID, getCurrentStackAddendum} = ReactComponentTreeHook; + + // Component that is being worked on + ReactDebugCurrentStack.current = (null: DebugID | null); + + ReactDebugCurrentStack.getStackAddendum = function(): string | null { + let stack = null; + const current = ReactDebugCurrentStack.current; + if (current !== null) { + stack = getStackAddendumByID(current); + } else { + stack = getCurrentStackAddendum(); + } + return stack; + }; +} + +module.exports = ReactDebugCurrentStack; diff --git a/src/renderers/shared/stack/reconciler/ReactMultiChild.js b/src/renderers/shared/stack/reconciler/ReactMultiChild.js index 8bde4e26ffffa..be03aeb44c57f 100644 --- a/src/renderers/shared/stack/reconciler/ReactMultiChild.js +++ b/src/renderers/shared/stack/reconciler/ReactMultiChild.js @@ -18,6 +18,10 @@ var ReactInstrumentation = require('ReactInstrumentation'); var ReactReconciler = require('ReactReconciler'); var ReactChildReconciler = require('ReactChildReconciler'); var {ReactCurrentOwner} = require('ReactGlobalSharedState'); +if (__DEV__) { + var {ReactDebugCurrentFrame} = require('ReactGlobalSharedState'); + var ReactDebugCurrentStack = require('ReactDebugCurrentStack'); +} var emptyFunction = require('fbjs/lib/emptyFunction'); var flattenStackChildren = require('flattenStackChildren'); @@ -179,6 +183,8 @@ var ReactMultiChild = { if (this._currentElement) { try { ReactCurrentOwner.current = this._currentElement._owner; + ReactDebugCurrentFrame.getCurrentStack = + ReactDebugCurrentStack.getStackAddendum; return ReactChildReconciler.instantiateChildren( nestedChildren, transaction, @@ -187,6 +193,7 @@ var ReactMultiChild = { ); } finally { ReactCurrentOwner.current = null; + ReactDebugCurrentFrame.getCurrentStack = null; } } } @@ -212,12 +219,15 @@ var ReactMultiChild = { if (this._currentElement) { try { ReactCurrentOwner.current = this._currentElement._owner; + ReactDebugCurrentFrame.getCurrentStack = + ReactDebugCurrentStack.getStackAddendum; nextChildren = flattenStackChildren( nextNestedChildrenElements, selfDebugID, ); } finally { ReactCurrentOwner.current = null; + ReactDebugCurrentFrame.getCurrentStack = null; } ReactChildReconciler.updateChildren( prevChildren, diff --git a/src/renderers/testing/ReactShallowRendererEntry.js b/src/renderers/testing/ReactShallowRendererEntry.js index ac610f1caf056..99682cf3414a0 100644 --- a/src/renderers/testing/ReactShallowRendererEntry.js +++ b/src/renderers/testing/ReactShallowRendererEntry.js @@ -18,7 +18,38 @@ const React = require('react'); const emptyObject = require('fbjs/lib/emptyObject'); const invariant = require('fbjs/lib/invariant'); -const {ReactDebugCurrentFrame} = require('ReactGlobalSharedState'); +if (__DEV__) { + var describeComponentFrame = require('describeComponentFrame'); + var getComponentName = require('getComponentName'); + + var currentlyValidatingElement = null; + + var getDisplayName = function(element: ?ReactElement): string { + if (element == null) { + return '#empty'; + } else if (typeof element === 'string' || typeof element === 'number') { + return '#text'; + } else if (typeof element.type === 'string') { + return element.type; + } else { + return element.type.displayName || element.type.name || 'Unknown'; + } + }; + + var getStackAddendum = function(): string { + var stack = ''; + if (currentlyValidatingElement) { + var name = getDisplayName(currentlyValidatingElement); + var owner = currentlyValidatingElement._owner; + stack += describeComponentFrame( + name, + currentlyValidatingElement._source, + owner && getComponentName(owner), + ); + } + return stack; + }; +} class ReactShallowRenderer { static createRenderer = function() { @@ -79,17 +110,17 @@ class ReactShallowRenderer { ); if (element.type.hasOwnProperty('contextTypes')) { - ReactDebugCurrentFrame.element = element; + currentlyValidatingElement = element; checkPropTypes( element.type.contextTypes, context, 'context', getName(element.type, this._instance), - ReactDebugCurrentFrame.getStackAddendum, + getStackAddendum, ); - ReactDebugCurrentFrame.element = null; + currentlyValidatingElement = null; } this._mountClassComponent(element.props, context); diff --git a/src/shared/ReactFiberComponentTreeHook.js b/src/shared/ReactFiberComponentTreeHook.js index d965c718add79..5f2163f6f0ae9 100644 --- a/src/shared/ReactFiberComponentTreeHook.js +++ b/src/shared/ReactFiberComponentTreeHook.js @@ -19,24 +19,11 @@ var { ClassComponent, HostComponent, } = ReactTypeOfWork; +var describeComponentFrame = require('describeComponentFrame'); var getComponentName = require('getComponentName'); import type {Fiber} from 'ReactFiber'; -function describeComponentFrame(name, source: any, ownerName) { - return ( - '\n in ' + - (name || 'Unknown') + - (source - ? ' (at ' + - source.fileName.replace(/^.*[\\\/]/, '') + - ':' + - source.lineNumber + - ')' - : ownerName ? ' (created by ' + ownerName + ')' : '') - ); -} - function describeFiber(fiber: Fiber): string { switch (fiber.tag) { case IndeterminateComponent: @@ -72,5 +59,4 @@ function getStackAddendumByWorkInProgressFiber(workInProgress: Fiber): string { module.exports = { getStackAddendumByWorkInProgressFiber, - describeComponentFrame, }; diff --git a/src/shared/describeComponentFrame.js b/src/shared/describeComponentFrame.js new file mode 100644 index 0000000000000..67e8fbe3b92eb --- /dev/null +++ b/src/shared/describeComponentFrame.js @@ -0,0 +1,31 @@ +/** + * Copyright 2016-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 describeComponentFrame + */ + +'use strict'; + +module.exports = function( + name: null | string, + source: any, + ownerName: null | string, +) { + return ( + '\n in ' + + (name || 'Unknown') + + (source + ? ' (at ' + + source.fileName.replace(/^.*[\\\/]/, '') + + ':' + + source.lineNumber + + ')' + : ownerName ? ' (created by ' + ownerName + ')' : '') + ); +};