Skip to content

Commit 3b5e3b5

Browse files
authored
Refactor Debug Frames to Enable Renderers to Provide Custom Logic (#10105)
* Extract the top element frame from ReactDebugCurrentFrame This is part of a larger refactor to decouple stack addendums. All renderers have their own way of getting the stack of the currently executing components. There is one special case in Element Validator that adds an additional line for the element being validated. This commit moves that special case in into the validator. There is another case where it looked like this was used in shallow renderer but this is actually something different. It is part of the component stack. It just happens to be that shallow renderer has a simpler implementation of the component stack that just happens to be a single element. This will let us decouple the implementation to get a stack from ReactDebugCurrentFrame and put that in each renderer. * Stop using ReactComponentTreeHook for Fiber Currently we fall back to ReactCurrentOwner in ReactComponentTreeHook for stack addendums. We shouldn't need to because we should use ReactDebugCurrrentFiber. Ensure we always set both ReactDebugCurrentFiber and ReactDebugCurrentFrame so that we can rely on these for all stacks. * Make ReactDebugCurrentFrame implementation independent Introduced ReactDebugCurrentStack for the Stack renderer which does the same thing as ReactDebugCurrentFiber. ReactDebugCurrentFrame no longer keeps track of the current fiber/debug id. That's handled by the individual renderers. Instead, it is now used to keep track of the current *implementation* of the current stack frame. That way it is decoupled from the specifics of the renderers. There can be multiple renderers in a context. What matters is which one is currently executing a debuggable context (such as a render function). * Add debug frames to ReactPartialRenderer (ssr) Basic functionality. * Add shared modules to shallow renderer This is now needed because we share describeComponentFrame.
1 parent fb30d23 commit 3b5e3b5

23 files changed

+320
-169
lines changed
Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,2 @@
11
src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
2-
* gives source code refs for unknown prop warning (ssr)
3-
* gives source code refs for unknown prop warning for exact elements (ssr)
42
* gives source code refs for unknown prop warning for exact elements in composition (ssr)
5-
* should suggest property name if available (ssr)

scripts/fiber/tests-passing.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,10 +853,13 @@ src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js
853853
* should warn about props that are no longer supported
854854
* should warn about props that are no longer supported (ssr)
855855
* gives source code refs for unknown prop warning
856+
* gives source code refs for unknown prop warning (ssr)
856857
* gives source code refs for unknown prop warning for update render
857858
* gives source code refs for unknown prop warning for exact elements
859+
* gives source code refs for unknown prop warning for exact elements (ssr)
858860
* gives source code refs for unknown prop warning for exact elements in composition
859861
* should suggest property name if available
862+
* should suggest property name if available (ssr)
860863
* renders innerHTML and preserves whitespace
861864
* render and then updates innerHTML and preserves whitespace
862865

scripts/rollup/bundles.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,11 @@ const bundles = [
381381
label: 'shallow-renderer',
382382
manglePropertiesOnProd: false,
383383
name: 'react-test-renderer/shallow',
384-
paths: ['src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js'],
384+
paths: [
385+
'src/renderers/shared/**/*.js',
386+
'src/renderers/testing/**/*.js',
387+
'src/shared/**/*.js',
388+
],
385389
},
386390

387391
/******* React Noop Renderer (used only for fixtures/fiber-debugger) *******/

src/isomorphic/children/traverseAllChildren.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ var REACT_ELEMENT_TYPE =
2424
0xeac7;
2525

2626
if (__DEV__) {
27-
var {getCurrentStackAddendum} = require('ReactComponentTreeHook');
27+
var {getStackAddendum} = require('ReactDebugCurrentFrame');
2828
}
2929

3030
var SEPARATOR = '.';
@@ -171,7 +171,7 @@ function traverseAllChildrenImpl(
171171
'Using Maps as children is unsupported and will likely yield ' +
172172
'unexpected results. Convert it to a sequence/iterable of keyed ' +
173173
'ReactElements instead.%s',
174-
getCurrentStackAddendum(),
174+
getStackAddendum(),
175175
);
176176
didWarnAboutMaps = true;
177177
}
@@ -196,7 +196,7 @@ function traverseAllChildrenImpl(
196196
addendum =
197197
' If you meant to render a collection of children, use an array ' +
198198
'instead.' +
199-
getCurrentStackAddendum();
199+
getStackAddendum();
200200
}
201201
var childrenString = '' + children;
202202
invariant(

src/isomorphic/classic/element/ReactDebugCurrentFrame.js

Lines changed: 5 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -12,46 +12,18 @@
1212

1313
'use strict';
1414

15-
import type {Fiber} from 'ReactFiber';
16-
import type {DebugID} from 'ReactInstanceType';
17-
1815
const ReactDebugCurrentFrame = {};
1916

2017
if (__DEV__) {
21-
var {
22-
getStackAddendumByID,
23-
getCurrentStackAddendum,
24-
} = require('ReactComponentTreeHook');
25-
var {
26-
getStackAddendumByWorkInProgressFiber,
27-
} = require('ReactFiberComponentTreeHook');
28-
2918
// Component that is being worked on
30-
ReactDebugCurrentFrame.current = (null: Fiber | DebugID | null);
31-
32-
// Element that is being cloned or created
33-
ReactDebugCurrentFrame.element = (null: *);
19+
ReactDebugCurrentFrame.getCurrentStack = (null: null | (() => string | null));
3420

3521
ReactDebugCurrentFrame.getStackAddendum = function(): string | null {
36-
let stack = null;
37-
const current = ReactDebugCurrentFrame.current;
38-
const element = ReactDebugCurrentFrame.element;
39-
if (current !== null) {
40-
if (typeof current === 'number') {
41-
// DebugID from Stack.
42-
const debugID = current;
43-
stack = getStackAddendumByID(debugID);
44-
} else if (typeof current.tag === 'number') {
45-
// This is a Fiber.
46-
// The stack will only be correct if this is a work in progress
47-
// version and we're calling it during reconciliation.
48-
const workInProgress = current;
49-
stack = getStackAddendumByWorkInProgressFiber(workInProgress);
50-
}
51-
} else if (element !== null) {
52-
stack = getCurrentStackAddendum(element);
22+
const impl = ReactDebugCurrentFrame.getCurrentStack;
23+
if (impl) {
24+
return impl();
5325
}
54-
return stack;
26+
return null;
5527
};
5628
}
5729

src/isomorphic/classic/element/ReactElementValidator.js

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,42 @@
2121
var ReactCurrentOwner = require('ReactCurrentOwner');
2222
var ReactElement = require('ReactElement');
2323

24-
var getComponentName = require('getComponentName');
25-
2624
if (__DEV__) {
2725
var checkPropTypes = require('prop-types/checkPropTypes');
2826
var lowPriorityWarning = require('lowPriorityWarning');
2927
var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame');
3028
var warning = require('fbjs/lib/warning');
31-
var {getCurrentStackAddendum} = require('ReactComponentTreeHook');
29+
var describeComponentFrame = require('describeComponentFrame');
30+
var getComponentName = require('getComponentName');
31+
32+
var currentlyValidatingElement = null;
33+
34+
var getDisplayName = function(element: ?ReactElement): string {
35+
if (element == null) {
36+
return '#empty';
37+
} else if (typeof element === 'string' || typeof element === 'number') {
38+
return '#text';
39+
} else if (typeof element.type === 'string') {
40+
return element.type;
41+
} else {
42+
return element.type.displayName || element.type.name || 'Unknown';
43+
}
44+
};
45+
46+
var getStackAddendum = function(): string {
47+
var stack = '';
48+
if (currentlyValidatingElement) {
49+
var name = getDisplayName(currentlyValidatingElement);
50+
var owner = currentlyValidatingElement._owner;
51+
stack += describeComponentFrame(
52+
name,
53+
currentlyValidatingElement._source,
54+
owner && getComponentName(owner),
55+
);
56+
}
57+
stack += ReactDebugCurrentFrame.getStackAddendum() || '';
58+
return stack;
59+
};
3260
}
3361

3462
var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
@@ -115,14 +143,16 @@ function validateExplicitKey(element, parentType) {
115143
childOwner = ` It was passed a child from ${getComponentName(element._owner)}.`;
116144
}
117145

146+
currentlyValidatingElement = element;
118147
warning(
119148
false,
120149
'Each child in an array or iterator should have a unique "key" prop.' +
121150
'%s%s See https://fb.me/react-warning-keys for more information.%s',
122151
currentComponentErrorInfo,
123152
childOwner,
124-
getCurrentStackAddendum(element),
153+
getStackAddendum(),
125154
);
155+
currentlyValidatingElement = null;
126156
}
127157

128158
/**
@@ -193,13 +223,9 @@ function validatePropTypes(element) {
193223
: componentClass.propTypes;
194224

195225
if (propTypes) {
196-
checkPropTypes(
197-
propTypes,
198-
element.props,
199-
'prop',
200-
name,
201-
ReactDebugCurrentFrame.getStackAddendum,
202-
);
226+
currentlyValidatingElement = element;
227+
checkPropTypes(propTypes, element.props, 'prop', name, getStackAddendum);
228+
currentlyValidatingElement = null;
203229
}
204230
if (typeof componentClass.getDefaultProps === 'function') {
205231
warning(
@@ -235,7 +261,7 @@ var ReactElementValidator = {
235261
info += getDeclarationErrorAddendum();
236262
}
237263

238-
info += getCurrentStackAddendum();
264+
info += ReactDebugCurrentFrame.getStackAddendum() || '';
239265

240266
warning(
241267
false,
@@ -255,10 +281,6 @@ var ReactElementValidator = {
255281
return element;
256282
}
257283

258-
if (__DEV__) {
259-
ReactDebugCurrentFrame.element = element;
260-
}
261-
262284
// Skip key warning if the type isn't valid since our key validation logic
263285
// doesn't expect a non-string/function type and can throw confusing errors.
264286
// We don't want exception behavior to differ between dev and prod.
@@ -272,10 +294,6 @@ var ReactElementValidator = {
272294

273295
validatePropTypes(element);
274296

275-
if (__DEV__) {
276-
ReactDebugCurrentFrame.element = null;
277-
}
278-
279297
return element;
280298
},
281299

@@ -306,16 +324,10 @@ var ReactElementValidator = {
306324

307325
cloneElement: function(element, props, children) {
308326
var newElement = ReactElement.cloneElement.apply(this, arguments);
309-
if (__DEV__) {
310-
ReactDebugCurrentFrame.element = newElement;
311-
}
312327
for (var i = 2; i < arguments.length; i++) {
313328
validateChildKeys(arguments[i], newElement.type);
314329
}
315330
validatePropTypes(newElement);
316-
if (__DEV__) {
317-
ReactDebugCurrentFrame.element = null;
318-
}
319331
return newElement;
320332
},
321333
};

src/isomorphic/hooks/ReactComponentTreeHook.js

Lines changed: 7 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,12 @@
1313
'use strict';
1414

1515
var ReactCurrentOwner = require('ReactCurrentOwner');
16-
var {
17-
getStackAddendumByWorkInProgressFiber,
18-
describeComponentFrame,
19-
} = require('ReactFiberComponentTreeHook');
2016
var invariant = require('fbjs/lib/invariant');
2117
var warning = require('fbjs/lib/warning');
22-
var getComponentName = require('getComponentName');
18+
var describeComponentFrame = require('describeComponentFrame');
2319

2420
import type {ReactElement, Source} from 'ReactElementType';
2521
import type {DebugID} from 'ReactInstanceType';
26-
import type {Fiber} from 'ReactFiber';
2722

2823
function isNative(fn) {
2924
// Based on isNative() from Lodash
@@ -313,26 +308,15 @@ var ReactComponentTreeHook = {
313308
return item ? item.isMounted : false;
314309
},
315310

316-
getCurrentStackAddendum(topElement: ?ReactElement): string {
311+
getCurrentStackAddendum(): string {
317312
var info = '';
318-
if (topElement) {
319-
var name = getDisplayName(topElement);
320-
var owner = topElement._owner;
321-
info += describeComponentFrame(
322-
name,
323-
topElement._source,
324-
owner && getComponentName(owner),
325-
);
326-
}
327-
328313
var currentOwner = ReactCurrentOwner.current;
329314
if (currentOwner) {
330-
if (typeof currentOwner.tag === 'number') {
331-
const workInProgress = ((currentOwner: any): Fiber);
332-
// Safe because if current owner exists, we are reconciling,
333-
// and it is guaranteed to be the work-in-progress version.
334-
info += getStackAddendumByWorkInProgressFiber(workInProgress);
335-
} else if (typeof currentOwner._debugID === 'number') {
315+
invariant(
316+
typeof currentOwner.tag !== 'number',
317+
'Fiber owners should not show up in Stack stack traces.',
318+
);
319+
if (typeof currentOwner._debugID === 'number') {
336320
info += ReactComponentTreeHook.getStackAddendumByID(
337321
currentOwner._debugID,
338322
);

src/renderers/__tests__/ReactComponentTreeHook-test.js

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe('ReactComponentTreeHook', () => {
2020
var ReactDOMServer;
2121
var ReactInstanceMap;
2222
var ReactComponentTreeHook;
23+
var ReactDebugCurrentFiber;
2324
var ReactComponentTreeTestUtils;
2425

2526
beforeEach(() => {
@@ -29,6 +30,7 @@ describe('ReactComponentTreeHook', () => {
2930
ReactDOM = require('react-dom');
3031
ReactDOMServer = require('react-dom/server');
3132
ReactInstanceMap = require('ReactInstanceMap');
33+
ReactDebugCurrentFiber = require('ReactDebugCurrentFiber');
3234
ReactComponentTreeHook = require('ReactComponentTreeHook');
3335
ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils');
3436
});
@@ -37,7 +39,9 @@ describe('ReactComponentTreeHook', () => {
3739
describe('stack addenda', () => {
3840
it('gets created', () => {
3941
function getAddendum(element) {
40-
var addendum = ReactComponentTreeHook.getCurrentStackAddendum(element);
42+
var addendum = ReactDOMFeatureFlags.useFiber
43+
? ReactDebugCurrentFiber.getCurrentFiberStackAddendum() || ''
44+
: ReactComponentTreeHook.getCurrentStackAddendum();
4145
return addendum.replace(/\(at .+?:\d+\)/g, '(at **)');
4246
}
4347

@@ -47,23 +51,23 @@ describe('ReactComponentTreeHook', () => {
4751
Object.defineProperty(Anon, 'name', {
4852
value: null,
4953
});
50-
function Orange() {
51-
return null;
52-
}
54+
// function Orange() {
55+
// return null;
56+
// }
5357

5458
expectDev(getAddendum()).toBe('');
55-
expectDev(getAddendum(<div />)).toBe('\n in div (at **)');
56-
expectDev(getAddendum(<Anon />)).toBe('\n in Unknown (at **)');
57-
expectDev(getAddendum(<Orange />)).toBe('\n in Orange (at **)');
58-
expectDev(getAddendum(React.createElement(Orange))).toBe(
59-
'\n in Orange',
60-
);
59+
// expectDev(getAddendum(<div />)).toBe('\n in div (at **)');
60+
// expectDev(getAddendum(<Anon />)).toBe('\n in Unknown (at **)');
61+
// expectDev(getAddendum(<Orange />)).toBe('\n in Orange (at **)');
62+
// expectDev(getAddendum(React.createElement(Orange))).toBe(
63+
// '\n in Orange',
64+
// );
6165

6266
var renders = 0;
63-
var rOwnedByQ;
67+
//var rOwnedByQ;
6468

6569
function Q() {
66-
return (rOwnedByQ = React.createElement(R));
70+
return /*rOwnedByQ =*/ React.createElement(R);
6771
}
6872
function R() {
6973
return <div><S /></div>;
@@ -82,15 +86,15 @@ describe('ReactComponentTreeHook', () => {
8286
'\n in Q (at **)',
8387
);
8488
expectDev(getAddendum(<span />)).toBe(
85-
'\n in span (at **)' +
86-
'\n in S (at **)' +
89+
// '\n in span (at **)' +
90+
'\n in S (at **)' +
8791
'\n in div (at **)' +
8892
'\n in R (created by Q)' +
8993
'\n in Q (at **)',
9094
);
9195
expectDev(getAddendum(React.createElement('span'))).toBe(
92-
'\n in span (created by S)' +
93-
'\n in S (at **)' +
96+
// '\n in span (created by S)' +
97+
'\n in S (at **)' +
9498
'\n in div (at **)' +
9599
'\n in R (created by Q)' +
96100
'\n in Q (at **)',
@@ -103,7 +107,7 @@ describe('ReactComponentTreeHook', () => {
103107
expectDev(renders).toBe(2);
104108

105109
// Make sure owner is fetched for the top element too.
106-
expectDev(getAddendum(rOwnedByQ)).toBe('\n in R (created by Q)');
110+
// expectDev(getAddendum(rOwnedByQ)).toBe('\n in R (created by Q)');
107111
});
108112

109113
// These are features and regression tests that only affect

0 commit comments

Comments
 (0)