Files
react/packages/shared/consoleWithStackDev.js
Sebastian Markbåge 4dcdf21325 [Fiber] Prefix owner stacks with the current stack at the console call (#29697)
This information is available in the regular stack but since that's
hidden behind an expando and our appended stack to logs is not hidden,
it hides the most important frames like the name of the current
component.

This is closer to what happens to the native stack.

We only include stacks if they're within a ReactFiberCallUserSpace call
frame. This should be most that have a current fiber but this is
critical to filtering out most React frames if the regular node_modules
filter doesn't work.

Most React warnings fire during the rendering phase and not inside a
user space function but some do like hooks warnings and setState in
render. This feature is more important if we port this to React DevTools
appending stacks to all logs where it's likely to originate from inside
a component and you want the line within that component to immediately
part of the visible stack.

One thing that kind sucks is that we don't have a reliable way to
exclude React internal stack frames. We filter node_modules but it might
not match. For other cases I try hard to only track the stack frame at
the root of React (e.g. immediately inside createElement) until the
ReactFiberCallUserSpace so we don't need the filtering to work. In this
case it's hard to achieve the same thing though. This is easier in RDT
because we have the start/end line and parsing of stack traces so we can
use that to exclude internals but that's a lot of code/complexity for
shipping within the library.

For example in Safari:

<img width="590" alt="Screenshot 2024-05-31 at 6 15 27 PM"
src="https://github.com/facebook/react/assets/63648/2820c8c0-8a03-42e9-8678-8348f66b051a">

Ideally warnOnUseFormStateInDev and useFormState wouldn't be included
since they're React internals. Before this change, the Counter.js line
also wasn't included though which points to exactly where the error is
within the user code.

(Note Server Components have V8 formatted lines and Client Components
have JSC formatted lines.)
2024-06-03 12:26:38 -04:00

79 lines
2.7 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {enableOwnerStacks} from 'shared/ReactFeatureFlags';
let suppressWarning = false;
export function setSuppressWarning(newSuppressWarning) {
if (__DEV__) {
suppressWarning = newSuppressWarning;
}
}
// In DEV, calls to console.warn and console.error get replaced
// by calls to these methods by a Babel plugin.
//
// In PROD (or in packages without access to React internals),
// they are left as they are instead.
export function warn(format, ...args) {
if (__DEV__) {
if (!suppressWarning) {
printWarning('warn', format, args, new Error('react-stack-top-frame'));
}
}
}
export function error(format, ...args) {
if (__DEV__) {
if (!suppressWarning) {
printWarning('error', format, args, new Error('react-stack-top-frame'));
}
}
}
// eslint-disable-next-line react-internal/no-production-logging
const supportsCreateTask = __DEV__ && enableOwnerStacks && !!console.createTask;
function printWarning(level, format, args, currentStack) {
// When changing this logic, you might want to also
// update consoleWithStackDev.www.js as well.
if (__DEV__) {
const isErrorLogger =
format === '%s\n\n%s\n' || format === '%o\n\n%s\n\n%s\n';
if (!supportsCreateTask && ReactSharedInternals.getCurrentStack) {
// We only add the current stack to the console when createTask is not supported.
// Since createTask requires DevTools to be open to work, this means that stacks
// can be lost while DevTools isn't open but we can't detect this.
const stack = ReactSharedInternals.getCurrentStack(currentStack);
if (stack !== '') {
format += '%s';
args = args.concat([stack]);
}
}
if (isErrorLogger) {
// Don't prefix our default logging formatting in ReactFiberErrorLoggger.
// Don't toString the arguments.
args.unshift(format);
} else {
// TODO: Remove this prefix and stop toStringing in the wrapper and
// instead do it at each callsite as needed.
// Careful: RN currently depends on this prefix
// eslint-disable-next-line react-internal/safe-string-coercion
args = args.map(item => String(item));
args.unshift('Warning: ' + format);
}
// We intentionally don't use spread (or .apply) directly because it
// breaks IE9: https://github.com/facebook/react/issues/13610
// eslint-disable-next-line react-internal/no-production-logging
Function.prototype.apply.call(console[level], console, args);
}
}