Skip to content

Commit

Permalink
errors: annotate frames for prepareStackTrace()
Browse files Browse the repository at this point in the history
When source-maps are enabled, overriding Error.prepareStackTrace()
is again supported.  Error.prepareStackTrace() will now be passed
stack frames with the methods getOriginalFileName(),
getOriginalLineNumber(), and getOriginalColumnNumber() which provide
information parsed from source map.
  • Loading branch information
bcoe committed Dec 26, 2019
1 parent f68285b commit 929b890
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 8 deletions.
51 changes: 43 additions & 8 deletions lib/internal/source_map/prepare_stack_trace.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,37 @@ const prepareStackTrace = (globalThis, error, trace) => {
return f(error, trace);
}

const { SourceMap } = require('internal/source_map/source_map');
const decoratedTrace = decorateCallSites(error, trace);

// `globalThis` is the global that contains the constructor which
// created `error`.
if (typeof globalThis.Error.prepareStackTrace === 'function') {
return globalThis.Error.prepareStackTrace(error, decoratedTrace);
}

const errorString = ErrorToString.call(error);

if (trace.length === 0) {
if (decoratedTrace.length === 0) {
return errorString;
}
const preparedTrace = trace.map((t, i) => {
const preparedTrace = decoratedTrace.map((t, i) => {
let str = i !== 0 ? '\n at ' : '';
str = `${str}${t}`;
if (t.getOriginalLineNumber) {
str += `\n -> ${
t.getOriginalFileName()
}:${
t.getOriginalLineNumber()
}:${t.getOriginalColumnNumber()}`;
}
return str;
});
return `${errorString}\n at ${preparedTrace.join('')}`;
};

function decorateCallSites(error, trace) {
const { SourceMap } = require('internal/source_map/source_map');
return trace.map((t) => {
try {
const sourceMap = findSourceMap(t.getFileName(), error);
if (sourceMap && sourceMap.data) {
Expand All @@ -35,17 +57,30 @@ const prepareStackTrace = (globalThis, error, trace) => {
const [, , url, line, col] =
sm.findEntry(t.getLineNumber() - 1, t.getColumnNumber() - 1);
if (url && line !== undefined && col !== undefined) {
str +=
`\n -> ${url.replace('file://', '')}:${line + 1}:${col + 1}`;
return decorateCallSite(t, url.replace('file://', ''), line + 1,
col + 1);
}
}
} catch (err) {
debug(err.stack);
}
return str;
return decorateCallSite(t, t.getFileName(), t.getLineNumber(),
t.getColumnNumber());
});
return `${errorString}\n at ${preparedTrace.join('')}`;
};
}

function decorateCallSite(callSite, fileName, lineNumber, columnNumber) {
callSite.getOriginalFileName = () => {
return fileName;
};
callSite.getOriginalLineNumber = () => {
return lineNumber;
};
callSite.getOriginalColumnNumber = () => {
return columnNumber;
};
return callSite;
}

module.exports = {
prepareStackTrace,
Expand Down
47 changes: 47 additions & 0 deletions test/parallel/test-error-prepare-stack-trace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Flags: --enable-source-maps
'use strict';

require('../common');
const assert = require('assert');

// Error.prepareStackTrace() can be overridden with source maps enabled.
{
let prepareCalled = false;
Error.prepareStackTrace = (_error, trace) => {
prepareCalled = true;
};
try {
throw new Error('foo');
} catch (err) {
err.stack;
}
assert(prepareCalled);
}

// Error.prepareStackTrace() should expose getOriginalLineNumber(),
// getOriginalColumnNumber(), getOriginalFileName().
{
let callSite;
Error.prepareStackTrace = (_error, trace) => {
const throwingRequireCallSite = trace[0];
if (throwingRequireCallSite.getFileName().endsWith('typescript-throw.js')) {
callSite = throwingRequireCallSite;
}
};
try {
// Require a file that throws an exception, and has a source map.
require('../fixtures/source-map/typescript-throw.js');
} catch (err) {
err.stack; // Force prepareStackTrace() to be called.
}
assert(callSite);

assert(callSite.getFileName().endsWith('typescript-throw.js'));
assert(callSite.getOriginalFileName().endsWith('typescript-throw.ts'));

assert.strictEqual(callSite.getLineNumber(), 20);
assert.strictEqual(callSite.getColumnNumber(), 15);

assert.strictEqual(callSite.getOriginalLineNumber(), 18);
assert.strictEqual(callSite.getOriginalColumnNumber(), 11);
}

0 comments on commit 929b890

Please sign in to comment.