Skip to content

Commit b6f03bf

Browse files
authored
ref(utils): reorder regexpa and add tests (#7505)
1 parent e1a9c66 commit b6f03bf

File tree

2 files changed

+262
-58
lines changed

2 files changed

+262
-58
lines changed

packages/utils/src/stacktrace.ts

Lines changed: 64 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -125,76 +125,83 @@ function node(getModule?: GetModuleFn): StackLineParserFn {
125125

126126
// eslint-disable-next-line complexity
127127
return (line: string) => {
128-
if (line.match(FILENAME_MATCH)) {
129-
return {
130-
filename: line,
131-
};
132-
}
133-
134128
const lineMatch = line.match(FULL_MATCH);
135-
if (!lineMatch) {
136-
return undefined;
137-
}
138129

139-
let object: string | undefined;
140-
let method: string | undefined;
141-
let functionName: string | undefined;
142-
let typeName: string | undefined;
143-
let methodName: string | undefined;
130+
if (lineMatch) {
131+
let object: string | undefined;
132+
let method: string | undefined;
133+
let functionName: string | undefined;
134+
let typeName: string | undefined;
135+
let methodName: string | undefined;
144136

145-
if (lineMatch[1]) {
146-
functionName = lineMatch[1];
137+
if (lineMatch[1]) {
138+
functionName = lineMatch[1];
147139

148-
let methodStart = functionName.lastIndexOf('.');
149-
if (functionName[methodStart - 1] === '.') {
150-
methodStart--;
151-
}
140+
let methodStart = functionName.lastIndexOf('.');
141+
if (functionName[methodStart - 1] === '.') {
142+
methodStart--;
143+
}
152144

153-
if (methodStart > 0) {
154-
object = functionName.slice(0, methodStart);
155-
method = functionName.slice(methodStart + 1);
156-
const objectEnd = object.indexOf('.Module');
157-
if (objectEnd > 0) {
158-
functionName = functionName.slice(objectEnd + 1);
159-
object = object.slice(0, objectEnd);
145+
if (methodStart > 0) {
146+
object = functionName.slice(0, methodStart);
147+
method = functionName.slice(methodStart + 1);
148+
const objectEnd = object.indexOf('.Module');
149+
if (objectEnd > 0) {
150+
functionName = functionName.slice(objectEnd + 1);
151+
object = object.slice(0, objectEnd);
152+
}
160153
}
154+
typeName = undefined;
161155
}
162-
typeName = undefined;
163-
}
164156

165-
if (method) {
166-
typeName = object;
167-
methodName = method;
168-
}
157+
if (method) {
158+
typeName = object;
159+
methodName = method;
160+
}
161+
162+
if (method === '<anonymous>') {
163+
methodName = undefined;
164+
functionName = undefined;
165+
}
169166

170-
if (method === '<anonymous>') {
171-
methodName = undefined;
172-
functionName = undefined;
167+
if (functionName === undefined) {
168+
methodName = methodName || '<anonymous>';
169+
functionName = typeName ? `${typeName}.${methodName}` : methodName;
170+
}
171+
172+
let filename = lineMatch[2] && lineMatch[2].startsWith('file://') ? lineMatch[2].slice(7) : lineMatch[2];
173+
const isNative = lineMatch[5] === 'native';
174+
175+
if (!filename && lineMatch[5] && !isNative) {
176+
filename = lineMatch[5];
177+
}
178+
179+
const isInternal =
180+
isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && !filename.includes(':\\'));
181+
182+
// in_app is all that's not an internal Node function or a module within node_modules
183+
// note that isNative appears to return true even for node core libraries
184+
// see https://github.com/getsentry/raven-node/issues/176
185+
186+
const in_app = !isInternal && filename !== undefined && !filename.includes('node_modules/');
187+
188+
return {
189+
filename,
190+
module: getModule ? getModule(filename) : undefined,
191+
function: functionName,
192+
lineno: parseInt(lineMatch[3], 10) || undefined,
193+
colno: parseInt(lineMatch[4], 10) || undefined,
194+
in_app,
195+
};
173196
}
174197

175-
if (functionName === undefined) {
176-
methodName = methodName || '<anonymous>';
177-
functionName = typeName ? `${typeName}.${methodName}` : methodName;
198+
if (line.match(FILENAME_MATCH)) {
199+
return {
200+
filename: line,
201+
};
178202
}
179203

180-
const filename = lineMatch[2] && lineMatch[2].startsWith('file://') ? lineMatch[2].slice(7) : lineMatch[2];
181-
const isNative = lineMatch[5] === 'native';
182-
const isInternal =
183-
isNative || (filename && !filename.startsWith('/') && !filename.startsWith('.') && filename.indexOf(':\\') !== 1);
184-
185-
// in_app is all that's not an internal Node function or a module within node_modules
186-
// note that isNative appears to return true even for node core libraries
187-
// see https://github.com/getsentry/raven-node/issues/176
188-
const in_app = !isInternal && filename !== undefined && !filename.includes('node_modules/');
189-
190-
return {
191-
filename,
192-
module: getModule ? getModule(filename) : undefined,
193-
function: functionName,
194-
lineno: parseInt(lineMatch[3], 10) || undefined,
195-
colno: parseInt(lineMatch[4], 10) || undefined,
196-
in_app,
197-
};
204+
return undefined;
198205
};
199206
}
200207

packages/utils/test/stacktrace.test.ts

Lines changed: 198 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { stripSentryFramesAndReverse } from '../src/stacktrace';
1+
import { nodeStackLineParser, stripSentryFramesAndReverse } from '../src/stacktrace';
22

33
describe('Stacktrace', () => {
44
describe('stripSentryFramesAndReverse()', () => {
@@ -68,3 +68,200 @@ describe('Stacktrace', () => {
6868
});
6969
});
7070
});
71+
72+
describe('node', () => {
73+
const mockGetModule = jest.fn();
74+
const parser = nodeStackLineParser(mockGetModule);
75+
const node = parser[1];
76+
77+
beforeEach(() => {
78+
mockGetModule.mockReset();
79+
});
80+
81+
it('should return undefined for invalid input', () => {
82+
expect(node('invalid input')).toBeUndefined();
83+
});
84+
85+
it('should extract function, module, filename, lineno, colno, and in_app from valid input', () => {
86+
const input = 'at myFunction (/path/to/file.js:10:5)';
87+
88+
const expectedOutput = {
89+
filename: '/path/to/file.js',
90+
module: undefined,
91+
function: 'myFunction',
92+
lineno: 10,
93+
colno: 5,
94+
in_app: true,
95+
};
96+
97+
expect(node(input)).toEqual(expectedOutput);
98+
});
99+
100+
it('extracts module from getModule', () => {
101+
const input = 'at myFunction (/path/to/file.js:10:5)';
102+
mockGetModule.mockReturnValue('myModule');
103+
expect(node(input)?.module).toEqual('myModule');
104+
});
105+
106+
it('should extract anonymous function name correctly', () => {
107+
const input = 'at /path/to/file.js:10:5';
108+
109+
const expectedOutput = {
110+
filename: '/path/to/file.js',
111+
module: undefined,
112+
function: '<anonymous>',
113+
lineno: 10,
114+
colno: 5,
115+
in_app: true,
116+
};
117+
118+
expect(node(input)).toEqual(expectedOutput);
119+
});
120+
121+
it('should extract method name and type name correctly', () => {
122+
const input = 'at myObject.myMethod (/path/to/file.js:10:5)';
123+
124+
const expectedOutput = {
125+
filename: '/path/to/file.js',
126+
module: undefined,
127+
function: 'myObject.myMethod',
128+
lineno: 10,
129+
colno: 5,
130+
in_app: true,
131+
};
132+
133+
expect(node(input)).toEqual(expectedOutput);
134+
});
135+
136+
it('should handle input with file:// protocol', () => {
137+
const input = 'at myFunction (file:///path/to/file.js:10:5)';
138+
139+
const expectedOutput = {
140+
filename: '/path/to/file.js',
141+
module: undefined,
142+
function: 'myFunction',
143+
lineno: 10,
144+
colno: 5,
145+
in_app: true,
146+
};
147+
148+
expect(node(input)).toEqual(expectedOutput);
149+
});
150+
151+
it('should handle input with no line or column number', () => {
152+
const input = 'at myFunction (/path/to/file.js)';
153+
154+
const expectedOutput = {
155+
filename: '/path/to/file.js',
156+
module: undefined,
157+
function: 'myFunction',
158+
lineno: undefined,
159+
colno: undefined,
160+
in_app: true,
161+
};
162+
163+
expect(node(input)).toEqual(expectedOutput);
164+
});
165+
166+
it('should handle input with "native" flag', () => {
167+
const input = 'at myFunction (native)';
168+
169+
const expectedOutput = {
170+
filename: undefined,
171+
module: undefined,
172+
function: 'myFunction',
173+
lineno: undefined,
174+
colno: undefined,
175+
in_app: false,
176+
};
177+
178+
expect(node(input)).toEqual(expectedOutput);
179+
});
180+
181+
it('should correctly parse a stack trace line with a function name and file URL', () => {
182+
const line = 'at myFunction (file:///path/to/myFile.js:10:20)';
183+
const result = node(line);
184+
expect(result).toEqual({
185+
filename: '/path/to/myFile.js',
186+
function: 'myFunction',
187+
lineno: 10,
188+
colno: 20,
189+
in_app: true,
190+
});
191+
});
192+
193+
it('should correctly parse a stack trace line with a method name and filename', () => {
194+
const line = 'at MyClass.myMethod (/path/to/myFile.js:10:20)';
195+
const result = node(line);
196+
expect(result).toEqual({
197+
filename: '/path/to/myFile.js',
198+
module: undefined,
199+
function: 'MyClass.myMethod',
200+
lineno: 10,
201+
colno: 20,
202+
in_app: true,
203+
});
204+
});
205+
206+
it('should correctly parse a stack trace line with an anonymous function', () => {
207+
const line = 'at Object.<anonymous> (/path/to/myFile.js:10:20)';
208+
const result = node(line);
209+
210+
expect(result).toEqual({
211+
filename: '/path/to/myFile.js',
212+
function: 'Object.<anonymous>',
213+
lineno: 10,
214+
colno: 20,
215+
in_app: true,
216+
});
217+
});
218+
219+
it('should correctly parse a stack trace line with no function or filename', () => {
220+
const line = 'at /path/to/myFile.js:10:20';
221+
const result = node(line);
222+
expect(result).toEqual({
223+
filename: '/path/to/myFile.js',
224+
function: '<anonymous>',
225+
lineno: 10,
226+
colno: 20,
227+
in_app: true,
228+
});
229+
});
230+
231+
it('should correctly parse a stack trace line with a native function', () => {
232+
const line = 'at Object.<anonymous> (native)';
233+
const result = node(line);
234+
expect(result).toEqual({
235+
filename: undefined,
236+
function: 'Object.<anonymous>',
237+
lineno: undefined,
238+
colno: undefined,
239+
in_app: false,
240+
});
241+
});
242+
243+
it('should correctly parse a stack trace line with a module filename', () => {
244+
const line = 'at Object.<anonymous> (/path/to/node_modules/myModule/index.js:10:20)';
245+
const result = node(line);
246+
247+
expect(result).toEqual({
248+
filename: '/path/to/node_modules/myModule/index.js',
249+
function: 'Object.<anonymous>',
250+
lineno: 10,
251+
colno: 20,
252+
in_app: false,
253+
});
254+
});
255+
256+
it('should correctly parse a stack trace line with a Windows filename', () => {
257+
const line = 'at Object.<anonymous> (C:\\path\\to\\myFile.js:10:20)';
258+
const result = node(line);
259+
expect(result).toEqual({
260+
filename: 'C:\\path\\to\\myFile.js',
261+
function: 'Object.<anonymous>',
262+
lineno: 10,
263+
colno: 20,
264+
in_app: true,
265+
});
266+
});
267+
});

0 commit comments

Comments
 (0)