Skip to content
This repository has been archived by the owner on Jan 11, 2023. It is now read-only.

Commit

Permalink
Use renderStacktrace prop if provided to render Error rep stacktrace (#…
Browse files Browse the repository at this point in the history
…7140)

* Use renderStacktrace prop if provided to render Error rep stacktrace

This will allow us to use the same component
in the console and in reps for example.
If the prop is not passed, it defers to the
current stacktrace rendering.

* Update error.js
  • Loading branch information
nchevobbe committed Oct 25, 2018
1 parent b70953a commit 90e0f6f
Show file tree
Hide file tree
Showing 3 changed files with 222 additions and 54 deletions.
150 changes: 96 additions & 54 deletions packages/devtools-reps/src/reps/error.js
Expand Up @@ -20,7 +20,9 @@ const IGNORED_SOURCE_URLS = ["debugger eval code"];
ErrorRep.propTypes = {
object: PropTypes.object.isRequired,
// @TODO Change this to Object.values when supported in Node's version of V8
mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key]))
mode: PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
// An optional function that will be used to render the Error stacktrace.
renderStacktrace: PropTypes.func
};

function ErrorRep(props) {
Expand Down Expand Up @@ -52,7 +54,10 @@ function ErrorRep(props) {
}

if (preview.stack && props.mode !== MODE.TINY) {
content.push("\n", getStacktraceElements(props, preview));
const stacktrace = props.renderStacktrace
? props.renderStacktrace(parseStackString(preview.stack))
: getStacktraceElements(props, preview);
content.push("\n", stacktrace);
}

return span(
Expand Down Expand Up @@ -86,10 +91,85 @@ function getStacktraceElements(props, preview) {
return stack;
}

const isStacktraceALongString = isLongString(preview.stack);
const stackString = isStacktraceALongString
? preview.stack.initial
: preview.stack;
parseStackString(preview.stack).forEach((frame, index, frames) => {
let onLocationClick;
const {
filename,
lineNumber,
columnNumber,
functionName,
location
} = frame;

if (
props.onViewSourceInDebugger &&
!IGNORED_SOURCE_URLS.includes(filename)
) {
onLocationClick = e => {
// Don't trigger ObjectInspector expand/collapse.
e.stopPropagation();
props.onViewSourceInDebugger({
url: filename,
line: lineNumber,
column: columnNumber
});
};
}

stack.push(
"\t",
span(
{
key: `fn${index}`,
className: "objectBox-stackTrace-fn"
},
cleanFunctionName(functionName)
),
" ",
span(
{
key: `location${index}`,
className: "objectBox-stackTrace-location",
onClick: onLocationClick,
title: onLocationClick
? `View source in debugger → ${location}`
: undefined
},
location
),
"\n"
);
});

return span(
{
key: "stack",
className: "objectBox-stackTrace-grid"
},
stack
);
}

/**
* Parse a string that should represent a stack trace and returns an array of
* the frames. The shape of the frames are extremely important as they can then
* be processed here or in the toolbox by other components.
* @param {String} stack
* @returns {Array} Array of frames, which are object with the following shape:
* - {String} filename
* - {String} functionName
* - {String} location
* - {Number} columnNumber
* - {Number} lineNumber
*/
function parseStackString(stack) {
const res = [];
if (!stack) {
return res;
}

const isStacktraceALongString = isLongString(stack);
const stackString = isStacktraceALongString ? stack.initial : stack;

stackString.split("\n").forEach((frame, index, frames) => {
if (!frame) {
Expand Down Expand Up @@ -128,62 +208,24 @@ function getStacktraceElements(props, preview) {
functionName = "<anonymous>";
}

let onLocationClick;
// Given the input: "scriptLocation:2:100"
// Result:
// ["scriptLocation:2:100", "scriptLocation", "2", "100"]
const locationParts = location.match(/^(.*):(\d+):(\d+)$/);

if (
props.onViewSourceInDebugger &&
location &&
locationParts &&
!IGNORED_SOURCE_URLS.includes(locationParts[1])
) {
const [, url, line, column] = locationParts;
onLocationClick = e => {
// Don't trigger ObjectInspector expand/collapse.
e.stopPropagation();
props.onViewSourceInDebugger({
url,
line: Number(line),
column: Number(column)
});
};
if (location && locationParts) {
const [, filename, line, column] = locationParts;
res.push({
filename,
functionName,
location,
columnNumber: Number(column),
lineNumber: Number(line)
});
}

stack.push(
"\t",
span(
{
key: `fn${index}`,
className: "objectBox-stackTrace-fn"
},
cleanFunctionName(functionName)
),
" ",
span(
{
key: `location${index}`,
className: "objectBox-stackTrace-location",
onClick: onLocationClick,
title: onLocationClick
? `View source in debugger → ${location}`
: undefined
},
location
),
"\n"
);
});

return span(
{
key: "stack",
className: "objectBox-stackTrace-grid"
},
stack
);
return res;
}

// Registration
Expand Down
82 changes: 82 additions & 0 deletions packages/devtools-reps/src/reps/tests/__snapshots__/error.js.snap
Expand Up @@ -712,3 +712,85 @@ exports[`Error - longString stacktrace renders as expected 1`] = `
</span>
</span>
`;

exports[`Error - renderStacktrace prop uses renderStacktrace prop when provided 1`] = `
<span
className="objectBox-stackTrace"
data-link-actor-id="server1.conn1.child1/obj1021"
>
Error: "bar"
<li
className="frame"
>
Function errorBar called from debugger eval code:6:15
</li>
<li
className="frame"
>
Function errorFoo called from debugger eval code:3:3
</li>
<li
className="frame"
>
Function &lt;anonymous&gt; called from debugger eval code:8:1
</li>
</span>
`;

exports[`Error - renderStacktrace prop uses renderStacktrace with longString errors too 1`] = `
<span
className="objectBox-stackTrace"
data-link-actor-id="server1.conn1.child1/obj33"
>
InternalError: "too much recursion"
<li
className="frame"
>
Function execute/AppComponent&lt;/AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:32:1
</li>
<li
className="frame"
>
Function execute/AppComponent&lt;/AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21
</li>
<li
className="frame"
>
Function execute/AppComponent&lt;/AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21
</li>
<li
className="frame"
>
Function execute/AppComponent&lt;/AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21
</li>
<li
className="frame"
>
Function execute/AppComponent&lt;/AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21
</li>
<li
className="frame"
>
Function execute/AppComponent&lt;/AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21
</li>
<li
className="frame"
>
Function execute/AppComponent&lt;/AppComponent.prototype.doStuff called from https://angular-3eqab4.stackblitz.io/tmp/appfiles/src/app/app.component.ts:33:21
</li>
</span>
`;
44 changes: 44 additions & 0 deletions packages/devtools-reps/src/reps/tests/error.js
Expand Up @@ -11,6 +11,7 @@ const { expectActorAttribute } = require("./test-helpers");
const { ErrorRep } = REPS;
const { MODE } = require("../constants");
const stubs = require("../stubs/error");
const dom = require("react-dom-factories");

describe("Error - Simple error", () => {
// Test object = `new Error("Error message")`
Expand Down Expand Up @@ -497,3 +498,46 @@ describe("Error - stacktrace location click", () => {
});
});
});

describe("Error - renderStacktrace prop", () => {
it("uses renderStacktrace prop when provided", () => {
const stub = stubs.get("MultilineStackError");

const renderedComponent = shallow(
ErrorRep.rep({
object: stub,
renderStacktrace: frames => {
return frames.map(frame =>
dom.li(
{ className: "frame" },
`Function ${frame.functionName} called from ${frame.filename}:${
frame.lineNumber
}:${frame.columnNumber}\n`
)
);
}
})
);
expect(renderedComponent).toMatchSnapshot();
});

it("uses renderStacktrace with longString errors too", () => {
const stub = stubs.get("longString stack Error - cut-off location");
const renderedComponent = shallow(
ErrorRep.rep({
object: stub,
renderStacktrace: frames => {
return frames.map(frame =>
dom.li(
{ className: "frame" },
`Function ${frame.functionName} called from ${frame.filename}:${
frame.lineNumber
}:${frame.columnNumber}\n`
)
);
}
})
);
expect(renderedComponent).toMatchSnapshot();
});
});

0 comments on commit 90e0f6f

Please sign in to comment.