mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
75deeb32fe
Summary: The React team wants exceptions thrown during render to pop over the screen as fatals. Changelog: [Internal] Reviewed By: motiz88 Differential Revision: D18439258 fbshipit-source-id: dded7b9d93271c1a4eff682be521c7567dfe7d7e
217 lines
5.6 KiB
JavaScript
217 lines
5.6 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 UTFSequence from '../../UTFSequence';
|
|
import stringifySafe from '../../Utilities/stringifySafe';
|
|
import type {LogLevel} from './LogBoxLog';
|
|
import type {ExceptionData} from '../../Core/NativeExceptionsManager';
|
|
import type {Stack} from './LogBoxSymbolication';
|
|
|
|
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<
|
|
$ReadOnly<{|
|
|
component: string,
|
|
location: string,
|
|
|}>,
|
|
>;
|
|
|
|
const SUBSTITUTION = UTFSequence.BOM + '%s';
|
|
|
|
export function parseCategory(
|
|
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;
|
|
}
|
|
let [component, location] = s.split(/ \(at /);
|
|
if (!location) {
|
|
[component, location] = s.split(/ \(/);
|
|
}
|
|
return {component, location: location && location.replace(')', '')};
|
|
})
|
|
.filter(Boolean);
|
|
}
|
|
|
|
export function parseLogBoxException(
|
|
error: ExtendedExceptionData,
|
|
): {|
|
|
level: LogLevel,
|
|
category: Category,
|
|
message: Message,
|
|
codeFrame?: CodeFrame,
|
|
stack: Stack,
|
|
componentStack?: ComponentStack,
|
|
|} {
|
|
const message =
|
|
error.originalMessage != null ? error.originalMessage : 'Unknown';
|
|
const match = message.match(
|
|
/(?:TransformError )?(?:SyntaxError: )(.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/,
|
|
);
|
|
|
|
if (!match) {
|
|
return {
|
|
level: error.isFatal ? 'fatal' : 'error',
|
|
stack: error.stack,
|
|
componentStack:
|
|
error.componentStack != null
|
|
? parseComponentStack(error.componentStack)
|
|
: [],
|
|
...parseCategory([message]),
|
|
};
|
|
}
|
|
|
|
const [fileName, content, row, column, codeFrame] = match.slice(1);
|
|
return {
|
|
level: 'syntax',
|
|
stack: [],
|
|
codeFrame: {
|
|
fileName,
|
|
location: {
|
|
row: parseInt(row, 10),
|
|
column: parseInt(column, 10),
|
|
},
|
|
content: codeFrame,
|
|
},
|
|
message: {
|
|
content,
|
|
substitutions: [],
|
|
},
|
|
category: `${fileName}-${row}-${column}`,
|
|
};
|
|
}
|
|
|
|
export function parseLogBoxLog(
|
|
args: $ReadOnlyArray<mixed>,
|
|
): {|
|
|
componentStack: ComponentStack,
|
|
category: Category,
|
|
message: Message,
|
|
|} {
|
|
// This detects a very narrow case of a simple log string,
|
|
// with a component stack appended by React DevTools.
|
|
// In this case, we extract the component stack,
|
|
// because LogBox formats those pleasantly.
|
|
// If there are other substitutions or formatting,
|
|
// this could potentially corrupt the data, but there
|
|
// are currently no known cases of that happening.
|
|
let componentStack = [];
|
|
let argsWithoutComponentStack = [];
|
|
for (const arg of args) {
|
|
if (typeof arg === 'string' && /^\n {4}in/.exec(arg)) {
|
|
componentStack = parseComponentStack(arg);
|
|
} else {
|
|
argsWithoutComponentStack.push(arg);
|
|
}
|
|
}
|
|
|
|
return {
|
|
...parseCategory(argsWithoutComponentStack),
|
|
componentStack,
|
|
};
|
|
}
|