mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
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:
committed by
Facebook Github Bot
parent
a64e5bc251
commit
178f126d83
@@ -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: {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user