@@ -63,6 +63,15 @@ function isStrictModeOverride(args: Array<any>): boolean {
6363 }
6464}
6565
66+ // We add a suffix to some frames that older versions of React didn't do.
67+ // To compare if it's equivalent we strip out the suffix to see if they're
68+ // still equivalent. Similarly, we sometimes use [] and sometimes () so we
69+ // strip them to for the comparison.
70+ const frameDiffs = / \( \< a n o n y m o u s \> \) $ | \@ u n k n o w n \: 0 \: 0 $ | \( | \) | \[ | \] / gm;
71+ function areStackTracesEqual ( a : string , b : string ) : boolean {
72+ return a . replace ( frameDiffs , '' ) === b . replace ( frameDiffs , '' ) ;
73+ }
74+
6675function restorePotentiallyModifiedArgs ( args : Array < any > ) : Array < any > {
6776 // If the arguments don't have any styles applied, then just copy
6877 if ( ! isStrictModeOverride ( args ) ) {
@@ -202,17 +211,11 @@ export function patch({
202211
203212 // $FlowFixMe[missing-local-annot]
204213 const overrideMethod = ( ...args ) => {
205- let shouldAppendWarningStack = false ;
206- if ( method !== 'log' ) {
207- if ( consoleSettingsRef . appendComponentStack ) {
208- const lastArg = args . length > 0 ? args [ args . length - 1 ] : null ;
209- const alreadyHasComponentStack =
210- typeof lastArg === 'string' && isStringComponentStack ( lastArg ) ;
211-
212- // If we are ever called with a string that already has a component stack,
213- // e.g. a React error/warning, don't append a second stack.
214- shouldAppendWarningStack = ! alreadyHasComponentStack ;
215- }
214+ let alreadyHasComponentStack = false ;
215+ if ( method !== 'log' && consoleSettingsRef . appendComponentStack ) {
216+ const lastArg = args . length > 0 ? args [ args . length - 1 ] : null ;
217+ alreadyHasComponentStack =
218+ typeof lastArg === 'string' && isStringComponentStack ( lastArg ) ; // The last argument should be a component stack.
216219 }
217220
218221 const shouldShowInlineWarningsAndErrors =
@@ -242,7 +245,7 @@ export function patch({
242245 }
243246
244247 if (
245- shouldAppendWarningStack &&
248+ consoleSettingsRef . appendComponentStack &&
246249 ! supportsNativeConsoleTasks ( current )
247250 ) {
248251 const componentStack = getStackByFiberInDevAndProd (
@@ -251,17 +254,60 @@ export function patch({
251254 ( currentDispatcherRef : any ) ,
252255 ) ;
253256 if ( componentStack !== '' ) {
254- if ( isStrictModeOverride ( args ) ) {
255- if ( __IS_FIREFOX__ ) {
256- args [ 0 ] = `${ args [ 0 ] } %s` ;
257- args . push ( componentStack ) ;
258- } else {
259- args [ 0 ] =
260- ANSI_STYLE_DIMMING_TEMPLATE_WITH_COMPONENT_STACK ;
261- args . push ( componentStack ) ;
257+ // Create a fake Error so that when we print it we get native source maps. Every
258+ // browser will print the .stack property of the error and then parse it back for source
259+ // mapping. Rather than print the internal slot. So it doesn't matter that the internal
260+ // slot doesn't line up.
261+ const fakeError = new Error ( '' ) ;
262+ // In Chromium, only the stack property is printed but in Firefox the <name>:<message>
263+ // gets printed so to make the colon make sense, we name it so we print Component Stack:
264+ // and similarly Safari leave an expandable slot.
265+ fakeError . name = 'Component Stack' ; // This gets printed
266+ // In Chromium, the stack property needs to start with ^[\w.]*Error\b to trigger stack
267+ // formatting. Otherwise it is left alone. So we prefix it. Otherwise we just override it
268+ // to our own stack.
269+ fakeError . stack =
270+ __IS_CHROME__ || __IS_EDGE__
271+ ? 'Error Component Stack:' + componentStack
272+ : componentStack ;
273+ if ( alreadyHasComponentStack ) {
274+ // Only modify the component stack if it matches what we would've added anyway.
275+ // Otherwise we assume it was a non-React stack.
276+ if (
277+ areStackTracesEqual (
278+ args [ args . length - 1 ] ,
279+ componentStack ,
280+ )
281+ ) {
282+ args [ args . length - 1 ] = fakeError ;
283+ if ( isStrictModeOverride ( args ) ) {
284+ if ( __IS_FIREFOX__ ) {
285+ args [ 0 ] = `${ args [ 0 ] } %o` ;
286+ } else {
287+ args [ 0 ] =
288+ ANSI_STYLE_DIMMING_TEMPLATE_WITH_COMPONENT_STACK ;
289+ }
290+ } else {
291+ const firstArg = args [ 0 ] ;
292+ if (
293+ args . length > 1 &&
294+ typeof firstArg === 'string' &&
295+ firstArg . endsWith ( '%s' )
296+ ) {
297+ args [ 0 ] = firstArg . slice ( 0 , firstArg . length - 2 ) ; // Strip the %s param
298+ }
299+ }
262300 }
263301 } else {
264- args . push ( componentStack ) ;
302+ args . push ( fakeError ) ;
303+ if ( isStrictModeOverride ( args ) ) {
304+ if ( __IS_FIREFOX__ ) {
305+ args [ 0 ] = `${ args [ 0 ] } %o` ;
306+ } else {
307+ args [ 0 ] =
308+ ANSI_STYLE_DIMMING_TEMPLATE_WITH_COMPONENT_STACK ;
309+ }
310+ }
265311 }
266312 }
267313 }
0 commit comments