.' +
+ '\n in tr (at **)' +
+ '\n in div (at **)',
+ 'Warning: validateDOMNesting(...):
cannot appear as a child of ' +
+ '.' +
+ '\n in tr (at **)' +
+ '\n in div (at **)',
+ ],
+ );
});
it('warns on invalid nesting at root', () => {
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js
index a910b011f050..359c61a2fada 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.internal.js
@@ -1097,7 +1097,7 @@ describe('ReactDOMServerHooks', () => {
it('useOpaqueIdentifier: ID is not used during hydration but is used in an update', async () => {
let _setShow;
- function App() {
+ function App({unused}) {
Scheduler.unstable_yieldValue('App');
const id = useOpaqueIdentifier();
const [show, setShow] = useState(false);
@@ -1129,7 +1129,7 @@ describe('ReactDOMServerHooks', () => {
it('useOpaqueIdentifier: ID is not used during hydration but is used in an update in legacy', async () => {
let _setShow;
- function App() {
+ function App({unused}) {
Scheduler.unstable_yieldValue('App');
const id = useOpaqueIdentifier();
const [show, setShow] = useState(false);
diff --git a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
index 7601a4723097..81427c97eb05 100644
--- a/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
+++ b/packages/react-dom/src/__tests__/ReactErrorBoundaries-test.internal.js
@@ -497,7 +497,7 @@ describe('ReactErrorBoundaries', () => {
}
};
- BrokenUseEffect = props => {
+ BrokenUseEffect = ({children}) => {
Scheduler.unstable_yieldValue('BrokenUseEffect render');
React.useEffect(() => {
@@ -505,10 +505,10 @@ describe('ReactErrorBoundaries', () => {
throw new Error('Hello');
});
- return props.children;
+ return children;
};
- BrokenUseLayoutEffect = props => {
+ BrokenUseLayoutEffect = ({children}) => {
Scheduler.unstable_yieldValue('BrokenUseLayoutEffect render');
React.useLayoutEffect(() => {
@@ -518,7 +518,7 @@ describe('ReactErrorBoundaries', () => {
throw new Error('Hello');
});
- return props.children;
+ return children;
};
NoopErrorBoundary = class extends React.Component {
diff --git a/packages/react-dom/src/__tests__/ReactServerRendering-test.js b/packages/react-dom/src/__tests__/ReactServerRendering-test.js
index 26fb497a6b66..fb5858e88114 100644
--- a/packages/react-dom/src/__tests__/ReactServerRendering-test.js
+++ b/packages/react-dom/src/__tests__/ReactServerRendering-test.js
@@ -18,7 +18,12 @@ const enableSuspenseServerRenderer = require('shared/ReactFeatureFlags')
.enableSuspenseServerRenderer;
function normalizeCodeLocInfo(str) {
- return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
+ return (
+ str &&
+ str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
+ return '\n in ' + name + ' (at **)';
+ })
+ );
}
describe('ReactDOMServer', () => {
diff --git a/packages/react-dom/src/__tests__/ReactUpdates-test.js b/packages/react-dom/src/__tests__/ReactUpdates-test.js
index 29bccc13b6e0..993869de6e99 100644
--- a/packages/react-dom/src/__tests__/ReactUpdates-test.js
+++ b/packages/react-dom/src/__tests__/ReactUpdates-test.js
@@ -1615,7 +1615,7 @@ describe('ReactUpdates', () => {
Scheduler.unstable_clearYields();
}
expect(error).toContain('Warning: Maximum update depth exceeded.');
- expect(stack).toContain('in NonTerminating');
+ expect(stack).toContain(' NonTerminating');
// rethrow error to prevent going into an infinite loop when act() exits
throw error;
});
diff --git a/packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js b/packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js
index a20ab6b9fc04..3cf8f803a47f 100644
--- a/packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js
+++ b/packages/react-native-renderer/src/__tests__/ReactNativeError-test.internal.js
@@ -16,7 +16,12 @@ let createReactNativeComponentClass;
let computeComponentStackForErrorReporting;
function normalizeCodeLocInfo(str) {
- return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
+ return (
+ str &&
+ str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
+ return '\n in ' + name + ' (at **)';
+ })
+ );
}
describe('ReactNativeError', () => {
@@ -74,20 +79,11 @@ describe('ReactNativeError', () => {
computeComponentStackForErrorReporting(reactTag),
);
- if (__DEV__) {
- expect(componentStack).toBe(
- '\n' +
- ' in View (at **)\n' +
- ' in FunctionComponent (at **)\n' +
- ' in ClassComponent (at **)',
- );
- } else {
- expect(componentStack).toBe(
- '\n' +
- ' in View\n' +
- ' in FunctionComponent\n' +
- ' in ClassComponent',
- );
- }
+ expect(componentStack).toBe(
+ '\n' +
+ ' in View (at **)\n' +
+ ' in FunctionComponent (at **)\n' +
+ ' in ClassComponent (at **)',
+ );
});
});
diff --git a/packages/react-reconciler/src/__tests__/ReactFragment-test.js b/packages/react-reconciler/src/__tests__/ReactFragment-test.js
index c258b25146ac..7c058e95d288 100644
--- a/packages/react-reconciler/src/__tests__/ReactFragment-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactFragment-test.js
@@ -10,6 +10,7 @@
'use strict';
let React;
+let ReactFeatureFlags;
let ReactNoop;
let Scheduler;
@@ -18,6 +19,7 @@ describe('ReactFragment', () => {
jest.resetModules();
React = require('react');
+ ReactFeatureFlags = require('shared/ReactFeatureFlags');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
});
@@ -900,17 +902,27 @@ describe('ReactFragment', () => {
);
ReactNoop.render();
- expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
- 'Each child in a list should have a unique "key" prop.',
- );
+ if (ReactFeatureFlags.enableComponentStackLocations) {
+ // The key warning gets deduped because it's in the same component.
+ expect(Scheduler).toFlushWithoutYielding();
+ } else {
+ expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
+ 'Each child in a list should have a unique "key" prop.',
+ );
+ }
expect(ops).toEqual(['Update Stateful']);
expect(ReactNoop.getChildren()).toEqual([span(), div()]);
ReactNoop.render();
- expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
- 'Each child in a list should have a unique "key" prop.',
- );
+ if (ReactFeatureFlags.enableComponentStackLocations) {
+ // The key warning gets deduped because it's in the same component.
+ expect(Scheduler).toFlushWithoutYielding();
+ } else {
+ expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
+ 'Each child in a list should have a unique "key" prop.',
+ );
+ }
expect(ops).toEqual(['Update Stateful', 'Update Stateful']);
expect(ReactNoop.getChildren()).toEqual([span(), div()]);
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
index 4ba335869e8d..b9b5836e7096 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
@@ -10,8 +10,8 @@
'use strict';
+let ReactFeatureFlags = require('shared/ReactFeatureFlags');
let PropTypes;
-let ReactFeatureFlags;
let React;
let ReactNoop;
let Scheduler;
@@ -37,7 +37,12 @@ describe('ReactIncrementalErrorHandling', () => {
}
function normalizeCodeLocInfo(str) {
- return str && str.replace(/\(at .+?:\d+\)/g, '(at **)');
+ return (
+ str &&
+ str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
+ return '\n in ' + name + ' (at **)';
+ })
+ );
}
it('recovers from errors asynchronously', () => {
@@ -57,17 +62,17 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function ErrorMessage(props) {
+ function ErrorMessage({error}) {
Scheduler.unstable_yieldValue('ErrorMessage');
- return ;
+ return ;
}
- function Indirection(props) {
+ function Indirection({children}) {
Scheduler.unstable_yieldValue('Indirection');
- return props.children || null;
+ return children || null;
}
- function BadRender() {
+ function BadRender({unused}) {
Scheduler.unstable_yieldValue('throw');
throw new Error('oops!');
}
@@ -151,17 +156,17 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function ErrorMessage(props) {
+ function ErrorMessage({error}) {
Scheduler.unstable_yieldValue('ErrorMessage');
- return ;
+ return ;
}
- function Indirection(props) {
+ function Indirection({children}) {
Scheduler.unstable_yieldValue('Indirection');
- return props.children || null;
+ return children || null;
}
- function BadRender() {
+ function BadRender({unused}) {
Scheduler.unstable_yieldValue('throw');
throw new Error('oops!');
}
@@ -341,17 +346,17 @@ describe('ReactIncrementalErrorHandling', () => {
});
it('retries one more time before handling error', () => {
- function BadRender() {
+ function BadRender({unused}) {
Scheduler.unstable_yieldValue('BadRender');
throw new Error('oops');
}
- function Sibling() {
+ function Sibling({unused}) {
Scheduler.unstable_yieldValue('Sibling');
return ;
}
- function Parent() {
+ function Parent({unused}) {
Scheduler.unstable_yieldValue('Parent');
return (
<>
@@ -381,7 +386,7 @@ describe('ReactIncrementalErrorHandling', () => {
});
it('retries one more time if an error occurs during a render that expires midway through the tree', () => {
- function Oops() {
+ function Oops({unused}) {
Scheduler.unstable_yieldValue('Oops');
throw new Error('Oops');
}
@@ -391,7 +396,7 @@ describe('ReactIncrementalErrorHandling', () => {
return text;
}
- function App() {
+ function App({unused}) {
return (
<>
@@ -525,7 +530,7 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function BrokenRender(props) {
+ function BrokenRender({unused}) {
Scheduler.unstable_yieldValue('BrokenRender');
throw new Error('Hello');
}
@@ -571,7 +576,7 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function BrokenRender(props) {
+ function BrokenRender({unused}) {
Scheduler.unstable_yieldValue('BrokenRender');
throw new Error('Hello');
}
@@ -618,7 +623,7 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function BrokenRender(props) {
+ function BrokenRender({unused}) {
Scheduler.unstable_yieldValue('BrokenRender');
throw new Error('Hello');
}
@@ -659,7 +664,7 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function BrokenRender() {
+ function BrokenRender({unused}) {
Scheduler.unstable_yieldValue('BrokenRender');
throw new Error('Hello');
}
@@ -698,7 +703,7 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function BrokenRender() {
+ function BrokenRender({unused}) {
Scheduler.unstable_yieldValue('BrokenRender');
throw new Error('Hello');
}
@@ -739,7 +744,7 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function BrokenRender() {
+ function BrokenRender({unused}) {
Scheduler.unstable_yieldValue('BrokenRender');
throw new Error('Hello');
}
@@ -779,7 +784,7 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function BrokenRender() {
+ function BrokenRender({unused}) {
Scheduler.unstable_yieldValue('BrokenRender');
throw new Error('Hello');
}
@@ -857,12 +862,12 @@ describe('ReactIncrementalErrorHandling', () => {
});
it('can schedule updates after uncaught error in render on mount', () => {
- function BrokenRender() {
+ function BrokenRender({unused}) {
Scheduler.unstable_yieldValue('BrokenRender');
throw new Error('Hello');
}
- function Foo() {
+ function Foo({unused}) {
Scheduler.unstable_yieldValue('Foo');
return null;
}
@@ -882,24 +887,24 @@ describe('ReactIncrementalErrorHandling', () => {
});
it('can schedule updates after uncaught error in render on update', () => {
- function BrokenRender(props) {
+ function BrokenRender({shouldThrow}) {
Scheduler.unstable_yieldValue('BrokenRender');
- if (props.throw) {
+ if (shouldThrow) {
throw new Error('Hello');
}
return null;
}
- function Foo() {
+ function Foo({unused}) {
Scheduler.unstable_yieldValue('Foo');
return null;
}
- ReactNoop.render();
+ ReactNoop.render();
expect(Scheduler).toFlushAndYield(['BrokenRender']);
expect(() => {
- ReactNoop.render();
+ ReactNoop.render();
expect(Scheduler).toFlushWithoutYielding();
}).toThrow('Hello');
expect(Scheduler).toHaveYielded([
@@ -1449,13 +1454,13 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function Indirection(props) {
+ function Indirection({children}) {
Scheduler.unstable_yieldValue('Indirection');
- return props.children;
+ return children;
}
const notAnError = {nonStandardMessage: 'oops'};
- function BadRender() {
+ function BadRender({unused}) {
Scheduler.unstable_yieldValue('BadRender');
throw notAnError;
}
@@ -1514,17 +1519,17 @@ describe('ReactIncrementalErrorHandling', () => {
}
}
- function ErrorMessage(props) {
+ function ErrorMessage({error}) {
Scheduler.unstable_yieldValue('ErrorMessage');
- return ;
+ return ;
}
- function BadRenderSibling(props) {
+ function BadRenderSibling({unused}) {
Scheduler.unstable_yieldValue('BadRenderSibling');
return null;
}
- function BadRender() {
+ function BadRender({unused}) {
Scheduler.unstable_yieldValue('throw');
throw new Error('oops!');
}
@@ -1562,7 +1567,7 @@ describe('ReactIncrementalErrorHandling', () => {
// This test seems a bit contrived, but it's based on an actual regression
// where we checked for the existence of didUpdate instead of didMount, and
// didMount was not defined.
- function BadRender() {
+ function BadRender({unused}) {
Scheduler.unstable_yieldValue('throw');
throw new Error('oops!');
}
@@ -1633,10 +1638,8 @@ describe('ReactIncrementalErrorHandling', () => {
expect(ReactNoop.getChildren()).toEqual([
span(
'Caught an error:\n' +
- (__DEV__
- ? ' in BrokenRender (at **)\n'
- : ' in BrokenRender\n') +
- (__DEV__ ? ' in ErrorBoundary (at **).' : ' in ErrorBoundary.'),
+ ' in BrokenRender (at **)\n' +
+ ' in ErrorBoundary (at **).',
),
]);
});
@@ -1669,7 +1672,7 @@ describe('ReactIncrementalErrorHandling', () => {
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
});
- if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
+ if (!ReactFeatureFlags.disableModulePatternComponents) {
it('handles error thrown inside getDerivedStateFromProps of a module-style context provider', () => {
function Provider() {
return {
@@ -1708,7 +1711,7 @@ describe('ReactIncrementalErrorHandling', () => {
it('uncaught errors should be discarded if the render is aborted', async () => {
const root = ReactNoop.createRoot();
- function Oops() {
+ function Oops({unused}) {
Scheduler.unstable_yieldValue('Oops');
throw Error('Oops');
}
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
index e4ade52f4eea..745ca558c9aa 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
@@ -59,9 +59,9 @@ describe('ReactIncrementalErrorLogging', () => {
? expect.stringMatching(
new RegExp(
'The above error occurred in the component:\n' +
- '\\s+in ErrorThrowingComponent (.*)\n' +
- '\\s+in span (.*)\n' +
- '\\s+in div (.*)\n\n' +
+ '\\s+(in|at) ErrorThrowingComponent (.*)\n' +
+ '\\s+(in|at) span(.*)\n' +
+ '\\s+(in|at) div(.*)\n\n' +
'Consider adding an error boundary to your tree ' +
'to customize error handling behavior\\.',
),
@@ -95,9 +95,9 @@ describe('ReactIncrementalErrorLogging', () => {
? expect.stringMatching(
new RegExp(
'The above error occurred in the component:\n' +
- '\\s+in ErrorThrowingComponent (.*)\n' +
- '\\s+in span (.*)\n' +
- '\\s+in div (.*)\n\n' +
+ '\\s+(in|at) ErrorThrowingComponent (.*)\n' +
+ '\\s+(in|at) span(.*)\n' +
+ '\\s+(in|at) div(.*)\n\n' +
'Consider adding an error boundary to your tree ' +
'to customize error handling behavior\\.',
),
@@ -134,9 +134,9 @@ describe('ReactIncrementalErrorLogging', () => {
? expect.stringMatching(
new RegExp(
'The above error occurred in the component:\n' +
- '\\s+in ErrorThrowingComponent (.*)\n' +
- '\\s+in span (.*)\n' +
- '\\s+in div (.*)\n\n' +
+ '\\s+(in|at) ErrorThrowingComponent (.*)\n' +
+ '\\s+(in|at) span(.*)\n' +
+ '\\s+(in|at) div(.*)\n\n' +
'Consider adding an error boundary to your tree ' +
'to customize error handling behavior\\.',
),
@@ -206,8 +206,8 @@ describe('ReactIncrementalErrorLogging', () => {
? expect.stringMatching(
new RegExp(
'The above error occurred in the component:\n' +
- '\\s+in Foo (.*)\n' +
- '\\s+in ErrorBoundary (.*)\n\n' +
+ '\\s+(in|at) Foo (.*)\n' +
+ '\\s+(in|at) ErrorBoundary (.*)\n\n' +
'React will try to recreate this component tree from scratch ' +
'using the error boundary you provided, ErrorBoundary.',
),
diff --git a/packages/react/src/ReactDebugCurrentFrame.js b/packages/react/src/ReactDebugCurrentFrame.js
index cdb2eb9fc2e7..ddaeed70f99b 100644
--- a/packages/react/src/ReactDebugCurrentFrame.js
+++ b/packages/react/src/ReactDebugCurrentFrame.js
@@ -7,26 +7,20 @@
* @flow
*/
-import type {ReactElement} from 'shared/ReactElementType';
-
-import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
-
const ReactDebugCurrentFrame = {};
-let currentlyValidatingElement = (null: null | ReactElement);
+let currentExtraStackFrame = (null: null | string);
-export function setCurrentlyValidatingElement(element: null | ReactElement) {
+export function setExtraStackFrame(stack: null | string) {
if (__DEV__) {
- currentlyValidatingElement = element;
+ currentExtraStackFrame = stack;
}
}
if (__DEV__) {
- ReactDebugCurrentFrame.setCurrentlyValidatingElement = function(
- element: null | ReactElement,
- ) {
+ ReactDebugCurrentFrame.setExtraStackFrame = function(stack: null | string) {
if (__DEV__) {
- currentlyValidatingElement = element;
+ currentExtraStackFrame = stack;
}
};
// Stack implementation injected by the current renderer.
@@ -36,13 +30,8 @@ if (__DEV__) {
let stack = '';
// Add an extra top frame while an element is being validated
- if (currentlyValidatingElement) {
- const owner = currentlyValidatingElement._owner;
- stack += describeUnknownElementTypeFrameInDEV(
- currentlyValidatingElement.type,
- currentlyValidatingElement._source,
- owner ? owner.type : null,
- );
+ if (currentExtraStackFrame) {
+ stack += currentExtraStackFrame;
}
// Delegate to the injected renderer-specific implementation
diff --git a/packages/react/src/ReactElementValidator.js b/packages/react/src/ReactElementValidator.js
index c81ace3c6b2f..43c514649289 100644
--- a/packages/react/src/ReactElementValidator.js
+++ b/packages/react/src/ReactElementValidator.js
@@ -31,7 +31,24 @@ import {
cloneElement,
jsxDEV,
} from './ReactElement';
-import {setCurrentlyValidatingElement} from './ReactDebugCurrentFrame';
+import {setExtraStackFrame} from './ReactDebugCurrentFrame';
+import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
+
+function setCurrentlyValidatingElement(element) {
+ if (__DEV__) {
+ if (element) {
+ const owner = element._owner;
+ const stack = describeUnknownElementTypeFrameInDEV(
+ element.type,
+ element._source,
+ owner ? owner.type : null,
+ );
+ setExtraStackFrame(stack);
+ } else {
+ setExtraStackFrame(null);
+ }
+ }
+}
let propTypesMisspellWarningShown;
@@ -127,16 +144,16 @@ function validateExplicitKey(element, parentType) {
)}.`;
}
- setCurrentlyValidatingElement(element);
if (__DEV__) {
+ setCurrentlyValidatingElement(element);
console.error(
'Each child in a list should have a unique "key" prop.' +
'%s%s See https://fb.me/react-warning-keys for more information.',
currentComponentErrorInfo,
childOwner,
);
+ setCurrentlyValidatingElement(null);
}
- setCurrentlyValidatingElement(null);
}
/**
diff --git a/packages/react/src/__tests__/ReactElementClone-test.js b/packages/react/src/__tests__/ReactElementClone-test.js
index b428787a7bf7..88cbea84cd7e 100644
--- a/packages/react/src/__tests__/ReactElementClone-test.js
+++ b/packages/react/src/__tests__/ReactElementClone-test.js
@@ -301,8 +301,8 @@ describe('ReactElementClone', () => {
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `Component`, ' +
'expected `string`.\n' +
- ' in Component (created by GrandParent)\n' +
- ' in Parent (created by GrandParent)\n' +
+ ' in Component (at **)\n' +
+ ' in Parent (at **)\n' +
' in GrandParent',
);
});
diff --git a/packages/react/src/__tests__/ReactElementJSX-test.js b/packages/react/src/__tests__/ReactElementJSX-test.js
index 0ce12c86f616..fbf9cc7a3ca2 100644
--- a/packages/react/src/__tests__/ReactElementJSX-test.js
+++ b/packages/react/src/__tests__/ReactElementJSX-test.js
@@ -364,8 +364,8 @@ describe('ReactElement.jsx', () => {
).toErrorDev(
'Warning: Each child in a list should have a unique "key" prop.\n\n' +
'Check the render method of `Parent`. See https://fb.me/react-warning-keys for more information.\n' +
- ' in Child (created by Parent)\n' +
- ' in Parent',
+ ' in Child (at **)\n' +
+ ' in Parent (at **)',
);
});
diff --git a/packages/react/src/__tests__/ReactElementValidator-test.internal.js b/packages/react/src/__tests__/ReactElementValidator-test.internal.js
index c563e6ce6fea..c8209952c1cc 100644
--- a/packages/react/src/__tests__/ReactElementValidator-test.internal.js
+++ b/packages/react/src/__tests__/ReactElementValidator-test.internal.js
@@ -227,8 +227,8 @@ describe('ReactElementValidator', () => {
'Warning: Failed prop type: ' +
'Invalid prop `color` of type `number` supplied to `MyComp`, ' +
'expected `string`.\n' +
- ' in MyComp (created by ParentComp)\n' +
- ' in ParentComp',
+ ' in MyComp (at **)\n' +
+ ' in ParentComp (at **)',
);
});
diff --git a/packages/react/src/__tests__/ReactProfiler-test.internal.js b/packages/react/src/__tests__/ReactProfiler-test.internal.js
index 1fce1caa623c..b3019fc280e5 100644
--- a/packages/react/src/__tests__/ReactProfiler-test.internal.js
+++ b/packages/react/src/__tests__/ReactProfiler-test.internal.js
@@ -1041,7 +1041,7 @@ describe('Profiler', () => {
it('should accumulate actual time after an error handled by componentDidCatch()', () => {
const callback = jest.fn();
- const ThrowsError = () => {
+ const ThrowsError = ({unused}) => {
Scheduler.unstable_advanceTime(3);
throw Error('expected error');
};
@@ -1120,7 +1120,7 @@ describe('Profiler', () => {
it('should accumulate actual time after an error handled by getDerivedStateFromError()', () => {
const callback = jest.fn();
- const ThrowsError = () => {
+ const ThrowsError = ({unused}) => {
Scheduler.unstable_advanceTime(10);
throw Error('expected error');
};
diff --git a/packages/react/src/__tests__/forwardRef-test.internal.js b/packages/react/src/__tests__/forwardRef-test.internal.js
index d98dfabcad67..ef8c7422f87a 100644
--- a/packages/react/src/__tests__/forwardRef-test.internal.js
+++ b/packages/react/src/__tests__/forwardRef-test.internal.js
@@ -159,8 +159,9 @@ describe('forwardRef', () => {
}
function Wrapper(props) {
+ const forwardedRef = props.forwardedRef;
Scheduler.unstable_yieldValue('Wrapper');
- return ;
+ return ;
}
const RefForwardingComponent = React.forwardRef((props, ref) => (
diff --git a/packages/react/src/jsx/ReactJSXElementValidator.js b/packages/react/src/jsx/ReactJSXElementValidator.js
index a96225a61d87..004c3354ed10 100644
--- a/packages/react/src/jsx/ReactJSXElementValidator.js
+++ b/packages/react/src/jsx/ReactJSXElementValidator.js
@@ -24,11 +24,30 @@ import {
import {warnAboutSpreadingKeyToJSX} from 'shared/ReactFeatureFlags';
import {jsxDEV} from './ReactJSXElement';
+
+import {describeUnknownElementTypeFrameInDEV} from 'shared/ReactComponentStackFrame';
+
import ReactSharedInternals from 'shared/ReactSharedInternals';
const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
const ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
+function setCurrentlyValidatingElement(element) {
+ if (__DEV__) {
+ if (element) {
+ const owner = element._owner;
+ const stack = describeUnknownElementTypeFrameInDEV(
+ element.type,
+ element._source,
+ owner ? owner.type : null,
+ );
+ ReactDebugCurrentFrame.setExtraStackFrame(stack);
+ } else {
+ ReactDebugCurrentFrame.setExtraStackFrame(null);
+ }
+ }
+}
+
let propTypesMisspellWarningShown;
if (__DEV__) {
@@ -140,14 +159,14 @@ function validateExplicitKey(element, parentType) {
)}.`;
}
- ReactDebugCurrentFrame.setCurrentlyValidatingElement(element);
+ setCurrentlyValidatingElement(element);
console.error(
'Each child in a list should have a unique "key" prop.' +
'%s%s See https://fb.me/react-warning-keys for more information.',
currentComponentErrorInfo,
childOwner,
);
- ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
+ setCurrentlyValidatingElement(null);
}
}
@@ -224,9 +243,9 @@ function validatePropTypes(element) {
return;
}
if (propTypes) {
- ReactDebugCurrentFrame.setCurrentlyValidatingElement(element);
+ setCurrentlyValidatingElement(element);
checkPropTypes(propTypes, element.props, 'prop', name);
- ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
+ setCurrentlyValidatingElement(null);
} else if (type.PropTypes !== undefined && !propTypesMisspellWarningShown) {
propTypesMisspellWarningShown = true;
console.error(
@@ -252,7 +271,7 @@ function validatePropTypes(element) {
*/
function validateFragmentProps(fragment) {
if (__DEV__) {
- ReactDebugCurrentFrame.setCurrentlyValidatingElement(fragment);
+ setCurrentlyValidatingElement(fragment);
const keys = Object.keys(fragment.props);
for (let i = 0; i < keys.length; i++) {
@@ -271,7 +290,7 @@ function validateFragmentProps(fragment) {
console.error('Invalid attribute `ref` supplied to `React.Fragment`.');
}
- ReactDebugCurrentFrame.setCurrentlyValidatingElement(null);
+ setCurrentlyValidatingElement(null);
}
}
diff --git a/packages/shared/ConsolePatchingDev.js b/packages/shared/ConsolePatchingDev.js
index 41c16a761d24..e1fdc0f27df2 100644
--- a/packages/shared/ConsolePatchingDev.js
+++ b/packages/shared/ConsolePatchingDev.js
@@ -12,6 +12,7 @@
// lazily which won't cover if the log function was extracted eagerly.
// We could also eagerly patch the method.
+let disabledDepth = 0;
let prevLog;
let prevInfo;
let prevWarn;
@@ -21,28 +22,40 @@ function disabledLog() {}
export function disableLogs(): void {
if (__DEV__) {
- /* eslint-disable react-internal/no-production-logging */
- prevLog = console.log;
- prevInfo = console.info;
- prevWarn = console.warn;
- prevError = console.error;
- // $FlowFixMe Flow thinks console is immutable.
- console.log = console.info = console.warn = console.error = disabledLog;
- /* eslint-enable react-internal/no-production-logging */
+ if (disabledDepth === 0) {
+ /* eslint-disable react-internal/no-production-logging */
+ prevLog = console.log;
+ prevInfo = console.info;
+ prevWarn = console.warn;
+ prevError = console.error;
+ // $FlowFixMe Flow thinks console is immutable.
+ console.log = console.info = console.warn = console.error = disabledLog;
+ /* eslint-enable react-internal/no-production-logging */
+ }
+ disabledDepth++;
}
}
export function reenableLogs(): void {
if (__DEV__) {
- /* eslint-disable react-internal/no-production-logging */
- // $FlowFixMe Flow thinks console is immutable.
- console.log = prevLog;
- // $FlowFixMe Flow thinks console is immutable.
- console.info = prevInfo;
- // $FlowFixMe Flow thinks console is immutable.
- console.warn = prevWarn;
- // $FlowFixMe Flow thinks console is immutable.
- console.error = prevError;
- /* eslint-enable react-internal/no-production-logging */
+ disabledDepth--;
+ if (disabledDepth === 0) {
+ /* eslint-disable react-internal/no-production-logging */
+ // $FlowFixMe Flow thinks console is immutable.
+ console.log = prevLog;
+ // $FlowFixMe Flow thinks console is immutable.
+ console.info = prevInfo;
+ // $FlowFixMe Flow thinks console is immutable.
+ console.warn = prevWarn;
+ // $FlowFixMe Flow thinks console is immutable.
+ console.error = prevError;
+ /* eslint-enable react-internal/no-production-logging */
+ }
+ if (disabledDepth < 0) {
+ console.error(
+ 'disabledDepth fell below zero. ' +
+ 'This is a bug in React. Please file an issue.',
+ );
+ }
}
}
diff --git a/packages/shared/ReactComponentStackFrame.js b/packages/shared/ReactComponentStackFrame.js
index 76043711fbeb..f3c80bc1ce0c 100644
--- a/packages/shared/ReactComponentStackFrame.js
+++ b/packages/shared/ReactComponentStackFrame.js
@@ -10,6 +10,8 @@
import type {Source} from 'shared/ReactElementType';
import type {LazyComponent} from 'react/src/ReactLazy';
+import {enableComponentStackLocations} from 'shared/ReactFeatureFlags';
+
import {
REACT_SUSPENSE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
@@ -19,6 +21,151 @@ import {
REACT_LAZY_TYPE,
} from 'shared/ReactSymbols';
+import {disableLogs, reenableLogs} from 'shared/ConsolePatchingDev';
+
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+
+const {ReactCurrentDispatcher} = ReactSharedInternals;
+
+let prefix;
+export function describeBuiltInComponentFrame(
+ name: string,
+ source: void | null | Source,
+ ownerFn: void | null | Function,
+): string {
+ if (enableComponentStackLocations) {
+ if (prefix === undefined) {
+ // Extract the VM specific prefix used by each line.
+ const match = Error()
+ .stack.trim()
+ .match(/\n( *(at )?)/);
+ prefix = (match && match[1]) || '';
+ }
+ // We use the prefix to ensure our stacks line up with native stack frames.
+ return '\n' + prefix + name;
+ } else {
+ let ownerName = null;
+ if (__DEV__ && ownerFn) {
+ ownerName = ownerFn.displayName || ownerFn.name || null;
+ }
+ return describeComponentFrame(name, source, ownerName);
+ }
+}
+
+let reentry = false;
+let componentFrameCache;
+if (__DEV__) {
+ const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
+ componentFrameCache = new PossiblyWeakMap();
+}
+
+export function describeNativeComponentFrame(
+ fn: Function,
+ construct: boolean,
+): string {
+ // If something asked for a stack inside a fake render, it should get ignored.
+ if (!fn || reentry) {
+ return '';
+ }
+
+ if (__DEV__) {
+ const frame = componentFrameCache.get(fn);
+ if (frame !== undefined) {
+ return frame;
+ }
+ }
+
+ const control = Error();
+
+ reentry = true;
+ let previousDispatcher;
+ if (__DEV__) {
+ previousDispatcher = ReactCurrentDispatcher.current;
+ // Set the dispatcher in DEV because this might be call in the render function
+ // for warnings.
+ ReactCurrentDispatcher.current = null;
+ disableLogs();
+ }
+ try {
+ // This should throw.
+ if (construct) {
+ // Something should be setting the props in the constructor.
+ const Fake = function() {};
+ // $FlowFixMe
+ Object.defineProperty(Fake.prototype, 'props', {
+ set: function() {
+ // We use a throwing setter instead of frozen or non-writable props
+ // because that won't throw in a non-strict mode function.
+ throw Error();
+ },
+ });
+ if (typeof Reflect === 'object' && Reflect.construct) {
+ Reflect.construct(fn, [], Fake);
+ } else {
+ fn.call(new Fake());
+ }
+ } else {
+ fn();
+ }
+ } catch (sample) {
+ // This is inlined manually because closure doesn't do it for us.
+ if (sample && typeof sample.stack === 'string') {
+ // This extracts the first frame from the sample that isn't also in the control.
+ // Skipping one frame that we assume is the frame that calls the two.
+ const sampleLines = sample.stack.split('\n');
+ const controlLines = control.stack.split('\n');
+ let s = sampleLines.length - 1;
+ let c = controlLines.length - 1;
+ while (s >= 1 && c >= 0 && sampleLines[s] !== controlLines[c]) {
+ // We expect at least one stack frame to be shared.
+ // Typically this will be the root most one. However, stack frames may be
+ // cut off due to maximum stack limits. In this case, one maybe cut off
+ // earlier than the other. We assume that the sample is longer or the same
+ // and there for cut off earlier. So we should find the root most frame in
+ // the sample somewhere in the control.
+ c--;
+ }
+ for (; s >= 1 && c >= 0; s--, c--) {
+ // Next we find the first one that isn't the same which should be the
+ // frame that called our sample function.
+ if (sampleLines[s] !== controlLines[c]) {
+ // In V8, the first line is describing the message but other VMs don't.
+ // If we're about to return the first line, and the control is also on the same
+ // line, that's a pretty good indicator that our sample threw at same line as
+ // the control. I.e. before we entered the sample frame. So we ignore this result.
+ // This can happen if you passed a class to function component, or non-function.
+ if (s !== 1 || c !== 1) {
+ // V8 adds a "new" prefix for native classes. Let's remove it to make it prettier.
+ const frame = '\n' + sampleLines[s - 1].replace(' at new ', ' at ');
+ if (__DEV__) {
+ if (typeof fn === 'function') {
+ componentFrameCache.set(fn, frame);
+ }
+ }
+ // Return the line we found.
+ return frame;
+ }
+ }
+ }
+ }
+ } finally {
+ reentry = false;
+ if (__DEV__) {
+ ReactCurrentDispatcher.current = previousDispatcher;
+ reenableLogs();
+ }
+ }
+ // Fallback to just using the name if we couldn't make it throw.
+ const name = fn ? fn.displayName || fn.name : '';
+ const syntheticFrame = name ? describeBuiltInComponentFrame(name) : '';
+ if (__DEV__) {
+ if (typeof fn === 'function') {
+ componentFrameCache.set(fn, syntheticFrame);
+ }
+ }
+ return syntheticFrame;
+}
+
const BEFORE_SLASH_RE = /^(.*)[\\\/]/;
function describeComponentFrame(
@@ -49,24 +196,16 @@ function describeComponentFrame(
return '\n in ' + (name || 'Unknown') + sourceInfo;
}
-export function describeBuiltInComponentFrame(
- name: string,
- source: void | null | Source,
- ownerFn: void | null | Function,
-): string {
- let ownerName = null;
- if (__DEV__ && ownerFn) {
- ownerName = ownerFn.displayName || ownerFn.name || null;
- }
- return describeComponentFrame(name, source, ownerName);
-}
-
export function describeClassComponentFrame(
ctor: Function,
source: void | null | Source,
ownerFn: void | null | Function,
): string {
- return describeFunctionComponentFrame(ctor, source, ownerFn);
+ if (enableComponentStackLocations) {
+ return describeNativeComponentFrame(ctor, true);
+ } else {
+ return describeFunctionComponentFrame(ctor, source, ownerFn);
+ }
}
export function describeFunctionComponentFrame(
@@ -74,15 +213,19 @@ export function describeFunctionComponentFrame(
source: void | null | Source,
ownerFn: void | null | Function,
): string {
- if (!fn) {
- return '';
- }
- const name = fn.displayName || fn.name || null;
- let ownerName = null;
- if (__DEV__ && ownerFn) {
- ownerName = ownerFn.displayName || ownerFn.name || null;
+ if (enableComponentStackLocations) {
+ return describeNativeComponentFrame(fn, false);
+ } else {
+ if (!fn) {
+ return '';
+ }
+ const name = fn.displayName || fn.name || null;
+ let ownerName = null;
+ if (__DEV__ && ownerFn) {
+ ownerName = ownerFn.displayName || ownerFn.name || null;
+ }
+ return describeComponentFrame(name, source, ownerName);
}
- return describeComponentFrame(name, source, ownerName);
}
function shouldConstruct(Component: Function) {
@@ -102,10 +245,11 @@ export function describeUnknownElementTypeFrameInDEV(
return '';
}
if (typeof type === 'function') {
- if (shouldConstruct(type)) {
- return describeClassComponentFrame(type, source, ownerFn);
+ if (enableComponentStackLocations) {
+ return describeNativeComponentFrame(type, shouldConstruct(type));
+ } else {
+ return describeFunctionComponentFrame(type, source, ownerFn);
}
- return describeFunctionComponentFrame(type, source, ownerFn);
}
if (typeof type === 'string') {
return describeBuiltInComponentFrame(type, source, ownerFn);
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 529965c9db6f..db00085a4cdc 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -101,7 +101,7 @@ export const deferPassiveEffectCleanupDuringUnmount = false;
// a deprecated pattern we want to get rid of in the future
export const warnAboutSpreadingKeyToJSX = false;
-export const enableComponentStackLocations = false;
+export const enableComponentStackLocations = __EXPERIMENTAL__;
// Internal-only attempt to debug a React Native issue. See D20130868.
export const throwEarlyForMysteriousError = false;
diff --git a/packages/shared/__tests__/describeComponentFrame-test.js b/packages/shared/__tests__/describeComponentFrame-test.js
index 7acd340e4737..3b7b843cae44 100644
--- a/packages/shared/__tests__/describeComponentFrame-test.js
+++ b/packages/shared/__tests__/describeComponentFrame-test.js
@@ -11,6 +11,7 @@
let React;
let ReactDOM;
+const ReactFeatureFlags = require('shared/ReactFeatureFlags');
describe('Component stack trace displaying', () => {
beforeEach(() => {
@@ -18,6 +19,11 @@ describe('Component stack trace displaying', () => {
ReactDOM = require('react-dom');
});
+ if (ReactFeatureFlags.enableComponentStackLocations) {
+ it("empty test so Jest doesn't complain", () => {});
+ return;
+ }
+
it('should provide filenames in stack traces', () => {
class Component extends React.Component {
render() {
diff --git a/scripts/jest/matchers/toWarnDev.js b/scripts/jest/matchers/toWarnDev.js
index 44ec4e56ed4a..780ab9c9cd92 100644
--- a/scripts/jest/matchers/toWarnDev.js
+++ b/scripts/jest/matchers/toWarnDev.js
@@ -5,7 +5,19 @@ const util = require('util');
const shouldIgnoreConsoleError = require('../shouldIgnoreConsoleError');
function normalizeCodeLocInfo(str) {
- return str && str.replace(/at .+?:\d+/g, 'at **');
+ if (typeof str !== 'string') {
+ return str;
+ }
+ // This special case exists only for the special source location in
+ // ReactElementValidator. That will go away if we remove source locations.
+ str = str.replace(/Check your code at .+?:\d+/g, 'Check your code at **');
+ // V8 format:
+ // at Component (/path/filename.js:123:45)
+ // React format:
+ // in Component (at filename.js:123)
+ return str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function(m, name) {
+ return '\n in ' + name + ' (at **)';
+ });
}
const createMatcherFor = (consoleMethod, matcherName) =>
diff --git a/scripts/rollup/validate/eslintrc.cjs.js b/scripts/rollup/validate/eslintrc.cjs.js
index 6e392ed42f49..008cbf1d7827 100644
--- a/scripts/rollup/validate/eslintrc.cjs.js
+++ b/scripts/rollup/validate/eslintrc.cjs.js
@@ -14,6 +14,7 @@ module.exports = {
WeakMap: true,
WeakSet: true,
Uint16Array: true,
+ Reflect: true,
// Vendor specific
MSApp: true,
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,
diff --git a/scripts/rollup/validate/eslintrc.fb.js b/scripts/rollup/validate/eslintrc.fb.js
index f8965a81ff49..b05619d10bd0 100644
--- a/scripts/rollup/validate/eslintrc.fb.js
+++ b/scripts/rollup/validate/eslintrc.fb.js
@@ -14,6 +14,7 @@ module.exports = {
WeakMap: true,
WeakSet: true,
Uint16Array: true,
+ Reflect: true,
// Vendor specific
MSApp: true,
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,
diff --git a/scripts/rollup/validate/eslintrc.rn.js b/scripts/rollup/validate/eslintrc.rn.js
index 9906dfb5b8ab..49b993ed90d7 100644
--- a/scripts/rollup/validate/eslintrc.rn.js
+++ b/scripts/rollup/validate/eslintrc.rn.js
@@ -13,6 +13,7 @@ module.exports = {
Proxy: true,
WeakMap: true,
WeakSet: true,
+ Reflect: true,
// Vendor specific
MSApp: true,
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,
diff --git a/scripts/rollup/validate/eslintrc.umd.js b/scripts/rollup/validate/eslintrc.umd.js
index 9537b41a357a..8dfe4ec5630c 100644
--- a/scripts/rollup/validate/eslintrc.umd.js
+++ b/scripts/rollup/validate/eslintrc.umd.js
@@ -13,6 +13,7 @@ module.exports = {
WeakMap: true,
WeakSet: true,
Uint16Array: true,
+ Reflect: true,
// Vendor specific
MSApp: true,
__REACT_DEVTOOLS_GLOBAL_HOOK__: true,