Skip to content

Commit

Permalink
Add codeFrame to each warning and error in LogBox
Browse files Browse the repository at this point in the history
Summary:
This diff changes the LogBox to show the code frame for the first non-collapsed stack frame. Let me know what you think about this change!

Changelog: [Internal] LogBox changes

Reviewed By: rickhanlonii

Differential Revision: D18372456

fbshipit-source-id: ddf6d6c53ab28d11d8355f4cb1cb071a00a7366e
  • Loading branch information
cpojer authored and facebook-github-bot committed Nov 8, 2019
1 parent 5eddf1d commit fa4f23e
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 24 deletions.
19 changes: 16 additions & 3 deletions Libraries/Core/Devtools/symbolicateStackTrace.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,27 @@ let fetch;

import type {StackFrame} from '../NativeExceptionsManager';

export type CodeFrame = $ReadOnly<{|
content: string,
location: {
row: number,
column: number,
},
fileName: string,
|}>;

export type SymbolicatedStackTrace = $ReadOnly<{|
stack: Array<StackFrame>,
codeFrame: ?CodeFrame,
|}>;

function isSourcedFromDisk(sourcePath: string): boolean {
return !/^http/.test(sourcePath) && /[\\/]/.test(sourcePath);
}

async function symbolicateStackTrace(
stack: Array<StackFrame>,
): Promise<Array<StackFrame>> {
): Promise<SymbolicatedStackTrace> {
// RN currently lazy loads whatwg-fetch using a custom fetch module, which,
// when called for the first time, requires and re-exports 'whatwg-fetch'.
// However, when a dependency of the project tries to require whatwg-fetch
Expand Down Expand Up @@ -74,8 +88,7 @@ async function symbolicateStackTrace(
method: 'POST',
body: JSON.stringify({stack: stackCopy}),
});
const json = await response.json();
return json.stack;
return await response.json();
}

