Parse any babel codeframe error as syntax error

Summary:
## Overview

This diff adds handling for syntax errors created using `buildCodeFrameError` which have a slightly different format than syntax errors thrown during transforms.

Changelog: [Internal]

Reviewed By: yungsters

Differential Revision: D18658502

fbshipit-source-id: 0836f2c16cdd57c10ed1e03dc7345d8e1ccf53f3
This commit is contained in:
Rick Hanlon
2019-11-27 09:25:04 -08:00
committed by Facebook Github Bot
parent a64e5bc251
commit 178f126d83
9 changed files with 336 additions and 45 deletions
@@ -21,7 +21,7 @@ import type {StackFrame} from '../NativeExceptionsManager';
export type CodeFrame = $ReadOnly<{|
content: string,
location: {
location: ?{
row: number,
column: number,
...
@@ -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: {
+69 -31
View File
@@ -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]),
};
}
@@ -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 (
<LogBoxInspectorSection heading="Source" action={<AppInfo />}>
<View style={styles.box}>
@@ -59,10 +70,8 @@ function LogBoxInspectorCodeFrame(props: Props): React.Node {
openFileInEditor(codeFrame.fileName, codeFrame.location?.row ?? 0);
}}>
<Text style={styles.fileText}>
{getFileName()} ({codeFrame.location.row}:
{codeFrame.location.column +
1 /* Code frame columns are zero indexed */}
)
{getFileName()}
{getLocation()}
</Text>
</LogBoxButton>
</View>
+2 -1
View File
@@ -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;
@@ -41,4 +41,22 @@ describe('LogBoxInspectorCodeFrame', () => {
expect(output).toMatchSnapshot();
});
it('should render a code frame without a location', () => {
const output = render.shallowRender(
<LogBoxInspectorCodeFrame
codeFrame={{
fileName: '/path/to/RKJSModules/Apps/CrashReact/CrashReactApp.js',
location: null,
content: ` 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |`,
}}
/>,
);
expect(output).toMatchSnapshot();
});
});
@@ -166,6 +166,20 @@ describe('LogBoxMessage', () => {
expect(output).toMatchSnapshot();
});
it('Should strip "TransformError " without breaking substitution', () => {
const output = render.shallowRender(
<LogBoxMessage
style={{}}
message={{
content: 'TransformError normal substitution normal',
substitutions: [{length: 12, offset: 22}],
}}
/>,
);
expect(output).toMatchSnapshot();
});
it('Should strip "Warning: " without breaking substitution', () => {
const output = render.shallowRender(
<LogBoxMessage
@@ -75,11 +75,88 @@ exports[`LogBoxInspectorCodeFrame should render a code frame 1`] = `
}
>
CrashReactApp.js
(
199
:
1
)
(199:1)
</Text>
</LogBoxButton>
</View>
</LogBoxInspectorSection>
`;
exports[`LogBoxInspectorCodeFrame should render a code frame without a location 1`] = `
<LogBoxInspectorSection
action={<AppInfo />}
heading="Source"
>
<View
style={
Object {
"backgroundColor": "rgba(51, 51, 51, 1)",
"borderRadius": 3,
"marginLeft": 10,
"marginRight": 10,
"marginTop": 5,
}
}
>
<View
style={
Object {
"borderBottomColor": "rgba(255, 255, 255, 0.1)",
"borderBottomWidth": 1,
"padding": 10,
}
}
>
<ScrollViewMock
horizontal={true}
>
<Ansi
style={
Object {
"color": "rgba(255, 255, 255, 1)",
"fontFamily": "Menlo",
"fontSize": 12,
"includeFontPadding": false,
"lineHeight": 20,
}
}
text=" 197 | });
198 |
> 199 | export default CrashReactApp;
| ^
200 |"
/>
</ScrollViewMock>
</View>
<LogBoxButton
backgroundColor={
Object {
"default": "transparent",
"pressed": "rgba(34, 34, 34, 1)",
}
}
onPress={[Function]}
style={
Object {
"paddingBottom": 10,
"paddingTop": 10,
}
}
>
<Text
style={
Object {
"color": "rgba(255, 255, 255, 0.5)",
"flex": 1,
"fontFamily": "Menlo",
"fontSize": 12,
"includeFontPadding": false,
"lineHeight": 16,
"textAlign": "center",
}
}
>
CrashReactApp.js
</Text>
</LogBoxButton>
</View>
@@ -1,5 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`LogBoxMessage Should strip "TransformError " without breaking substitution 1`] = `
<React.Fragment>
<Text>
normal
</Text>
<Text
style={Object {}}
>
substitution
</Text>
<Text>
normal
</Text>
</React.Fragment>
`;
exports[`LogBoxMessage Should strip "Warning: " without breaking substitution 1`] = `
<React.Fragment>
<Text>