yeet
This commit is contained in:
480
node_modules/react-native/Libraries/LogBox/Data/LogBoxData.js
generated
vendored
Normal file
480
node_modules/react-native/Libraries/LogBox/Data/LogBoxData.js
generated
vendored
Normal file
@ -0,0 +1,480 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow strict-local
|
||||
* @format
|
||||
*/
|
||||
|
||||
('use strict');
|
||||
|
||||
import * as React from 'react';
|
||||
import LogBoxLog from './LogBoxLog';
|
||||
import {parseLogBoxException} from './parseLogBoxLog';
|
||||
import type {LogLevel} from './LogBoxLog';
|
||||
import type {
|
||||
Message,
|
||||
Category,
|
||||
ComponentStack,
|
||||
ExtendedExceptionData,
|
||||
} from './parseLogBoxLog';
|
||||
import parseErrorStack from '../../Core/Devtools/parseErrorStack';
|
||||
import type {ExtendedError} from '../../Core/Devtools/parseErrorStack';
|
||||
import NativeLogBox from '../../NativeModules/specs/NativeLogBox';
|
||||
export type LogBoxLogs = Set<LogBoxLog>;
|
||||
export type LogData = $ReadOnly<{|
|
||||
level: LogLevel,
|
||||
message: Message,
|
||||
category: Category,
|
||||
componentStack: ComponentStack,
|
||||
|}>;
|
||||
|
||||
export type Observer = (
|
||||
$ReadOnly<{|
|
||||
logs: LogBoxLogs,
|
||||
isDisabled: boolean,
|
||||
selectedLogIndex: number,
|
||||
|}>,
|
||||
) => void;
|
||||
|
||||
export type IgnorePattern = string | RegExp;
|
||||
|
||||
export type Subscription = $ReadOnly<{|
|
||||
unsubscribe: () => void,
|
||||
|}>;
|
||||
|
||||
export type WarningInfo = {|
|
||||
finalFormat: string,
|
||||
forceDialogImmediately: boolean,
|
||||
suppressDialog_LEGACY: boolean,
|
||||
suppressCompletely: boolean,
|
||||
monitorEvent: string | null,
|
||||
monitorListVersion: number,
|
||||
monitorSampleRate: number,
|
||||
|};
|
||||
|
||||
export type WarningFilter = (format: string) => WarningInfo;
|
||||
|
||||
type AppInfo = $ReadOnly<{|
|
||||
appVersion: string,
|
||||
engine: string,
|
||||
onPress?: ?() => void,
|
||||
|}>;
|
||||
|
||||
const observers: Set<{observer: Observer, ...}> = new Set();
|
||||
const ignorePatterns: Set<IgnorePattern> = new Set();
|
||||
let appInfo: ?() => AppInfo = null;
|
||||
let logs: LogBoxLogs = new Set();
|
||||
let updateTimeout = null;
|
||||
let _isDisabled = false;
|
||||
let _selectedIndex = -1;
|
||||
|
||||
let warningFilter: WarningFilter = function(format) {
|
||||
return {
|
||||
finalFormat: format,
|
||||
forceDialogImmediately: false,
|
||||
suppressDialog_LEGACY: true,
|
||||
suppressCompletely: false,
|
||||
monitorEvent: 'unknown',
|
||||
monitorListVersion: 0,
|
||||
monitorSampleRate: 1,
|
||||
};
|
||||
};
|
||||
|
||||
const LOGBOX_ERROR_MESSAGE =
|
||||
'An error was thrown when attempting to render log messages via LogBox.';
|
||||
|
||||
function getNextState() {
|
||||
return {
|
||||
logs,
|
||||
isDisabled: _isDisabled,
|
||||
selectedLogIndex: _selectedIndex,
|
||||
};
|
||||
}
|
||||
|
||||
export function reportLogBoxError(
|
||||
error: ExtendedError,
|
||||
componentStack?: string,
|
||||
): void {
|
||||
const ExceptionsManager = require('../../Core/ExceptionsManager');
|
||||
|
||||
error.forceRedbox = true;
|
||||
error.message = `${LOGBOX_ERROR_MESSAGE}\n\n${error.message}`;
|
||||
if (componentStack != null) {
|
||||
error.componentStack = componentStack;
|
||||
}
|
||||
ExceptionsManager.handleException(error, /* isFatal */ true);
|
||||
}
|
||||
|
||||
export function isLogBoxErrorMessage(message: string): boolean {
|
||||
return typeof message === 'string' && message.includes(LOGBOX_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
export function isMessageIgnored(message: string): boolean {
|
||||
for (const pattern of ignorePatterns) {
|
||||
if (
|
||||
(pattern instanceof RegExp && pattern.test(message)) ||
|
||||
(typeof pattern === 'string' && message.includes(pattern))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleUpdate(): void {
|
||||
if (updateTimeout == null) {
|
||||
updateTimeout = setImmediate(() => {
|
||||
updateTimeout = null;
|
||||
const nextState = getNextState();
|
||||
observers.forEach(({observer}) => observer(nextState));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function appendNewLog(newLog) {
|
||||
// We don't want to store these logs because they trigger a
|
||||
// state update whenever we add them to the store, which is
|
||||
// expensive to noisy logs. If we later want to display these
|
||||
// we will store them in a different state object.
|
||||
if (isMessageIgnored(newLog.message.content)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the next log has the same category as the previous one
|
||||
// then we want to roll it up into the last log in the list
|
||||
// by incrementing the count (simar to how Chrome does it).
|
||||
const lastLog = Array.from(logs).pop();
|
||||
if (lastLog && lastLog.category === newLog.category) {
|
||||
lastLog.incrementCount();
|
||||
handleUpdate();
|
||||
return;
|
||||
}
|
||||
|
||||
if (newLog.level === 'fatal') {
|
||||
// If possible, to avoid jank, we don't want to open the error before
|
||||
// it's symbolicated. To do that, we optimistically wait for
|
||||
// sybolication for up to a second before adding the log.
|
||||
const OPTIMISTIC_WAIT_TIME = 1000;
|
||||
|
||||
let addPendingLog = () => {
|
||||
logs.add(newLog);
|
||||
if (_selectedIndex <= 0) {
|
||||
setSelectedLog(logs.size - 1);
|
||||
} else {
|
||||
handleUpdate();
|
||||
}
|
||||
addPendingLog = null;
|
||||
};
|
||||
|
||||
const optimisticTimeout = setTimeout(() => {
|
||||
if (addPendingLog) {
|
||||
addPendingLog();
|
||||
}
|
||||
}, OPTIMISTIC_WAIT_TIME);
|
||||
|
||||
newLog.symbolicate(status => {
|
||||
if (addPendingLog && status !== 'PENDING') {
|
||||
addPendingLog();
|
||||
clearTimeout(optimisticTimeout);
|
||||
} else if (status !== 'PENDING') {
|
||||
// The log has already been added but we need to trigger a render.
|
||||
handleUpdate();
|
||||
}
|
||||
});
|
||||
} else if (newLog.level === 'syntax') {
|
||||
logs.add(newLog);
|
||||
setSelectedLog(logs.size - 1);
|
||||
} else {
|
||||
logs.add(newLog);
|
||||
handleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
export function addLog(log: LogData): void {
|
||||
const errorForStackTrace = new Error();
|
||||
|
||||
// Parsing logs are expensive so we schedule this
|
||||
// otherwise spammy logs would pause rendering.
|
||||
setImmediate(() => {
|
||||
try {
|
||||
// TODO: Use Error.captureStackTrace on Hermes
|
||||
const stack = parseErrorStack(errorForStackTrace);
|
||||
|
||||
appendNewLog(
|
||||
new LogBoxLog({
|
||||
level: log.level,
|
||||
message: log.message,
|
||||
isComponentError: false,
|
||||
stack,
|
||||
category: log.category,
|
||||
componentStack: log.componentStack,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
reportLogBoxError(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function addException(error: ExtendedExceptionData): void {
|
||||
// Parsing logs are expensive so we schedule this
|
||||
// otherwise spammy logs would pause rendering.
|
||||
setImmediate(() => {
|
||||
try {
|
||||
appendNewLog(new LogBoxLog(parseLogBoxException(error)));
|
||||
} catch (loggingError) {
|
||||
reportLogBoxError(loggingError);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function symbolicateLogNow(log: LogBoxLog) {
|
||||
log.symbolicate(() => {
|
||||
handleUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
export function retrySymbolicateLogNow(log: LogBoxLog) {
|
||||
log.retrySymbolicate(() => {
|
||||
handleUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
export function symbolicateLogLazy(log: LogBoxLog) {
|
||||
log.symbolicate();
|
||||
}
|
||||
|
||||
export function clear(): void {
|
||||
if (logs.size > 0) {
|
||||
logs = new Set();
|
||||
setSelectedLog(-1);
|
||||
}
|
||||
}
|
||||
|
||||
export function setSelectedLog(proposedNewIndex: number): void {
|
||||
const oldIndex = _selectedIndex;
|
||||
let newIndex = proposedNewIndex;
|
||||
|
||||
const logArray = Array.from(logs);
|
||||
let index = logArray.length - 1;
|
||||
while (index >= 0) {
|
||||
// The latest syntax error is selected and displayed before all other logs.
|
||||
if (logArray[index].level === 'syntax') {
|
||||
newIndex = index;
|
||||
break;
|
||||
}
|
||||
index -= 1;
|
||||
}
|
||||
_selectedIndex = newIndex;
|
||||
handleUpdate();
|
||||
if (NativeLogBox) {
|
||||
setTimeout(() => {
|
||||
if (oldIndex < 0 && newIndex >= 0) {
|
||||
NativeLogBox.show();
|
||||
} else if (oldIndex >= 0 && newIndex < 0) {
|
||||
NativeLogBox.hide();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
export function clearWarnings(): void {
|
||||
const newLogs = Array.from(logs).filter(log => log.level !== 'warn');
|
||||
if (newLogs.length !== logs.size) {
|
||||
logs = new Set(newLogs);
|
||||
setSelectedLog(-1);
|
||||
handleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
export function clearErrors(): void {
|
||||
const newLogs = Array.from(logs).filter(
|
||||
log => log.level !== 'error' && log.level !== 'fatal',
|
||||
);
|
||||
if (newLogs.length !== logs.size) {
|
||||
logs = new Set(newLogs);
|
||||
setSelectedLog(-1);
|
||||
}
|
||||
}
|
||||
|
||||
export function dismiss(log: LogBoxLog): void {
|
||||
if (logs.has(log)) {
|
||||
logs.delete(log);
|
||||
handleUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
export function setWarningFilter(filter: WarningFilter): void {
|
||||
warningFilter = filter;
|
||||
}
|
||||
|
||||
export function setAppInfo(info: () => AppInfo): void {
|
||||
appInfo = info;
|
||||
}
|
||||
|
||||
export function getAppInfo(): ?AppInfo {
|
||||
return appInfo != null ? appInfo() : null;
|
||||
}
|
||||
|
||||
export function checkWarningFilter(format: string): WarningInfo {
|
||||
return warningFilter(format);
|
||||
}
|
||||
|
||||
export function addIgnorePatterns(
|
||||
patterns: $ReadOnlyArray<IgnorePattern>,
|
||||
): void {
|
||||
// The same pattern may be added multiple times, but adding a new pattern
|
||||
// can be expensive so let's find only the ones that are new.
|
||||
const newPatterns = patterns.filter((pattern: IgnorePattern) => {
|
||||
if (pattern instanceof RegExp) {
|
||||
for (const existingPattern of ignorePatterns.entries()) {
|
||||
if (
|
||||
existingPattern instanceof RegExp &&
|
||||
existingPattern.toString() === pattern.toString()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return !ignorePatterns.has(pattern);
|
||||
});
|
||||
|
||||
if (newPatterns.length === 0) {
|
||||
return;
|
||||
}
|
||||
for (const pattern of newPatterns) {
|
||||
ignorePatterns.add(pattern);
|
||||
|
||||
// We need to recheck all of the existing logs.
|
||||
// This allows adding an ignore pattern anywhere in the codebase.
|
||||
// Without this, if you ignore a pattern after the a log is created,
|
||||
// then we would keep showing the log.
|
||||
logs = new Set(
|
||||
Array.from(logs).filter(log => !isMessageIgnored(log.message.content)),
|
||||
);
|
||||
}
|
||||
handleUpdate();
|
||||
}
|
||||
|
||||
export function setDisabled(value: boolean): void {
|
||||
if (value === _isDisabled) {
|
||||
return;
|
||||
}
|
||||
_isDisabled = value;
|
||||
handleUpdate();
|
||||
}
|
||||
|
||||
export function isDisabled(): boolean {
|
||||
return _isDisabled;
|
||||
}
|
||||
|
||||
export function observe(observer: Observer): Subscription {
|
||||
const subscription = {observer};
|
||||
observers.add(subscription);
|
||||
|
||||
observer(getNextState());
|
||||
|
||||
return {
|
||||
unsubscribe(): void {
|
||||
observers.delete(subscription);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
type Props = $ReadOnly<{||}>;
|
||||
type State = $ReadOnly<{|
|
||||
logs: LogBoxLogs,
|
||||
isDisabled: boolean,
|
||||
hasError: boolean,
|
||||
selectedLogIndex: number,
|
||||
|}>;
|
||||
|
||||
type SubscribedComponent = React.AbstractComponent<
|
||||
$ReadOnly<{|
|
||||
logs: $ReadOnlyArray<LogBoxLog>,
|
||||
isDisabled: boolean,
|
||||
selectedLogIndex: number,
|
||||
|}>,
|
||||
>;
|
||||
|
||||
export function withSubscription(
|
||||
WrappedComponent: SubscribedComponent,
|
||||
): React.AbstractComponent<{||}> {
|
||||
class LogBoxStateSubscription extends React.Component<Props, State> {
|
||||
static getDerivedStateFromError() {
|
||||
return {hasError: true};
|
||||
}
|
||||
|
||||
componentDidCatch(err: Error, errorInfo: {componentStack: string, ...}) {
|
||||
reportLogBoxError(err, errorInfo.componentStack);
|
||||
}
|
||||
|
||||
_subscription: ?Subscription;
|
||||
|
||||
state = {
|
||||
logs: new Set(),
|
||||
isDisabled: false,
|
||||
hasError: false,
|
||||
selectedLogIndex: -1,
|
||||
};
|
||||
|
||||
render(): React.Node {
|
||||
if (this.state.hasError) {
|
||||
// This happens when the component failed to render, in which case we delegate to the native redbox.
|
||||
// We can't show anyback fallback UI here, because the error may be with <View> or <Text>.
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<WrappedComponent
|
||||
logs={Array.from(this.state.logs)}
|
||||
isDisabled={this.state.isDisabled}
|
||||
selectedLogIndex={this.state.selectedLogIndex}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount(): void {
|
||||
this._subscription = observe(data => {
|
||||
this.setState(data);
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount(): void {
|
||||
if (this._subscription != null) {
|
||||
this._subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
_handleDismiss = (): void => {
|
||||
// Here we handle the cases when the log is dismissed and it
|
||||
// was either the last log, or when the current index
|
||||
// is now outside the bounds of the log array.
|
||||
const {selectedLogIndex, logs: stateLogs} = this.state;
|
||||
const logsArray = Array.from(stateLogs);
|
||||
if (selectedLogIndex != null) {
|
||||
if (logsArray.length - 1 <= 0) {
|
||||
setSelectedLog(-1);
|
||||
} else if (selectedLogIndex >= logsArray.length - 1) {
|
||||
setSelectedLog(selectedLogIndex - 1);
|
||||
}
|
||||
|
||||
dismiss(logsArray[selectedLogIndex]);
|
||||
}
|
||||
};
|
||||
|
||||
_handleMinimize = (): void => {
|
||||
setSelectedLog(-1);
|
||||
};
|
||||
|
||||
_handleSetSelectedLog = (index: number): void => {
|
||||
setSelectedLog(index);
|
||||
};
|
||||
}
|
||||
|
||||
return LogBoxStateSubscription;
|
||||
}
|
Reference in New Issue
Block a user