From 768afb219390406caa3c96755ac9cef604ddfcf5 Mon Sep 17 00:00:00 2001 From: JonasBa Date: Fri, 17 Mar 2023 08:56:28 -0400 Subject: [PATCH] ref(utils): reorder regexpa and add tests --- packages/utils/src/stacktrace.ts | 121 ++++++++------- packages/utils/test/stacktrace.test.ts | 199 ++++++++++++++++++++++++- 2 files changed, 262 insertions(+), 58 deletions(-) diff --git a/packages/utils/src/stacktrace.ts b/packages/utils/src/stacktrace.ts index f26c4c11a084..78eb1c0eba5d 100644 --- a/packages/utils/src/stacktrace.ts +++ b/packages/utils/src/stacktrace.ts @@ -125,76 +125,83 @@ function node(getModule?: GetModuleFn): StackLineParserFn { // eslint-disable-next-line complexity return (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) { + 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]; + if (lineMatch[1]) { + functionName = lineMatch[1]; - let methodStart = functionName.lastIndexOf('.'); - if (functionName[methodStart - 1] === '.') { - methodStart--; - } + let methodStart = functionName.lastIndexOf('.'); + if (functionName[methodStart - 1] === '.') { + methodStart--; + } - if (methodStart > 0) { - object = functionName.slice(0, methodStart); - method = functionName.slice(methodStart + 1); - const objectEnd = object.indexOf('.Module'); - if (objectEnd > 0) { - functionName = functionName.slice(objectEnd + 1); - object = object.slice(0, objectEnd); + if (methodStart > 0) { + object = functionName.slice(0, methodStart); + method = functionName.slice(methodStart + 1); + const objectEnd = object.indexOf('.Module'); + if (objectEnd > 0) { + functionName = functionName.slice(objectEnd + 1); + object = object.slice(0, objectEnd); + } } + typeName = undefined; } - typeName = undefined; - } - if (method) { - typeName = object; - methodName = method; - } + if (method) { + typeName = object; + methodName = method; + } + + if (method === '') { + methodName = undefined; + functionName = undefined; + } - if (method === '') { - methodName = undefined; - functionName = undefined; + if (functionName === undefined) { + methodName = methodName || ''; + functionName = typeName ? `${typeName}.${methodName}` : methodName; + } + + let filename = lineMatch[2] && lineMatch[2].startsWith('file://') ? lineMatch[2].slice(7) : lineMatch[2]; + const isNative = lineMatch[5] === 'native'; + + if (!filename && lineMatch[5] && !isNative) { + filename = lineMatch[5]; + } + + const isInternal = + isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && !filename.includes(':\\')); + + // 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 ? getModule(filename) : undefined, + function: functionName, + lineno: parseInt(lineMatch[3], 10) || undefined, + colno: parseInt(lineMatch[4], 10) || undefined, + in_app, + }; } - if (functionName === undefined) { - methodName = methodName || ''; - functionName = typeName ? `${typeName}.${methodName}` : methodName; + if (line.match(FILENAME_MATCH)) { + return { + filename: line, + }; } - const filename = lineMatch[2] && lineMatch[2].startsWith('file://') ? lineMatch[2].slice(7) : 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 ? getModule(filename) : undefined, - function: functionName, - lineno: parseInt(lineMatch[3], 10) || undefined, - colno: parseInt(lineMatch[4], 10) || undefined, - in_app, - }; + return undefined; }; } diff --git a/packages/utils/test/stacktrace.test.ts b/packages/utils/test/stacktrace.test.ts index 61b44de34366..23257e507fb7 100644 --- a/packages/utils/test/stacktrace.test.ts +++ b/packages/utils/test/stacktrace.test.ts @@ -1,4 +1,4 @@ -import { stripSentryFramesAndReverse } from '../src/stacktrace'; +import { nodeStackLineParser, stripSentryFramesAndReverse } from '../src/stacktrace'; describe('Stacktrace', () => { describe('stripSentryFramesAndReverse()', () => { @@ -68,3 +68,200 @@ describe('Stacktrace', () => { }); }); }); + +describe('node', () => { + const mockGetModule = jest.fn(); + const parser = nodeStackLineParser(mockGetModule); + const node = parser[1]; + + beforeEach(() => { + mockGetModule.mockReset(); + }); + + it('should return undefined for invalid input', () => { + expect(node('invalid input')).toBeUndefined(); + }); + + it('should extract function, module, filename, lineno, colno, and in_app from valid input', () => { + const input = 'at myFunction (/path/to/file.js:10:5)'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: 'myFunction', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('extracts module from getModule', () => { + const input = 'at myFunction (/path/to/file.js:10:5)'; + mockGetModule.mockReturnValue('myModule'); + expect(node(input)?.module).toEqual('myModule'); + }); + + it('should extract anonymous function name correctly', () => { + const input = 'at /path/to/file.js:10:5'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: '', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should extract method name and type name correctly', () => { + const input = 'at myObject.myMethod (/path/to/file.js:10:5)'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: 'myObject.myMethod', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should handle input with file:// protocol', () => { + const input = 'at myFunction (file:///path/to/file.js:10:5)'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: 'myFunction', + lineno: 10, + colno: 5, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should handle input with no line or column number', () => { + const input = 'at myFunction (/path/to/file.js)'; + + const expectedOutput = { + filename: '/path/to/file.js', + module: undefined, + function: 'myFunction', + lineno: undefined, + colno: undefined, + in_app: true, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should handle input with "native" flag', () => { + const input = 'at myFunction (native)'; + + const expectedOutput = { + filename: undefined, + module: undefined, + function: 'myFunction', + lineno: undefined, + colno: undefined, + in_app: false, + }; + + expect(node(input)).toEqual(expectedOutput); + }); + + it('should correctly parse a stack trace line with a function name and file URL', () => { + const line = 'at myFunction (file:///path/to/myFile.js:10:20)'; + const result = node(line); + expect(result).toEqual({ + filename: '/path/to/myFile.js', + function: 'myFunction', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should correctly parse a stack trace line with a method name and filename', () => { + const line = 'at MyClass.myMethod (/path/to/myFile.js:10:20)'; + const result = node(line); + expect(result).toEqual({ + filename: '/path/to/myFile.js', + module: undefined, + function: 'MyClass.myMethod', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should correctly parse a stack trace line with an anonymous function', () => { + const line = 'at Object. (/path/to/myFile.js:10:20)'; + const result = node(line); + + expect(result).toEqual({ + filename: '/path/to/myFile.js', + function: 'Object.', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should correctly parse a stack trace line with no function or filename', () => { + const line = 'at /path/to/myFile.js:10:20'; + const result = node(line); + expect(result).toEqual({ + filename: '/path/to/myFile.js', + function: '', + lineno: 10, + colno: 20, + in_app: true, + }); + }); + + it('should correctly parse a stack trace line with a native function', () => { + const line = 'at Object. (native)'; + const result = node(line); + expect(result).toEqual({ + filename: undefined, + function: 'Object.', + lineno: undefined, + colno: undefined, + in_app: false, + }); + }); + + it('should correctly parse a stack trace line with a module filename', () => { + const line = 'at Object. (/path/to/node_modules/myModule/index.js:10:20)'; + const result = node(line); + + expect(result).toEqual({ + filename: '/path/to/node_modules/myModule/index.js', + function: 'Object.', + lineno: 10, + colno: 20, + in_app: false, + }); + }); + + it('should correctly parse a stack trace line with a Windows filename', () => { + const line = 'at Object. (C:\\path\\to\\myFile.js:10:20)'; + const result = node(line); + expect(result).toEqual({ + filename: 'C:\\path\\to\\myFile.js', + function: 'Object.', + lineno: 10, + colno: 20, + in_app: true, + }); + }); +});