123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336 |
- /**
- * 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,
- };
- }
|