diff --git a/packages/node/src/backend.ts b/packages/node/src/backend.ts index 707d0bec9324..209450a375a3 100644 --- a/packages/node/src/backend.ts +++ b/packages/node/src/backend.ts @@ -1,8 +1,8 @@ import { BaseBackend } from '@sentry/core'; import { Event, EventHint, Severity, Transport, TransportOptions } from '@sentry/types'; -import { makeDsn } from '@sentry/utils'; +import { makeDsn, resolvedSyncPromise } from '@sentry/utils'; -import { eventFromException, eventFromMessage } from './eventbuilder'; +import { eventFromError, eventFromMessage } from './eventbuilder'; import { HTTPSTransport, HTTPTransport } from './transports'; import { NodeOptions } from './types'; @@ -16,14 +16,14 @@ export class NodeBackend extends BaseBackend { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types public eventFromException(exception: any, hint?: EventHint): PromiseLike { - return eventFromException(exception, hint); + return resolvedSyncPromise(eventFromError(exception, hint)); } /** * @inheritDoc */ public eventFromMessage(message: string, level: Severity = Severity.Info, hint?: EventHint): PromiseLike { - return eventFromMessage(this._options, message, level, hint); + return resolvedSyncPromise(eventFromMessage(this._options, message, level, hint)); } /** diff --git a/packages/node/src/eventbuilder.ts b/packages/node/src/eventbuilder.ts index 3959e8f6daa2..bf5af4ab2a58 100644 --- a/packages/node/src/eventbuilder.ts +++ b/packages/node/src/eventbuilder.ts @@ -1,22 +1,46 @@ import { getCurrentHub } from '@sentry/hub'; -import { Event, EventHint, Mechanism, Options, Severity } from '@sentry/types'; +import { Event, EventHint, Exception, Mechanism, Options, Severity, StackFrame } from '@sentry/types'; import { addExceptionMechanism, addExceptionTypeValue, + createStackParser, extractExceptionKeysForMessage, isError, isPlainObject, normalizeToSize, - SyncPromise, } from '@sentry/utils'; -import { extractStackFromError, parseError, parseStack, prepareFramesForEvent } from './parsers'; +import { nodeStackParser } from './stack-parser'; + +/** + * Extracts stack frames from the error.stack string + */ +export function extractStackFromError(error: Error): StackFrame[] { + return createStackParser(nodeStackParser)(error.stack || ''); +} + +/** + * Extracts stack frames from the error and builds a Sentry Exception + */ +export function exceptionFromError(error: Error): Exception { + const exception: Exception = { + type: error.name || error.constructor.name, + value: error.message, + }; + + const frames = extractStackFromError(error); + if (frames.length) { + exception.stacktrace = { frames }; + } + + return exception; +} /** * Builds and Event from a Exception * @hidden */ -export function eventFromException(exception: unknown, hint?: EventHint): PromiseLike { +export function eventFromError(exception: unknown, hint?: EventHint): Event { // eslint-disable-next-line @typescript-eslint/no-explicit-any let ex: any = exception; const providedMechanism: Mechanism | undefined = @@ -47,19 +71,19 @@ export function eventFromException(exception: unknown, hint?: EventHint): Promis mechanism.synthetic = true; } - return new SyncPromise((resolve, reject) => - parseError(ex as Error) - .then(event => { - addExceptionTypeValue(event, undefined, undefined); - addExceptionMechanism(event, mechanism); + const event = { + exception: { + values: [exceptionFromError(ex as Error)], + }, + }; - resolve({ - ...event, - event_id: hint && hint.event_id, - }); - }) - .then(null, reject), - ); + addExceptionTypeValue(event, undefined, undefined); + addExceptionMechanism(event, mechanism); + + return { + ...event, + event_id: hint && hint.event_id, + }; } /** @@ -71,23 +95,19 @@ export function eventFromMessage( message: string, level: Severity = Severity.Info, hint?: EventHint, -): PromiseLike { +): Event { const event: Event = { event_id: hint && hint.event_id, level, message, }; - return new SyncPromise(resolve => { - if (options.attachStacktrace && hint && hint.syntheticException) { - const stack = hint.syntheticException ? extractStackFromError(hint.syntheticException) : []; - const frames = parseStack(stack); - event.stacktrace = { - frames: prepareFramesForEvent(frames), - }; - resolve(event); - } else { - resolve(event); + if (options.attachStacktrace && hint && hint.syntheticException) { + const frames = extractStackFromError(hint.syntheticException); + if (frames.length) { + event.stacktrace = { frames }; } - }); + } + + return event; } diff --git a/packages/node/src/integrations/linkederrors.ts b/packages/node/src/integrations/linkederrors.ts index 0b70aad73360..854b47a6281f 100644 --- a/packages/node/src/integrations/linkederrors.ts +++ b/packages/node/src/integrations/linkederrors.ts @@ -2,7 +2,7 @@ import { addGlobalEventProcessor, getCurrentHub } from '@sentry/core'; import { Event, EventHint, Exception, ExtendedError, Integration } from '@sentry/types'; import { isInstanceOf, resolvedSyncPromise, SyncPromise } from '@sentry/utils'; -import { getExceptionFromError } from '../parsers'; +import { exceptionFromError } from '../eventbuilder'; const DEFAULT_KEY = 'cause'; const DEFAULT_LIMIT = 5; @@ -80,15 +80,12 @@ export class LinkedErrors implements Integration { if (!isInstanceOf(error[key], Error) || stack.length + 1 >= this._limit) { return resolvedSyncPromise(stack); } + + const exception = exceptionFromError(error[key]); + return new SyncPromise((resolve, reject) => { - void getExceptionFromError(error[key]) - .then((exception: Exception) => { - void this._walkErrorTree(error[key], key, [exception, ...stack]) - .then(resolve) - .then(null, () => { - reject(); - }); - }) + void this._walkErrorTree(error[key], key, [exception, ...stack]) + .then(resolve) .then(null, () => { reject(); }); diff --git a/packages/node/src/parsers.ts b/packages/node/src/parsers.ts deleted file mode 100644 index 835dfa33ec9f..000000000000 --- a/packages/node/src/parsers.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { Event, Exception, ExtendedError, StackFrame } from '@sentry/types'; -import { basename, dirname, SyncPromise } from '@sentry/utils'; - -import * as stacktrace from './stacktrace'; - -/** JSDoc */ -function getFunction(frame: stacktrace.StackFrame): string { - try { - return frame.functionName || `${frame.typeName}.${frame.methodName || ''}`; - } catch (e) { - // This seems to happen sometimes when using 'use strict', - // stemming from `getTypeName`. - // [TypeError: Cannot read property 'constructor' of undefined] - return ''; - } -} - -const mainModule: string = `${ - (require.main && require.main.filename && dirname(require.main.filename)) || global.process.cwd() -}/`; - -/** JSDoc */ -function getModule(filename: string, base?: string): string { - if (!base) { - // eslint-disable-next-line no-param-reassign - base = mainModule; - } - - // It's specifically a module - const file = basename(filename, '.js'); - // eslint-disable-next-line no-param-reassign - filename = dirname(filename); - let n = filename.lastIndexOf('/node_modules/'); - if (n > -1) { - // /node_modules/ is 14 chars - return `${filename.substr(n + 14).replace(/\//g, '.')}:${file}`; - } - // Let's see if it's a part of the main module - // To be a part of main module, it has to share the same base - n = `${filename}/`.lastIndexOf(base, 0); - if (n === 0) { - let moduleName = filename.substr(base.length).replace(/\//g, '.'); - if (moduleName) { - moduleName += ':'; - } - moduleName += file; - return moduleName; - } - return file; -} - -/** - * @hidden - */ -export function extractStackFromError(error: Error): stacktrace.StackFrame[] { - const stack = stacktrace.parse(error); - if (!stack) { - return []; - } - return stack; -} - -/** - * @hidden - */ -export function parseStack(stack: stacktrace.StackFrame[]): StackFrame[] { - return stack.map(frame => { - const parsedFrame: StackFrame = { - colno: frame.columnNumber, - filename: frame.fileName?.startsWith('file://') ? frame.fileName.substr(7) : frame.fileName || '', - function: getFunction(frame), - lineno: frame.lineNumber, - }; - - const isInternal = - frame.native || - (parsedFrame.filename && - !parsedFrame.filename.startsWith('/') && - !parsedFrame.filename.startsWith('.') && - parsedFrame.filename.indexOf(':\\') !== 1); - - // in_app is all that's not an internal Node function or a module within node_modules - // note that isNative appears to return true even for node core libraries - // see https://github.com/getsentry/raven-node/issues/176 - parsedFrame.in_app = - !isInternal && parsedFrame.filename !== undefined && parsedFrame.filename.indexOf('node_modules/') === -1; - - // Extract a module name based on the filename - if (parsedFrame.filename) { - parsedFrame.module = getModule(parsedFrame.filename); - } - - return parsedFrame; - }); -} - -/** - * @hidden - */ -export function getExceptionFromError(error: Error): PromiseLike { - const name = error.name || error.constructor.name; - const stack = extractStackFromError(error); - return new SyncPromise(resolve => { - const frames = parseStack(stack); - const result = { - stacktrace: { - frames: prepareFramesForEvent(frames), - }, - type: name, - value: error.message, - }; - resolve(result); - }); -} - -/** - * @hidden - */ -export function parseError(error: ExtendedError): PromiseLike { - return new SyncPromise(resolve => - getExceptionFromError(error).then((exception: Exception) => { - resolve({ - exception: { - values: [exception], - }, - }); - }), - ); -} - -/** - * @hidden - */ -export function prepareFramesForEvent(stack: StackFrame[]): StackFrame[] { - if (!stack || !stack.length) { - return []; - } - - let localStack = stack; - const firstFrameFunction = localStack[0].function || ''; - - if (firstFrameFunction.indexOf('captureMessage') !== -1 || firstFrameFunction.indexOf('captureException') !== -1) { - localStack = localStack.slice(1); - } - - // The frame where the crash happened, should be the last entry in the array - return localStack.reverse(); -} diff --git a/packages/node/src/stack-parser.ts b/packages/node/src/stack-parser.ts new file mode 100644 index 000000000000..bd71f39a9bf5 --- /dev/null +++ b/packages/node/src/stack-parser.ts @@ -0,0 +1,108 @@ +import { basename, dirname, StackLineParser } from '@sentry/utils'; + +/** Gets the module */ +function getModule(filename: string | undefined): string | undefined { + if (!filename) { + return; + } + + // We could use optional chaining here but webpack does like that mixed with require + const base = `${ + (require && require.main && require.main.filename && dirname(require.main.filename)) || global.process.cwd() + }/`; + + // It's specifically a module + const file = basename(filename, '.js'); + + const path = dirname(filename); + let n = path.lastIndexOf('/node_modules/'); + if (n > -1) { + // /node_modules/ is 14 chars + return `${path.substr(n + 14).replace(/\//g, '.')}:${file}`; + } + // Let's see if it's a part of the main module + // To be a part of main module, it has to share the same base + n = `${path}/`.lastIndexOf(base, 0); + + if (n === 0) { + let moduleName = path.substr(base.length).replace(/\//g, '.'); + if (moduleName) { + moduleName += ':'; + } + moduleName += file; + return moduleName; + } + return file; +} + +const FILENAME_MATCH = /^\s*[-]{4,}$/; +const FULL_MATCH = /at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/; + +export const nodeStackParser: StackLineParser = (line: string) => { + if (line.match(FILENAME_MATCH)) { + return { + filename: line, + }; + } + + const lineMatch = line.match(FULL_MATCH); + if (!lineMatch) { + return undefined; + } + + let object: string | undefined; + let method: string | undefined; + let functionName: string | undefined; + let typeName: string | undefined; + let methodName: string | undefined; + + if (lineMatch[1]) { + functionName = lineMatch[1]; + + let methodStart = functionName.lastIndexOf('.'); + if (functionName[methodStart - 1] === '.') { + // eslint-disable-next-line no-plusplus + methodStart--; + } + + if (methodStart > 0) { + object = functionName.substr(0, methodStart); + method = functionName.substr(methodStart + 1); + const objectEnd = object.indexOf('.Module'); + if (objectEnd > 0) { + functionName = functionName.substr(objectEnd + 1); + object = object.substr(0, objectEnd); + } + } + typeName = undefined; + } + + if (method) { + typeName = object; + methodName = method; + } + + if (method === '') { + methodName = undefined; + functionName = undefined; + } + + const filename = lineMatch[2]; + const isNative = lineMatch[5] === 'native'; + const isInternal = + isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1); + + // in_app is all that's not an internal Node function or a module within node_modules + // note that isNative appears to return true even for node core libraries + // see https://github.com/getsentry/raven-node/issues/176 + const in_app = !isInternal && filename !== undefined && !filename.includes('node_modules/'); + + return { + filename, + module: getModule(filename), + function: functionName || `${typeName}.${methodName || ''}`, + lineno: parseInt(lineMatch[3], 10) || undefined, + colno: parseInt(lineMatch[4], 10) || undefined, + in_app, + }; +}; diff --git a/packages/node/src/stacktrace.ts b/packages/node/src/stacktrace.ts deleted file mode 100644 index 249fffcb0b5a..000000000000 --- a/packages/node/src/stacktrace.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * stack-trace - Parses node.js stack traces - * - * This was originally forked to fix this issue: - * https://github.com/felixge/node-stack-trace/issues/31 - * - * Mar 19,2019 - #4fd379e - * - * https://github.com/felixge/node-stack-trace/ - * @license MIT - */ - -/** Decoded StackFrame */ -export interface StackFrame { - fileName: string; - lineNumber: number; - functionName: string; - typeName: string; - methodName: string; - native: boolean; - columnNumber: number; -} - -/** Extracts StackFrames from the Error */ -export function parse(err: Error): StackFrame[] { - if (!err.stack) { - return []; - } - - const lines = err.stack.split('\n').slice(1); - - return lines - .map(line => { - if (line.match(/^\s*[-]{4,}$/)) { - return { - columnNumber: null, - fileName: line, - functionName: null, - lineNumber: null, - methodName: null, - native: null, - typeName: null, - }; - } - - const lineMatch = line.match(/at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/); - if (!lineMatch) { - return undefined; - } - - let object = null; - let method = null; - let functionName = null; - let typeName = null; - let methodName = null; - const isNative = lineMatch[5] === 'native'; - - if (lineMatch[1]) { - functionName = lineMatch[1]; - let methodStart = functionName.lastIndexOf('.'); - if (functionName[methodStart - 1] === '.') { - // eslint-disable-next-line no-plusplus - methodStart--; - } - if (methodStart > 0) { - object = functionName.substr(0, methodStart); - method = functionName.substr(methodStart + 1); - const objectEnd = object.indexOf('.Module'); - if (objectEnd > 0) { - functionName = functionName.substr(objectEnd + 1); - object = object.substr(0, objectEnd); - } - } - typeName = null; - } - - if (method) { - typeName = object; - methodName = method; - } - - if (method === '') { - methodName = null; - functionName = null; - } - - const properties = { - columnNumber: parseInt(lineMatch[4], 10) || null, - fileName: lineMatch[2] || null, - functionName, - lineNumber: parseInt(lineMatch[3], 10) || null, - methodName, - native: isNative, - typeName, - }; - - return properties; - }) - .filter(callSite => !!callSite) as StackFrame[]; -} diff --git a/packages/node/test/parsers.test.ts b/packages/node/test/parsers.test.ts index 501645fe9215..456435a4cbfb 100644 --- a/packages/node/test/parsers.test.ts +++ b/packages/node/test/parsers.test.ts @@ -1,13 +1,11 @@ import { StackFrame } from '@sentry/types'; import * as fs from 'fs'; +import { extractStackFromError } from '../src/eventbuilder'; import { ContextLines, resetFileContentCache } from '../src/integrations/contextlines'; -import * as Parsers from '../src/parsers'; -import * as stacktrace from '../src/stacktrace'; import { getError } from './helper/error'; describe('parsers.ts', () => { - let frames: stacktrace.StackFrame[]; let readFileSpy: jest.SpyInstance; let contextLines: ContextLines; @@ -17,7 +15,6 @@ describe('parsers.ts', () => { beforeEach(() => { readFileSpy = jest.spyOn(fs, 'readFile'); - frames = stacktrace.parse(new Error('test')); contextLines = new ContextLines(); resetFileContentCache(); }); @@ -30,12 +27,12 @@ describe('parsers.ts', () => { test('parseStack with same file', async () => { expect.assertions(1); - let parsedFrames = Parsers.parseStack(frames); - await addContext(parsedFrames); + const frames = extractStackFromError(new Error('test')); + + await addContext(Array.from(frames)); const numCalls = readFileSpy.mock.calls.length; - parsedFrames = Parsers.parseStack(frames); - await addContext(parsedFrames); + await addContext(frames); // Calls to `readFile` shouldn't increase if there isn't a new error to // parse whose stacktrace contains a file we haven't yet seen @@ -45,29 +42,27 @@ describe('parsers.ts', () => { test('parseStack with ESM module names', async () => { expect.assertions(1); - const framesWithFilePath: stacktrace.StackFrame[] = [ + const framesWithFilePath: StackFrame[] = [ { - columnNumber: 1, - fileName: 'file:///var/task/index.js', - functionName: 'module.exports../src/index.ts.fxn1', - lineNumber: 1, - methodName: 'fxn1', - native: false, - typeName: 'module.exports../src/index.ts', + colno: 1, + filename: 'file:///var/task/index.js', + lineno: 1, + function: 'fxn1', }, ]; - const parsedFrames = Parsers.parseStack(framesWithFilePath); - await addContext(parsedFrames); + + await addContext(framesWithFilePath); expect(readFileSpy).toHaveBeenCalledTimes(1); }); test('parseStack with adding different file', async () => { expect.assertions(1); - let parsedFrames = Parsers.parseStack(frames); - await addContext(parsedFrames); + const frames = extractStackFromError(new Error('test')); + + await addContext(frames); const numCalls = readFileSpy.mock.calls.length; - parsedFrames = Parsers.parseStack(stacktrace.parse(getError())); + const parsedFrames = extractStackFromError(getError()); await addContext(parsedFrames); const newErrorCalls = readFileSpy.mock.calls.length; @@ -76,45 +71,36 @@ describe('parsers.ts', () => { test('parseStack with duplicate files', async () => { expect.assertions(1); - const framesWithDuplicateFiles: stacktrace.StackFrame[] = [ + const framesWithDuplicateFiles: StackFrame[] = [ { - columnNumber: 1, - fileName: '/var/task/index.js', - functionName: 'module.exports../src/index.ts.fxn1', - lineNumber: 1, - methodName: 'fxn1', - native: false, - typeName: 'module.exports../src/index.ts', + colno: 1, + filename: '/var/task/index.js', + lineno: 1, + function: 'fxn1', }, { - columnNumber: 2, - fileName: '/var/task/index.js', - functionName: 'module.exports../src/index.ts.fxn2', - lineNumber: 2, - methodName: 'fxn2', - native: false, - typeName: 'module.exports../src/index.ts', + colno: 2, + filename: '/var/task/index.js', + lineno: 2, + function: 'fxn2', }, { - columnNumber: 3, - fileName: '/var/task/index.js', - functionName: 'module.exports../src/index.ts.fxn3', - lineNumber: 3, - methodName: 'fxn3', - native: false, - typeName: 'module.exports../src/index.ts', + colno: 3, + filename: '/var/task/index.js', + lineno: 3, + function: 'fxn3', }, ]; - const parsedFrames = Parsers.parseStack(framesWithDuplicateFiles); - await addContext(parsedFrames); + await addContext(framesWithDuplicateFiles); expect(readFileSpy).toHaveBeenCalledTimes(1); }); test('parseStack with no context', async () => { expect.assertions(1); - const parsedFrames = Parsers.parseStack(frames); - await addContext(parsedFrames, 0); + const frames = extractStackFromError(new Error('test')); + + await addContext(frames, 0); expect(readFileSpy).toHaveBeenCalledTimes(0); }); }); diff --git a/packages/node/test/stacktrace.test.ts b/packages/node/test/stacktrace.test.ts index 8309dec4b61d..78567a78b8d9 100644 --- a/packages/node/test/stacktrace.test.ts +++ b/packages/node/test/stacktrace.test.ts @@ -10,7 +10,7 @@ * @license MIT */ -import * as stacktrace from '../src/stacktrace'; +import { extractStackFromError } from '../src/eventbuilder'; function testBasic() { return new Error('something went wrong'); @@ -24,70 +24,71 @@ function evalWrapper() { return eval('testWrapper()'); } -describe('stacktrace.ts', () => { - test('testBasic', () => { - const trace = stacktrace.parse(testBasic()); +describe('Stack parsing', () => { + test('test basic error', () => { + const frames = extractStackFromError(testBasic()); - expect(trace[0].fileName).toEqual(__filename); - expect(trace[0].functionName).toEqual('testBasic'); - expect(trace[0].lineNumber).toEqual(16); - expect(trace[0].columnNumber).toEqual(10); + const last = frames.length - 1; + expect(frames[last].filename).toEqual(__filename); + expect(frames[last].function).toEqual('testBasic'); + expect(frames[last].lineno).toEqual(16); + expect(frames[last].colno).toEqual(10); }); - test('testWrapper', () => { - const trace = stacktrace.parse(testWrapper()); + test('test error with wrapper', () => { + const frames = extractStackFromError(testWrapper()); - expect(trace[0].functionName).toEqual('testBasic'); - expect(trace[1].functionName).toEqual('testWrapper'); + const last = frames.length - 1; + expect(frames[last].function).toEqual('testBasic'); + expect(frames[last - 1].function).toEqual('testWrapper'); }); - test('evalWrapper', () => { - const trace = stacktrace.parse(evalWrapper()); + test('test error with eval wrapper', () => { + const frames = extractStackFromError(evalWrapper()); - expect(trace[0].functionName).toEqual('testBasic'); - expect(trace[1].functionName).toEqual('testWrapper'); - expect(trace[2].functionName).toEqual('eval'); + const last = frames.length - 1; + expect(frames[last].function).toEqual('testBasic'); + expect(frames[last - 1].function).toEqual('testWrapper'); + expect(frames[last - 2].function).toEqual('eval'); }); - test('testObjectInMethodName', () => { + test('parses object in fn name', () => { const err: { [key: string]: any } = {}; err.stack = 'Error: Foo\n' + ' at [object Object].global.every [as _onTimeout] (/Users/hoitz/develop/test.coffee:36:3)\n' + ' at Timer.listOnTimeout [as ontimeout] (timers.js:110:15)\n'; - const trace = stacktrace.parse(err as Error); + const frames = extractStackFromError(err as Error); - expect(trace).toEqual([ + expect(frames).toEqual([ { - columnNumber: 3, - fileName: '/Users/hoitz/develop/test.coffee', - functionName: '[object Object].global.every [as _onTimeout]', - lineNumber: 36, - methodName: 'every [as _onTimeout]', - native: false, - typeName: '[object Object].global', + filename: 'timers.js', + module: 'timers', + function: 'Timer.listOnTimeout [as ontimeout]', + lineno: 110, + colno: 15, + in_app: false, }, { - columnNumber: 15, - fileName: 'timers.js', - functionName: 'Timer.listOnTimeout [as ontimeout]', - lineNumber: 110, - methodName: 'listOnTimeout [as ontimeout]', - native: false, - typeName: 'Timer', + filename: '/Users/hoitz/develop/test.coffee', + module: 'test.coffee', + function: '[object Object].global.every [as _onTimeout]', + lineno: 36, + colno: 3, + in_app: true, }, ]); }); - test('testNoStack', () => { + test('parses undefined stack', () => { const err = { stack: undefined }; - const trace = stacktrace.parse(err as Error); + const trace = extractStackFromError(err as Error); expect(trace).toEqual([]); }); - test('testCorruptStack', () => { + test('parses corrupt stack', () => { const err: { [key: string]: any } = {}; err.stack = 'AssertionError: true == false\n' + @@ -96,62 +97,56 @@ describe('stacktrace.ts', () => { 'oh no' + ' at TestCase.run (/Users/felix/code/node-fast-or-slow/lib/test_case.js:61:8)\n'; - const trace = stacktrace.parse(err as Error); + const frames = extractStackFromError(err as Error); - expect(trace).toEqual([ + expect(frames).toEqual([ { - columnNumber: 10, - fileName: '/Users/felix/code/node-fast-or-slow/lib/test.js', - functionName: 'Test.run', - lineNumber: 45, - methodName: 'run', - native: false, - typeName: 'Test', + filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', + module: 'test_case', + function: 'TestCase.run', + lineno: 61, + colno: 8, + in_app: true, }, { - columnNumber: 8, - fileName: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', - functionName: 'TestCase.run', - lineNumber: 61, - methodName: 'run', - native: false, - typeName: 'TestCase', + filename: '/Users/felix/code/node-fast-or-slow/lib/test.js', + module: 'test', + function: 'Test.run', + lineno: 45, + colno: 10, + in_app: true, }, ]); }); - test('testTraceWitoutColumnNumbers', () => { + test('parses with missing column numbers', () => { const err: { [key: string]: any } = {}; err.stack = 'AssertionError: true == false\n' + ' at Test.fn (/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js:6)\n' + ' at Test.run (/Users/felix/code/node-fast-or-slow/lib/test.js:45)'; - const trace = stacktrace.parse(err as Error); + const frames = extractStackFromError(err as Error); - expect(trace).toEqual([ + expect(frames).toEqual([ { - columnNumber: null, - fileName: '/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js', - functionName: 'Test.fn', - lineNumber: 6, - methodName: 'fn', - native: false, - typeName: 'Test', + filename: '/Users/felix/code/node-fast-or-slow/lib/test.js', + module: 'test', + function: 'Test.run', + lineno: 45, + in_app: true, }, { - columnNumber: null, - fileName: '/Users/felix/code/node-fast-or-slow/lib/test.js', - functionName: 'Test.run', - lineNumber: 45, - methodName: 'run', - native: false, - typeName: 'Test', + filename: '/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js', + module: 'test-example', + function: 'Test.fn', + lineno: 6, + in_app: true, }, ]); }); - test('testStackWithNativeCall', () => { + test('parses with native methods', () => { const err: { [key: string]: any } = {}; err.stack = 'AssertionError: true == false\n' + @@ -162,154 +157,140 @@ describe('stacktrace.ts', () => { ' at Array.0 (native)\n' + ' at EventEmitter._tickCallback (node.js:126:26)'; - const trace = stacktrace.parse(err as Error); + const frames = extractStackFromError(err as Error); - expect(trace).toEqual([ + expect(frames).toEqual([ { - columnNumber: 10, - fileName: '/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js', - functionName: 'Test.fn', - lineNumber: 6, - methodName: 'fn', - native: false, - typeName: 'Test', + filename: 'node.js', + module: 'node', + function: 'EventEmitter._tickCallback', + lineno: 126, + colno: 26, + in_app: false, }, { - columnNumber: 10, - fileName: '/Users/felix/code/node-fast-or-slow/lib/test.js', - functionName: 'Test.run', - lineNumber: 45, - methodName: 'run', - native: false, - typeName: 'Test', + filename: '/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js', + function: 'Array.0', + in_app: false, }, { - columnNumber: 8, - fileName: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', - functionName: 'TestCase.runNext', - lineNumber: 73, - methodName: 'runNext', - native: false, - typeName: 'TestCase', + filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', + module: 'test_case', + function: 'TestCase.run', + lineno: 61, + colno: 8, + in_app: true, }, { - columnNumber: 8, - fileName: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', - functionName: 'TestCase.run', - lineNumber: 61, - methodName: 'run', - native: false, - typeName: 'TestCase', + filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', + module: 'test_case', + function: 'TestCase.runNext', + lineno: 73, + colno: 8, + in_app: true, }, { - columnNumber: null, - fileName: null, - functionName: 'Array.0', - lineNumber: null, - methodName: '0', - native: true, - typeName: 'Array', + filename: '/Users/felix/code/node-fast-or-slow/lib/test.js', + module: 'test', + function: 'Test.run', + lineno: 45, + colno: 10, + in_app: true, }, { - columnNumber: 26, - fileName: 'node.js', - functionName: 'EventEmitter._tickCallback', - lineNumber: 126, - methodName: '_tickCallback', - native: false, - typeName: 'EventEmitter', + filename: '/Users/felix/code/node-fast-or-slow/test/fast/example/test-example.js', + module: 'test-example', + function: 'Test.fn', + lineno: 6, + colno: 10, + in_app: true, }, ]); }); - test('testStackWithFileOnly', () => { + test('parses with file only', () => { const err: { [key: string]: any } = {}; err.stack = 'AssertionError: true == false\n' + ' at /Users/felix/code/node-fast-or-slow/lib/test_case.js:80:10'; - const trace = stacktrace.parse(err as Error); + const frames = extractStackFromError(err as Error); - expect(trace).toEqual([ + expect(frames).toEqual([ { - columnNumber: 10, - fileName: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', - functionName: null, - lineNumber: 80, - methodName: null, - native: false, - typeName: null, + filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', + module: 'test_case', + function: 'undefined.', + lineno: 80, + colno: 10, + in_app: true, }, ]); }); - test('testStackWithMultilineMessage', () => { + test('parses with multi line message', () => { const err: { [key: string]: any } = {}; err.stack = 'AssertionError: true == false\nAnd some more shit\n' + ' at /Users/felix/code/node-fast-or-slow/lib/test_case.js:80:10'; - const trace = stacktrace.parse(err as Error); + const frames = extractStackFromError(err as Error); - expect(trace).toEqual([ + expect(frames).toEqual([ { - columnNumber: 10, - fileName: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', - functionName: null, - lineNumber: 80, - methodName: null, - native: false, - typeName: null, + filename: '/Users/felix/code/node-fast-or-slow/lib/test_case.js', + module: 'test_case', + function: 'undefined.', + lineno: 80, + colno: 10, + in_app: true, }, ]); }); - test('testStackWithAnonymousFunctionCall', () => { + test('parses with anonymous fn call', () => { const err: { [key: string]: any } = {}; err.stack = 'AssertionError: expected [] to be arguments\n' + ' at Assertion.prop.(anonymous function) (/Users/den/Projects/should.js/lib/should.js:60:14)\n'; - const trace = stacktrace.parse(err as Error); + const frames = extractStackFromError(err as Error); - expect(trace).toEqual([ + expect(frames).toEqual([ { - columnNumber: 14, - fileName: '/Users/den/Projects/should.js/lib/should.js', - functionName: 'Assertion.prop.(anonymous function)', - lineNumber: 60, - methodName: '(anonymous function)', - native: false, - typeName: 'Assertion.prop', + filename: '/Users/den/Projects/should.js/lib/should.js', + module: 'should', + function: 'Assertion.prop.(anonymous function)', + lineno: 60, + colno: 14, + in_app: true, }, ]); }); - test('testTraceBracesInPath', () => { + test('parses with braces in paths', () => { const err: { [key: string]: any } = {}; err.stack = 'AssertionError: true == false\n' + ' at Test.run (/Users/felix (something)/code/node-fast-or-slow/lib/test.js:45:10)\n' + ' at TestCase.run (/Users/felix (something)/code/node-fast-or-slow/lib/test_case.js:61:8)\n'; - const trace = stacktrace.parse(err as Error); + const frames = extractStackFromError(err as Error); - expect(trace).toEqual([ + expect(frames).toEqual([ { - columnNumber: 10, - fileName: '/Users/felix (something)/code/node-fast-or-slow/lib/test.js', - functionName: 'Test.run', - lineNumber: 45, - methodName: 'run', - native: false, - typeName: 'Test', + filename: '/Users/felix (something)/code/node-fast-or-slow/lib/test_case.js', + module: 'test_case', + function: 'TestCase.run', + lineno: 61, + colno: 8, + in_app: true, }, { - columnNumber: 8, - fileName: '/Users/felix (something)/code/node-fast-or-slow/lib/test_case.js', - functionName: 'TestCase.run', - lineNumber: 61, - methodName: 'run', - native: false, - typeName: 'TestCase', + filename: '/Users/felix (something)/code/node-fast-or-slow/lib/test.js', + module: 'test', + function: 'Test.run', + lineno: 45, + colno: 10, + in_app: true, }, ]); });