react-native-macos/Libraries/LogBox/LogBox.js

252 строки
6.7 KiB
JavaScript

/**
* 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
* @format
*/
'use strict';
import * as React from 'react';
import Platform from '../Utilities/Platform';
import RCTLog from '../Utilities/RCTLog';
import LogBoxContainer from './UI/LogBoxContainer';
import * as LogBoxData from './Data/LogBoxData';
import {parseLogBoxLog} from './Data/parseLogBoxLog';
import type {LogBoxLogs, Subscription, IgnorePattern} from './Data/LogBoxData';
import LogBoxLog from './Data/LogBoxLog';
type Props = $ReadOnly<{||}>;
type State = {|
logs: LogBoxLogs,
isDisabled: boolean,
hasError: boolean,
selectedLogIndex: number,
|};
let LogBoxComponent;
/**
* LogBox displays logs in the app.
*/
if (__DEV__) {
// LogBox needs to insert itself early,
// in order to access the component stacks appended by React DevTools.
const {error, warn} = console;
let errorImpl = error.bind(console);
let warnImpl = warn.bind(console);
(console: any).error = function(...args) {
errorImpl(...args);
};
(console: any).warn = function(...args) {
warnImpl(...args);
};
LogBoxComponent = class LogBox extends React.Component<Props, State> {
static getDerivedStateFromError() {
return {hasError: true};
}
componentDidCatch(err, errorInfo) {
LogBoxData.reportLogBoxError(err, errorInfo.componentStack);
}
// TODO: deprecated, replace with ignoreLogs
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
LogBox.ignoreLogs(patterns);
}
static ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
LogBoxData.addIgnorePatterns(patterns);
}
static install(): void {
errorImpl = function(...args) {
registerError(...args);
};
warnImpl = function(...args) {
registerWarning(...args);
};
if ((console: any).disableLogBox === true) {
LogBoxData.setDisabled(true);
}
(Object.defineProperty: any)(console, 'disableLogBox', {
configurable: true,
get: () => LogBoxData.isDisabled(),
set: value => LogBoxData.setDisabled(value),
});
if (Platform.isTesting) {
(console: any).disableLogBox = true;
}
RCTLog.setWarningHandler((...args) => {
registerWarning(...args);
});
}
static uninstall(): void {
errorImpl = error;
warnImpl = warn;
delete (console: any).disableLogBox;
}
_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 this.state.logs == null ? null : (
<LogBoxContainer
onDismiss={this._handleDismiss}
onDismissWarns={LogBoxData.clearWarnings}
onDismissErrors={LogBoxData.clearErrors}
logs={this.state.logs}
isDisabled={this.state.isDisabled}
selectedLogIndex={this.state.selectedLogIndex}
setSelectedLog={this._handleSetSelectedLog}
/>
);
}
componentDidMount(): void {
this._subscription = LogBoxData.observe(data => {
this.setState(data);
});
}
componentWillUnmount(): void {
if (this._subscription != null) {
this._subscription.unsubscribe();
}
}
_handleDismiss(log: LogBoxLog): void {
LogBoxData.dismiss(log);
}
_handleSetSelectedLog(index: number): void {
LogBoxData.setSelectedLog(index);
}
};
const registerWarning = (...args): void => {
try {
// This is carried over from the old YellowBox, but it is not clear why.
if (typeof args[0] !== 'string' || !args[0].startsWith('(ADVICE)')) {
const {category, message, componentStack} = parseLogBoxLog(args);
if (!LogBoxData.isMessageIgnored(message.content)) {
// Be sure to pass LogBox warnings through.
warn.call(console, ...args);
if (!LogBoxData.isLogBoxErrorMessage(message.content)) {
LogBoxData.addLog({
level: 'warn',
category,
message,
componentStack,
});
}
}
}
} catch (err) {
LogBoxData.reportLogBoxError(err);
}
};
const registerError = (...args): void => {
try {
// Only show LogBox for the `warning` module, otherwise pass through and skip.
if (typeof args[0] !== 'string' || !args[0].startsWith('Warning: ')) {
error.call(console, ...args);
return;
}
const format = args[0].replace('Warning: ', '');
const filterResult = LogBoxData.checkWarningFilter(format);
if (filterResult.suppressCompletely) {
return;
}
let level = 'error';
if (filterResult.suppressDialog_LEGACY === true) {
level = 'warn';
} else if (filterResult.forceDialogImmediately === true) {
level = 'fatal'; // Do not downgrade. These are real bugs with same severity as throws.
}
// Unfortunately, we need to add the Warning: prefix back for downstream dependencies.
args[0] = `Warning: ${filterResult.finalFormat}`;
const {category, message, componentStack} = parseLogBoxLog(args);
if (!LogBoxData.isMessageIgnored(message.content)) {
// Be sure to pass LogBox errors through.
error.call(console, ...args);
if (!LogBoxData.isLogBoxErrorMessage(message.content)) {
LogBoxData.addLog({
level,
category,
message,
componentStack,
});
}
}
} catch (err) {
LogBoxData.reportLogBoxError(err);
}
};
} else {
LogBoxComponent = class extends React.Component<Props, State> {
// TODO: deprecated, replace with ignoreLogs
static ignoreWarnings(patterns: $ReadOnlyArray<IgnorePattern>): void {
// Do nothing.
}
static ignoreLogs(patterns: $ReadOnlyArray<IgnorePattern>): void {
// Do nothing.
}
static install(): void {
// Do nothing.
}
static uninstall(): void {
// Do nothing.
}
render(): React.Node {
return null;
}
};
}
module.exports = (LogBoxComponent: Class<React.Component<Props, State>> & {
// TODO: deprecated, replace with ignoreLogs
ignoreWarnings($ReadOnlyArray<IgnorePattern>): void,
ignoreLogs($ReadOnlyArray<IgnorePattern>): void,
install(): void,
uninstall(): void,
});