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;
|
||||
}
|
144
node_modules/react-native/Libraries/LogBox/Data/LogBoxLog.js
generated
vendored
Normal file
144
node_modules/react-native/Libraries/LogBox/Data/LogBoxLog.js
generated
vendored
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* 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 LogBoxSymbolication from './LogBoxSymbolication';
|
||||
|
||||
import type {
|
||||
Category,
|
||||
Message,
|
||||
ComponentStack,
|
||||
CodeFrame,
|
||||
} from './parseLogBoxLog';
|
||||
import type {Stack} from './LogBoxSymbolication';
|
||||
|
||||
type SymbolicationStatus = 'NONE' | 'PENDING' | 'COMPLETE' | 'FAILED';
|
||||
|
||||
export type LogLevel = 'warn' | 'error' | 'fatal' | 'syntax';
|
||||
|
||||
export type LogBoxLogData = $ReadOnly<{|
|
||||
level: LogLevel,
|
||||
type?: ?string,
|
||||
message: Message,
|
||||
stack: Stack,
|
||||
category: string,
|
||||
componentStack: ComponentStack,
|
||||
codeFrame?: ?CodeFrame,
|
||||
isComponentError: boolean,
|
||||
|}>;
|
||||
|
||||
class LogBoxLog {
|
||||
message: Message;
|
||||
type: ?string;
|
||||
category: Category;
|
||||
componentStack: ComponentStack;
|
||||
stack: Stack;
|
||||
count: number;
|
||||
level: LogLevel;
|
||||
codeFrame: ?CodeFrame;
|
||||
isComponentError: boolean;
|
||||
symbolicated:
|
||||
| $ReadOnly<{|error: null, stack: null, status: 'NONE'|}>
|
||||
| $ReadOnly<{|error: null, stack: null, status: 'PENDING'|}>
|
||||
| $ReadOnly<{|error: null, stack: Stack, status: 'COMPLETE'|}>
|
||||
| $ReadOnly<{|error: Error, stack: null, status: 'FAILED'|}> = {
|
||||
error: null,
|
||||
stack: null,
|
||||
status: 'NONE',
|
||||
};
|
||||
|
||||
constructor(data: LogBoxLogData) {
|
||||
this.level = data.level;
|
||||
this.type = data.type;
|
||||
this.message = data.message;
|
||||
this.stack = data.stack;
|
||||
this.category = data.category;
|
||||
this.componentStack = data.componentStack;
|
||||
this.codeFrame = data.codeFrame;
|
||||
this.isComponentError = data.isComponentError;
|
||||
this.count = 1;
|
||||
}
|
||||
|
||||
incrementCount(): void {
|
||||
this.count += 1;
|
||||
}
|
||||
|
||||
getAvailableStack(): Stack {
|
||||
return this.symbolicated.status === 'COMPLETE'
|
||||
? this.symbolicated.stack
|
||||
: this.stack;
|
||||
}
|
||||
|
||||
retrySymbolicate(callback?: (status: SymbolicationStatus) => void): void {
|
||||
if (this.symbolicated.status !== 'COMPLETE') {
|
||||
LogBoxSymbolication.deleteStack(this.stack);
|
||||
this.handleSymbolicate(callback);
|
||||
}
|
||||
}
|
||||
|
||||
symbolicate(callback?: (status: SymbolicationStatus) => void): void {
|
||||
if (this.symbolicated.status === 'NONE') {
|
||||
this.handleSymbolicate(callback);
|
||||
}
|
||||
}
|
||||
|
||||
handleSymbolicate(callback?: (status: SymbolicationStatus) => void): void {
|
||||
if (this.symbolicated.status !== 'PENDING') {
|
||||
this.updateStatus(null, null, null, callback);
|
||||
LogBoxSymbolication.symbolicate(this.stack).then(
|
||||
data => {
|
||||
this.updateStatus(null, data?.stack, data?.codeFrame, callback);
|
||||
},
|
||||
error => {
|
||||
this.updateStatus(error, null, null, callback);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(
|
||||
error: ?Error,
|
||||
stack: ?Stack,
|
||||
codeFrame: ?CodeFrame,
|
||||
callback?: (status: SymbolicationStatus) => void,
|
||||
): void {
|
||||
const lastStatus = this.symbolicated.status;
|
||||
if (error != null) {
|
||||
this.symbolicated = {
|
||||
error,
|
||||
stack: null,
|
||||
status: 'FAILED',
|
||||
};
|
||||
} else if (stack != null) {
|
||||
if (codeFrame) {
|
||||
this.codeFrame = codeFrame;
|
||||
}
|
||||
|
||||
this.symbolicated = {
|
||||
error: null,
|
||||
stack,
|
||||
status: 'COMPLETE',
|
||||
};
|
||||
} else {
|
||||
this.symbolicated = {
|
||||
error: null,
|
||||
stack: null,
|
||||
status: 'PENDING',
|
||||
};
|
||||
}
|
||||
|
||||
if (callback && lastStatus !== this.symbolicated.status) {
|
||||
callback(this.symbolicated.status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LogBoxLog;
|
64
node_modules/react-native/Libraries/LogBox/Data/LogBoxSymbolication.js
generated
vendored
Normal file
64
node_modules/react-native/Libraries/LogBox/Data/LogBoxSymbolication.js
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
||||
/**
|
||||
* 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 symbolicateStackTrace from '../../Core/Devtools/symbolicateStackTrace';
|
||||
|
||||
import type {StackFrame} from '../../Core/NativeExceptionsManager';
|
||||
import type {SymbolicatedStackTrace} from '../../Core/Devtools/symbolicateStackTrace';
|
||||
|
||||
export type Stack = Array<StackFrame>;
|
||||
|
||||
const cache: Map<Stack, Promise<SymbolicatedStackTrace>> = new Map();
|
||||
|
||||
/**
|
||||
* Sanitize because sometimes, `symbolicateStackTrace` gives us invalid values.
|
||||
*/
|
||||
const sanitize = ({
|
||||
stack: maybeStack,
|
||||
codeFrame,
|
||||
}: SymbolicatedStackTrace): SymbolicatedStackTrace => {
|
||||
if (!Array.isArray(maybeStack)) {
|
||||
throw new Error('Expected stack to be an array.');
|
||||
}
|
||||
const stack = [];
|
||||
for (const maybeFrame of maybeStack) {
|
||||
let collapse = false;
|
||||
if ('collapse' in maybeFrame) {
|
||||
if (typeof maybeFrame.collapse !== 'boolean') {
|
||||
throw new Error('Expected stack frame `collapse` to be a boolean.');
|
||||
}
|
||||
collapse = maybeFrame.collapse;
|
||||
}
|
||||
stack.push({
|
||||
column: maybeFrame.column,
|
||||
file: maybeFrame.file,
|
||||
lineNumber: maybeFrame.lineNumber,
|
||||
methodName: maybeFrame.methodName,
|
||||
collapse,
|
||||
});
|
||||
}
|
||||
return {stack, codeFrame};
|
||||
};
|
||||
|
||||
export function deleteStack(stack: Stack): void {
|
||||
cache.delete(stack);
|
||||
}
|
||||
|
||||
export function symbolicate(stack: Stack): Promise<SymbolicatedStackTrace> {
|
||||
let promise = cache.get(stack);
|
||||
if (promise == null) {
|
||||
promise = symbolicateStackTrace(stack).then(sanitize);
|
||||
cache.set(stack, promise);
|
||||
}
|
||||
|
||||
return promise;
|
||||
}
|
336
node_modules/react-native/Libraries/LogBox/Data/parseLogBoxLog.js
generated
vendored
Normal file
336
node_modules/react-native/Libraries/LogBox/Data/parseLogBoxLog.js
generated
vendored
Normal file
@ -0,0 +1,336 @@
|
||||
/**
|
||||
* 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 UTFSequence from '../../UTFSequence';
|
||||
import stringifySafe from '../../Utilities/stringifySafe';
|
||||
import type {ExceptionData} from '../../Core/NativeExceptionsManager';
|
||||
import type {LogBoxLogData} from './LogBoxLog';
|
||||
|
||||
const BABEL_TRANSFORM_ERROR_FORMAT = /^(?:TransformError )?(?:SyntaxError: |ReferenceError: )(.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/;
|
||||
const BABEL_CODE_FRAME_ERROR_FORMAT = /^(?:TransformError )?(?:.*):? (?:.*?)(\/.*): ([\s\S]+?)\n([ >]{2}[\d\s]+ \|[\s\S]+|\u{001b}[\s\S]+)/u;
|
||||
const METRO_ERROR_FORMAT = /^(?:InternalError Metro has encountered an error:) (.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/u;
|
||||
|
||||
export type ExtendedExceptionData = ExceptionData & {
|
||||
isComponentError: boolean,
|
||||
...
|
||||
};
|
||||
export type Category = string;
|
||||
export type CodeFrame = $ReadOnly<{|
|
||||
content: string,
|
||||
location: ?{
|
||||
row: number,
|
||||
column: number,
|
||||
...
|
||||
},
|
||||
fileName: string,
|
||||
|}>;
|
||||
export type Message = $ReadOnly<{|
|
||||
content: string,
|
||||
substitutions: $ReadOnlyArray<
|
||||
$ReadOnly<{|
|
||||
length: number,
|
||||
offset: number,
|
||||
|}>,
|
||||
>,
|
||||
|}>;
|
||||
|
||||
export type ComponentStack = $ReadOnlyArray<CodeFrame>;
|
||||
|
||||
const SUBSTITUTION = UTFSequence.BOM + '%s';
|
||||
|
||||
export function parseInterpolation(
|
||||
args: $ReadOnlyArray<mixed>,
|
||||
): $ReadOnly<{|
|
||||
category: Category,
|
||||
message: Message,
|
||||
|}> {
|
||||
const categoryParts = [];
|
||||
const contentParts = [];
|
||||
const substitutionOffsets = [];
|
||||
|
||||
const remaining = [...args];
|
||||
if (typeof remaining[0] === 'string') {
|
||||
const formatString = String(remaining.shift());
|
||||
const formatStringParts = formatString.split('%s');
|
||||
const substitutionCount = formatStringParts.length - 1;
|
||||
const substitutions = remaining.splice(0, substitutionCount);
|
||||
|
||||
let categoryString = '';
|
||||
let contentString = '';
|
||||
|
||||
let substitutionIndex = 0;
|
||||
for (const formatStringPart of formatStringParts) {
|
||||
categoryString += formatStringPart;
|
||||
contentString += formatStringPart;
|
||||
|
||||
if (substitutionIndex < substitutionCount) {
|
||||
if (substitutionIndex < substitutions.length) {
|
||||
// Don't stringify a string type.
|
||||
// It adds quotation mark wrappers around the string,
|
||||
// which causes the LogBox to look odd.
|
||||
const substitution =
|
||||
typeof substitutions[substitutionIndex] === 'string'
|
||||
? substitutions[substitutionIndex]
|
||||
: stringifySafe(substitutions[substitutionIndex]);
|
||||
substitutionOffsets.push({
|
||||
length: substitution.length,
|
||||
offset: contentString.length,
|
||||
});
|
||||
|
||||
categoryString += SUBSTITUTION;
|
||||
contentString += substitution;
|
||||
} else {
|
||||
substitutionOffsets.push({
|
||||
length: 2,
|
||||
offset: contentString.length,
|
||||
});
|
||||
|
||||
categoryString += '%s';
|
||||
contentString += '%s';
|
||||
}
|
||||
|
||||
substitutionIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
categoryParts.push(categoryString);
|
||||
contentParts.push(contentString);
|
||||
}
|
||||
|
||||
const remainingArgs = remaining.map(arg => {
|
||||
// Don't stringify a string type.
|
||||
// It adds quotation mark wrappers around the string,
|
||||
// which causes the LogBox to look odd.
|
||||
return typeof arg === 'string' ? arg : stringifySafe(arg);
|
||||
});
|
||||
categoryParts.push(...remainingArgs);
|
||||
contentParts.push(...remainingArgs);
|
||||
|
||||
return {
|
||||
category: categoryParts.join(' '),
|
||||
message: {
|
||||
content: contentParts.join(' '),
|
||||
substitutions: substitutionOffsets,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function parseComponentStack(message: string): ComponentStack {
|
||||
return message
|
||||
.split(/\n {4}in /g)
|
||||
.map(s => {
|
||||
if (!s) {
|
||||
return null;
|
||||
}
|
||||
const match = s.match(/(.*) \(at (.*\.js):([\d]+)\)/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let [content, fileName, row] = match.slice(1);
|
||||
return {
|
||||
content,
|
||||
fileName,
|
||||
location: {column: -1, row: parseInt(row, 10)},
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function parseLogBoxException(
|
||||
error: ExtendedExceptionData,
|
||||
): LogBoxLogData {
|
||||
const message =
|
||||
error.originalMessage != null ? error.originalMessage : 'Unknown';
|
||||
|
||||
const metroInternalError = message.match(METRO_ERROR_FORMAT);
|
||||
if (metroInternalError) {
|
||||
const [
|
||||
content,
|
||||
fileName,
|
||||
row,
|
||||
column,
|
||||
codeFrame,
|
||||
] = metroInternalError.slice(1);
|
||||
|
||||
return {
|
||||
level: 'fatal',
|
||||
type: 'Metro Error',
|
||||
stack: [],
|
||||
isComponentError: false,
|
||||
componentStack: [],
|
||||
codeFrame: {
|
||||
fileName,
|
||||
location: {
|
||||
row: parseInt(row, 10),
|
||||
column: parseInt(column, 10),
|
||||
},
|
||||
content: codeFrame,
|
||||
},
|
||||
message: {
|
||||
content,
|
||||
substitutions: [],
|
||||
},
|
||||
category: `${fileName}-${row}-${column}`,
|
||||
};
|
||||
}
|
||||
|
||||
const babelTransformError = message.match(BABEL_TRANSFORM_ERROR_FORMAT);
|
||||
if (babelTransformError) {
|
||||
// Transform errors are thrown from inside the Babel transformer.
|
||||
const [
|
||||
fileName,
|
||||
content,
|
||||
row,
|
||||
column,
|
||||
codeFrame,
|
||||
] = babelTransformError.slice(1);
|
||||
|
||||
return {
|
||||
level: 'syntax',
|
||||
stack: [],
|
||||
isComponentError: false,
|
||||
componentStack: [],
|
||||
codeFrame: {
|
||||
fileName,
|
||||
location: {
|
||||
row: parseInt(row, 10),
|
||||
column: parseInt(column, 10),
|
||||
},
|
||||
content: codeFrame,
|
||||
},
|
||||
message: {
|
||||
content,
|
||||
substitutions: [],
|
||||
},
|
||||
category: `${fileName}-${row}-${column}`,
|
||||
};
|
||||
}
|
||||
|
||||
const babelCodeFrameError = message.match(BABEL_CODE_FRAME_ERROR_FORMAT);
|
||||
|
||||
if (babelCodeFrameError) {
|
||||
// Codeframe errors are thrown from any use of buildCodeFrameError.
|
||||
const [fileName, content, codeFrame] = babelCodeFrameError.slice(1);
|
||||
return {
|
||||
level: 'syntax',
|
||||
stack: [],
|
||||
isComponentError: false,
|
||||
componentStack: [],
|
||||
codeFrame: {
|
||||
fileName,
|
||||
location: null, // We are not given the location.
|
||||
content: codeFrame,
|
||||
},
|
||||
message: {
|
||||
content,
|
||||
substitutions: [],
|
||||
},
|
||||
category: `${fileName}-${1}-${1}`,
|
||||
};
|
||||
}
|
||||
|
||||
if (message.match(/^TransformError /)) {
|
||||
return {
|
||||
level: 'syntax',
|
||||
stack: error.stack,
|
||||
isComponentError: error.isComponentError,
|
||||
componentStack: [],
|
||||
message: {
|
||||
content: message,
|
||||
substitutions: [],
|
||||
},
|
||||
category: message,
|
||||
};
|
||||
}
|
||||
|
||||
const componentStack = error.componentStack;
|
||||
if (error.isFatal || error.isComponentError) {
|
||||
return {
|
||||
level: 'fatal',
|
||||
stack: error.stack,
|
||||
isComponentError: error.isComponentError,
|
||||
componentStack:
|
||||
componentStack != null ? parseComponentStack(componentStack) : [],
|
||||
...parseInterpolation([message]),
|
||||
};
|
||||
}
|
||||
|
||||
if (componentStack != null) {
|
||||
// It is possible that console errors have a componentStack.
|
||||
return {
|
||||
level: 'error',
|
||||
stack: error.stack,
|
||||
isComponentError: error.isComponentError,
|
||||
componentStack: parseComponentStack(componentStack),
|
||||
...parseInterpolation([message]),
|
||||
};
|
||||
}
|
||||
|
||||
// Most `console.error` calls won't have a componentStack. We parse them like
|
||||
// regular logs which have the component stack burried in the message.
|
||||
return {
|
||||
level: 'error',
|
||||
stack: error.stack,
|
||||
isComponentError: error.isComponentError,
|
||||
...parseLogBoxLog([message]),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseLogBoxLog(
|
||||
args: $ReadOnlyArray<mixed>,
|
||||
): {|
|
||||
componentStack: ComponentStack,
|
||||
category: Category,
|
||||
message: Message,
|
||||
|} {
|
||||
const message = args[0];
|
||||
let argsWithoutComponentStack = [];
|
||||
let componentStack = [];
|
||||
|
||||
// Extract component stack from warnings like "Some warning%s".
|
||||
if (
|
||||
typeof message === 'string' &&
|
||||
message.slice(-2) === '%s' &&
|
||||
args.length > 0
|
||||
) {
|
||||
const lastArg = args[args.length - 1];
|
||||
// Does it look like React component stack? " in ..."
|
||||
if (typeof lastArg === 'string' && /\s{4}in/.test(lastArg)) {
|
||||
argsWithoutComponentStack = args.slice(0, -1);
|
||||
argsWithoutComponentStack[0] = message.slice(0, -2);
|
||||
componentStack = parseComponentStack(lastArg);
|
||||
}
|
||||
}
|
||||
|
||||
if (componentStack.length === 0) {
|
||||
// Try finding the component stack elsewhere.
|
||||
for (const arg of args) {
|
||||
if (typeof arg === 'string' && /\n {4}in /.exec(arg)) {
|
||||
// Strip out any messages before the component stack.
|
||||
const messageEndIndex = arg.indexOf('\n in ');
|
||||
if (messageEndIndex > 0) {
|
||||
argsWithoutComponentStack.push(arg.slice(0, messageEndIndex));
|
||||
}
|
||||
|
||||
componentStack = parseComponentStack(arg);
|
||||
} else {
|
||||
argsWithoutComponentStack.push(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...parseInterpolation(argsWithoutComponentStack),
|
||||
componentStack,
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user