module.exports = symbolicateStackTrace;
2 changes: 1 addition & 1 deletion Libraries/Core/ExceptionsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function reportException(e: ExtendedError, isFatal: boolean) {
}
const symbolicateStackTrace = require('./Devtools/symbolicateStackTrace');
symbolicateStackTrace(stack)
.then(prettyStack => {
.then(({stack: prettyStack}) => {
if (prettyStack) {
NativeExceptionsManager.updateExceptionMessage(
data.message,
Expand Down
12 changes: 7 additions & 5 deletions Libraries/Core/__tests__/ExceptionsManager-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ describe('ExceptionsManager', () => {
};
});
// Make symbolication a no-op.
jest.mock('../Devtools/symbolicateStackTrace', () => {
return async function symbolicateStackTrace(stack) {
return stack;
};
});
jest.mock(
'../Devtools/symbolicateStackTrace',
() =>
async function symbolicateStackTrace(stack) {
return {stack};
},
);
jest.spyOn(console, 'error').mockImplementation(() => {});
ReactFiberErrorDialog = require('../ReactFiberErrorDialog');
NativeExceptionsManager = require('../NativeExceptionsManager').default;
Expand Down
32 changes: 26 additions & 6 deletions Libraries/LogBox/Data/LogBoxLog.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,33 @@ class LogBoxLog {
let aborted = false;

if (this.symbolicated.status !== 'COMPLETE') {
const updateStatus = (error: ?Error, stack: ?Stack): void => {
const updateStatus = (
error: ?Error,
stack: ?Stack,
codeFrame: ?CodeFrame,
): void => {
if (error != null) {
this.symbolicated = {error, stack: null, status: 'FAILED'};
this.symbolicated = {
error,
stack: null,
status: 'FAILED',
};
} else if (stack != null) {
this.symbolicated = {error: null, stack, status: 'COMPLETE'};
if (codeFrame) {
this.codeFrame = codeFrame;
}

this.symbolicated = {
error: null,
stack,
status: 'COMPLETE',
};
} else {
this.symbolicated = {error: null, stack: null, status: 'PENDING'};
this.symbolicated = {
error: null,
stack: null,
status: 'PENDING',
};
}
if (!aborted) {
if (callback != null) {
Expand All @@ -99,8 +119,8 @@ class LogBoxLog {

updateStatus(null, null);
LogBoxSymbolication.symbolicate(this.stack).then(
stack => {
updateStatus(null, stack);
data => {
updateStatus(null, data?.stack, data?.codeFrame);
},
error => {
updateStatus(error, null);
Expand Down
12 changes: 8 additions & 4 deletions Libraries/LogBox/Data/LogBoxSymbolication.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@
import symbolicateStackTrace from '../../Core/Devtools/symbolicateStackTrace';

import type {StackFrame} from '../../Core/NativeExceptionsManager';
import type {SymbolicatedStackTrace} from '../../Core/Devtools/symbolicateStackTrace';

export type Stack = Array<StackFrame>;

const cache: Map<Stack, Promise<Stack>> = new Map();
const cache: Map<Stack, Promise<SymbolicatedStackTrace>> = new Map();

/**
* Sanitize because sometimes, `symbolicateStackTrace` gives us invalid values.
*/
const sanitize = (maybeStack: mixed): Stack => {
const sanitize = ({
stack: maybeStack,
codeFrame,
}: SymbolicatedStackTrace): SymbolicatedStackTrace => {
if (!Array.isArray(maybeStack)) {
throw new Error('Expected stack to be an array.');
}
Expand Down Expand Up @@ -58,14 +62,14 @@ const sanitize = (maybeStack: mixed): Stack => {
collapse,
});
}
return stack;
return {stack, codeFrame};
};

export function deleteStack(stack: Stack): void {
cache.delete(stack);
}

export function symbolicate(stack: Stack): Promise<Stack> {
export function symbolicate(stack: Stack): Promise<SymbolicatedStackTrace> {
let promise = cache.get(stack);
if (promise == null) {
promise = symbolicateStackTrace(stack).then(sanitize);
Expand Down
10 changes: 6 additions & 4 deletions Libraries/LogBox/Data/__tests__/LogBoxLog-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
'use strict';

import type {StackFrame} from '../../../Core/NativeExceptionsManager';
import type {SymbolicatedStackTrace} from '../../../Core/Devtools/symbolicateStackTrace';

jest.mock('../LogBoxSymbolication', () => {
return {__esModule: true, symbolicate: jest.fn(), deleteStack: jest.fn()};
Expand All @@ -35,7 +36,7 @@ function getLogBoxLog() {
function getLogBoxSymbolication(): {|
symbolicate: JestMockFn<
$ReadOnlyArray<Array<StackFrame>>,
Promise<Array<StackFrame>>,
Promise<SymbolicatedStackTrace>,
>,
|} {
return (require('../LogBoxSymbolication'): any);
Expand All @@ -53,9 +54,10 @@ describe('LogBoxLog', () => {
beforeEach(() => {
jest.resetModules();

getLogBoxSymbolication().symbolicate.mockImplementation(async stack =>
createStack(stack.map(frame => `S(${frame.methodName})`)),
);
getLogBoxSymbolication().symbolicate.mockImplementation(async stack => ({
stack: createStack(stack.map(frame => `S(${frame.methodName})`)),
codeFrame: null,
}));
});

it('creates a LogBoxLog object', () => {
Expand Down
4 changes: 3 additions & 1 deletion Libraries/YellowBox/Data/YellowBoxSymbolication.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
const symbolicateStackTrace = require('../../Core/Devtools/symbolicateStackTrace');

import type {StackFrame} from '../../Core/NativeExceptionsManager';
import type {SymbolicatedStackTrace} from '../../Core/Devtools/symbolicateStackTrace';

type CacheKey = string;

Expand Down Expand Up @@ -45,7 +46,8 @@ const getCacheKey = (stack: Stack): CacheKey => {
/**
* Sanitize because sometimes, `symbolicateStackTrace` gives us invalid values.
*/
const sanitize = (maybeStack: mixed): Stack => {
const sanitize = (data: SymbolicatedStackTrace): Stack => {
const maybeStack = data?.stack;
if (!Array.isArray(maybeStack)) {
throw new Error('Expected stack to be an array.');
}
Expand Down

0 comments on commit fa4f23e

Please sign in to comment.