diff --git a/Libraries/Core/Devtools/symbolicateStackTrace.js b/Libraries/Core/Devtools/symbolicateStackTrace.js index 26d00d72d76..ef128990867 100644 --- a/Libraries/Core/Devtools/symbolicateStackTrace.js +++ b/Libraries/Core/Devtools/symbolicateStackTrace.js @@ -21,7 +21,7 @@ import type {StackFrame} from '../NativeExceptionsManager'; export type CodeFrame = $ReadOnly<{| content: string, - location: { + location: ?{ row: number, column: number, ... diff --git a/Libraries/LogBox/Data/__tests__/parseLogBoxLog-test.js b/Libraries/LogBox/Data/__tests__/parseLogBoxLog-test.js index 889ec9ef79e..cc114c222b2 100644 --- a/Libraries/LogBox/Data/__tests__/parseLogBoxLog-test.js +++ b/Libraries/LogBox/Data/__tests__/parseLogBoxLog-test.js @@ -177,7 +177,32 @@ describe('parseLogBoxLog', () => { }); }); - it('parses a syntax error', () => { + it('parses a transform error as a fatal', () => { + const error = { + message: 'TransformError failed to transform file.', + originalMessage: 'TransformError failed to transform file.', + name: '', + isComponentError: false, + componentStack: '', + stack: [], + id: 0, + isFatal: true, + }; + + expect(parseLogBoxException(error)).toEqual({ + level: 'syntax', + isComponentError: false, + message: { + content: 'TransformError failed to transform file.', + substitutions: [], + }, + stack: [], + componentStack: [], + category: 'TransformError failed to transform file.', + }); + }); + + it('parses a babel transform syntax error', () => { const error = { message: ` @@ -269,6 +294,99 @@ describe('parseLogBoxLog', () => { }); }); + it('parses a babel codeframe error', () => { + const error = { + message: `TransformError RKJSModules/Apps/CrashReact/CrashReactApp.js: /path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js: The first argument to \`fbRemoteAsset\` is "null_state_glyphs", but the requested asset is missing from the local metadata. Either the asset does not exist or the metadata is not up-to-date. + +Please follow the instructions at: fburl.com/rn-remote-assets + 197 | }); + 198 | +> 199 | export default CrashReactApp; + | ^ + 200 |`, + originalMessage: `TransformError RKJSModules/Apps/CrashReact/CrashReactApp.js: /path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js: The first argument to \`fbRemoteAsset\` is "null_state_glyphs", but the requested asset is missing from the local metadata. Either the asset does not exist or the metadata is not up-to-date. + +Please follow the instructions at: fburl.com/rn-remote-assets + 197 | }); + 198 | +> 199 | export default CrashReactApp; + | ^ + 200 |`, + name: '', + isComponentError: false, + componentStack: '', + stack: [], + id: 0, + isFatal: true, + }; + + expect(parseLogBoxException(error)).toEqual({ + level: 'syntax', + isComponentError: false, + codeFrame: { + fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js', + location: null, + content: ` 197 | }); + 198 | +> 199 | export default CrashReactApp; + | ^ + 200 |`, + }, + message: { + content: `The first argument to \`fbRemoteAsset\` is "null_state_glyphs", but the requested asset is missing from the local metadata. Either the asset does not exist or the metadata is not up-to-date. + +Please follow the instructions at: fburl.com/rn-remote-assets`, + substitutions: [], + }, + stack: [], + componentStack: [], + category: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js-1-1', + }); + }); + + it('parses a babel codeframe error with ansi', () => { + const error = { + message: `TransformError RKJSModules/Apps/CrashReact/CrashReactApp.js: /path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js: The first argument to \`fbRemoteAsset\` is "null_state_glyphs", but the requested asset is missing from the local metadata. Either the asset does not exist or the metadata is not up-to-date. + +Please follow the instructions at: fburl.com/rn-remote-assets + 197 | }); + 198 | +> 199 | export default CrashReactApp; + | ^ + 200 |`, + originalMessage: `TransformError RKJSModules/Apps/CrashReact/CrashReactApp.js: /path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js: The first argument to \`fbRemoteAsset\` is "null_state_glyphs", but the requested asset is missing from the local metadata. Either the asset does not exist or the metadata is not up-to-date. + +Please follow the instructions at: fburl.com/rn-remote-assets +\u001b[0m \u001b[90m 46 | \u001b[39m headline\u001b[33m=\u001b[39m\u001b[32m"CrashReact Error Boundary"\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 47 | \u001b[39m body\u001b[33m=\u001b[39m{\u001b[32m\`\${this.state.errorMessage}\`\u001b[39m}\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 48 | \u001b[39m icon\u001b[33m=\u001b[39m{fbRemoteAsset(\u001b[32m'null_state_glyphs'\u001b[39m\u001b[33m,\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m | \u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 49 | \u001b[39m name\u001b[33m:\u001b[39m \u001b[32m'codexxx'\u001b[39m\u001b[33m,\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 50 | \u001b[39m size\u001b[33m:\u001b[39m \u001b[32m'112'\u001b[39m\u001b[33m,\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 51 | \u001b[39m })}\u001b[0m`, + name: '', + isComponentError: false, + componentStack: '', + stack: [], + id: 0, + isFatal: true, + }; + + expect(parseLogBoxException(error)).toEqual({ + level: 'syntax', + isComponentError: false, + codeFrame: { + fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js', + location: null, + content: + "\u001b[0m \u001b[90m 46 | \u001b[39m headline\u001b[33m=\u001b[39m\u001b[32m\"CrashReact Error Boundary\"\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 47 | \u001b[39m body\u001b[33m=\u001b[39m{\u001b[32m`${this.state.errorMessage}`\u001b[39m}\u001b[0m\n\u001b[0m\u001b[31m\u001b[1m>\u001b[22m\u001b[39m\u001b[90m 48 | \u001b[39m icon\u001b[33m=\u001b[39m{fbRemoteAsset(\u001b[32m'null_state_glyphs'\u001b[39m\u001b[33m,\u001b[39m {\u001b[0m\n\u001b[0m \u001b[90m | \u001b[39m \u001b[31m\u001b[1m^\u001b[22m\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 49 | \u001b[39m name\u001b[33m:\u001b[39m \u001b[32m'codexxx'\u001b[39m\u001b[33m,\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 50 | \u001b[39m size\u001b[33m:\u001b[39m \u001b[32m'112'\u001b[39m\u001b[33m,\u001b[39m\u001b[0m\n\u001b[0m \u001b[90m 51 | \u001b[39m })}\u001b[0m", + }, + message: { + content: `The first argument to \`fbRemoteAsset\` is "null_state_glyphs", but the requested asset is missing from the local metadata. Either the asset does not exist or the metadata is not up-to-date. + +Please follow the instructions at: fburl.com/rn-remote-assets`, + substitutions: [], + }, + stack: [], + componentStack: [], + category: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js-1-1', + }); + }); + it('parses a error log', () => { const error = { id: 0, @@ -402,7 +520,7 @@ describe('parseLogBoxLog', () => { }); }); - it('a malformed syntax error falls back to a fatal', () => { + it('a malformed syntax error falls back to a syntax error', () => { const error = { id: 0, isFatal: true, @@ -426,7 +544,7 @@ describe('parseLogBoxLog', () => { }; expect(parseLogBoxException(error)).toEqual({ - level: 'fatal', + level: 'syntax', category: "TransformError SyntaxError: /path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js: 'import' and 'export' may only appear at the top level (199:0)", message: { diff --git a/Libraries/LogBox/Data/parseLogBoxLog.js b/Libraries/LogBox/Data/parseLogBoxLog.js index 6a601759cc5..402c9d30341 100644 --- a/Libraries/LogBox/Data/parseLogBoxLog.js +++ b/Libraries/LogBox/Data/parseLogBoxLog.js @@ -15,6 +15,9 @@ 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; + export type ExtendedExceptionData = ExceptionData & { isComponentError: boolean, ... @@ -22,7 +25,7 @@ export type ExtendedExceptionData = ExceptionData & { export type Category = string; export type CodeFrame = $ReadOnly<{| content: string, - location: { + location: ?{ row: number, column: number, ... @@ -145,42 +148,77 @@ export function parseLogBoxException( ): LogBoxLogData { const message = error.originalMessage != null ? error.originalMessage : 'Unknown'; - const match = message.match( - /(?:TransformError )?(?:SyntaxError: |ReferenceError: )(.*): (.*) \((\d+):(\d+)\)\n\n([\s\S]+)/, - ); - if (!match) { + 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: error.isFatal || error.isComponentError ? 'fatal' : 'error', - stack: error.stack, - isComponentError: error.isComponentError, - componentStack: - error.componentStack != null - ? parseComponentStack(error.componentStack) - : [], - ...parseCategory([message]), + 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 [fileName, content, row, column, codeFrame] = match.slice(1); - return { - level: 'syntax', - stack: [], - isComponentError: false, - componentStack: [], - codeFrame: { - fileName, - location: { - row: parseInt(row, 10), - column: parseInt(column, 10), + 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, }, - content: codeFrame, - }, - message: { - content, - substitutions: [], - }, - category: `${fileName}-${row}-${column}`, + message: { + content, + substitutions: [], + }, + category: `${fileName}-${1}-${1}`, + }; + } + + const level = message.match(/^TransformError /) + ? 'syntax' + : error.isFatal || error.isComponentError + ? 'fatal' + : 'error'; + + return { + level: level, + stack: error.stack, + isComponentError: error.isComponentError, + componentStack: + error.componentStack != null + ? parseComponentStack(error.componentStack) + : [], + ...parseCategory([message]), }; } diff --git a/Libraries/LogBox/UI/LogBoxInspectorCodeFrame.js b/Libraries/LogBox/UI/LogBoxInspectorCodeFrame.js index 50e0dbc1f4d..ee07a2bbc51 100644 --- a/Libraries/LogBox/UI/LogBoxInspectorCodeFrame.js +++ b/Libraries/LogBox/UI/LogBoxInspectorCodeFrame.js @@ -41,6 +41,17 @@ function LogBoxInspectorCodeFrame(props: Props): React.Node { return codeFrame.fileName; } + function getLocation() { + const location = codeFrame.location; + if (location != null) { + return ` (${location.row}:${ + location.column + 1 /* Code frame columns are zero indexed */ + })`; + } + + return null; + } + return ( }> @@ -59,10 +70,8 @@ function LogBoxInspectorCodeFrame(props: Props): React.Node { openFileInEditor(codeFrame.fileName, codeFrame.location?.row ?? 0); }}> - {getFileName()} ({codeFrame.location.row}: - {codeFrame.location.column + - 1 /* Code frame columns are zero indexed */} - ) + {getFileName()} + {getLocation()} diff --git a/Libraries/LogBox/UI/LogBoxMessage.js b/Libraries/LogBox/UI/LogBoxMessage.js index 8bd2740a2ea..dd300c12a02 100644 --- a/Libraries/LogBox/UI/LogBoxMessage.js +++ b/Libraries/LogBox/UI/LogBoxMessage.js @@ -24,7 +24,8 @@ type Props = { ... }; -const cleanContent = content => content.replace(/(Warning|Error): /g, ''); +const cleanContent = content => + content.replace(/^(TransformError |Warning: (Warning: )?|Error: )/g, ''); function LogBoxMessage(props: Props): React.Node { const {content, substitutions}: Message = props.message; diff --git a/Libraries/LogBox/UI/__tests__/LogBoxInspectorCodeFrame-test.js b/Libraries/LogBox/UI/__tests__/LogBoxInspectorCodeFrame-test.js index 7228553951f..16480ba822d 100644 --- a/Libraries/LogBox/UI/__tests__/LogBoxInspectorCodeFrame-test.js +++ b/Libraries/LogBox/UI/__tests__/LogBoxInspectorCodeFrame-test.js @@ -41,4 +41,22 @@ describe('LogBoxInspectorCodeFrame', () => { expect(output).toMatchSnapshot(); }); + + it('should render a code frame without a location', () => { + const output = render.shallowRender( + 199 | export default CrashReactApp; + | ^ + 200 |`, + }} + />, + ); + + expect(output).toMatchSnapshot(); + }); }); diff --git a/Libraries/LogBox/UI/__tests__/LogBoxMessage-test.js b/Libraries/LogBox/UI/__tests__/LogBoxMessage-test.js index 2e215b55855..98e6e356c47 100644 --- a/Libraries/LogBox/UI/__tests__/LogBoxMessage-test.js +++ b/Libraries/LogBox/UI/__tests__/LogBoxMessage-test.js @@ -166,6 +166,20 @@ describe('LogBoxMessage', () => { expect(output).toMatchSnapshot(); }); + it('Should strip "TransformError " without breaking substitution', () => { + const output = render.shallowRender( + , + ); + + expect(output).toMatchSnapshot(); + }); + it('Should strip "Warning: " without breaking substitution', () => { const output = render.shallowRender( CrashReactApp.js - ( - 199 - : - 1 - ) + (199:1) + + + + +`; + +exports[`LogBoxInspectorCodeFrame should render a code frame without a location 1`] = ` +} + heading="Source" +> + + + + + + + + + CrashReactApp.js diff --git a/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxMessage-test.js.snap b/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxMessage-test.js.snap index 94379a0b8fc..9f9260a0429 100644 --- a/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxMessage-test.js.snap +++ b/Libraries/LogBox/UI/__tests__/__snapshots__/LogBoxMessage-test.js.snap @@ -1,5 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`LogBoxMessage Should strip "TransformError " without breaking substitution 1`] = ` + + + normal + + + substitution + + + normal + + +`; + exports[`LogBoxMessage Should strip "Warning: " without breaking substitution 1`] = `