Skip to content

Commit 52519c6

Browse files
e11syCopilot
andauthored
feat(js): arrange babel plugins respectfully to vue and svetle frameworks (#492)
* feat(): arrange babel plugins respectfully to vue and svetle frameworks * chore(): add context to hawk errors and cover utils with tests * chore(): clean up * Update workers/javascript/src/utils.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore(): fix tests description * imp(): move getFunctionContext to utils * chore(): lint fix * chore(): improve jsdoc --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 0bf70da commit 52519c6

4 files changed

Lines changed: 619 additions & 267 deletions

File tree

workers/javascript/src/index.ts

Lines changed: 6 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ import { JavaScriptEventWorkerTask } from '../types/javascript-event-worker-task
1010
import { BeautifyBacktracePayload } from '../types/beautify-backtrace-payload';
1111
import HawkCatcher from '@hawk.so/nodejs';
1212
import { BacktraceFrame, CatcherMessagePayload, CatcherMessageType, ErrorsCatcherType, SourceCodeLine, SourceMapDataExtended } from '@hawk.so/types';
13-
import { beautifyUserAgent } from './utils';
13+
import { beautifyUserAgent, getFunctionContext } from './utils';
1414
import { Collection } from 'mongodb';
15-
import { parse } from '@babel/parser';
16-
import traverse from '@babel/traverse';
17-
import { extname } from 'path';
1815
/* eslint-disable-next-line no-unused-vars */
1916
import { memoize } from '../../../lib/memoize';
2017

@@ -135,6 +132,9 @@ export default class JavascriptEventWorker extends EventWorker {
135132
*/
136133
HawkCatcher.send(error, {
137134
payload: backtrace as unknown as Record<string, never>,
135+
releaseRecord: JSON.stringify(releaseRecord),
136+
backtrace: JSON.stringify(backtrace),
137+
projectId,
138138
});
139139

140140
return backtrace[index];
@@ -232,13 +232,13 @@ export default class JavascriptEventWorker extends EventWorker {
232232

233233
const originalContent = consumer.sourceContentFor(originalLocation.source);
234234

235-
functionContext = await this.getFunctionContext(
235+
functionContext = await getFunctionContext(
236236
originalContent,
237237
originalLocation.line,
238238
originalLocation.source
239239
) ?? originalLocation.name;
240240
} catch (e) {
241-
HawkCatcher.send(e);
241+
HawkCatcher.send(e, { stackFrame: JSON.stringify(stackFrame) });
242242
this.logger.error('Can\'t get function context');
243243
this.logger.error(e);
244244
}
@@ -253,103 +253,6 @@ export default class JavascriptEventWorker extends EventWorker {
253253
}) as BacktraceFrame;
254254
}
255255

256-
/**
257-
* Method that is used to parse full function context of the code position
258-
*
259-
* @param sourceCode - content of the source file
260-
* @param line - number of the line from the stack trace
261-
* @param sourcePath - original source path from the source map (used to pick parser plugins)
262-
* @returns {string | null} - string of the function context or null if it could not be parsed
263-
*/
264-
private getFunctionContext(sourceCode: string, line: number, sourcePath?: string): string | null {
265-
let functionName: string | null = null;
266-
let className: string | null = null;
267-
let isAsync = false;
268-
269-
try {
270-
const parserPlugins = this.getBabelParserPluginsForFile(sourcePath);
271-
272-
const ast = parse(sourceCode, {
273-
sourceType: 'module',
274-
plugins: parserPlugins,
275-
});
276-
277-
traverse(ast as any, {
278-
/**
279-
* It is used to get class decorator of the position, it will save class that is related to original position
280-
*
281-
* @param path
282-
*/
283-
ClassDeclaration(path) {
284-
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
285-
console.log(`class declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);
286-
287-
className = path.node.id.name || null;
288-
}
289-
},
290-
/**
291-
* It is used to get class and its method decorator of the position
292-
* It will save class and method, that are related to original position
293-
*
294-
* @param path
295-
*/
296-
ClassMethod(path) {
297-
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
298-
console.log(`class declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);
299-
300-
// Handle different key types
301-
if (path.node.key.type === 'Identifier') {
302-
functionName = path.node.key.name;
303-
}
304-
isAsync = path.node.async;
305-
}
306-
},
307-
/**
308-
* It is used to get function name that is declared out of class
309-
*
310-
* @param path
311-
*/
312-
FunctionDeclaration(path) {
313-
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
314-
console.log(`function declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);
315-
316-
functionName = path.node.id.name || null;
317-
isAsync = path.node.async;
318-
}
319-
},
320-
/**
321-
* It is used to get anonimous function names in function expressions or arrow function expressions
322-
*
323-
* @param path
324-
*/
325-
VariableDeclarator(path) {
326-
if (
327-
path.node.init &&
328-
(path.node.init.type === 'FunctionExpression' || path.node.init.type === 'ArrowFunctionExpression') &&
329-
path.node.loc &&
330-
path.node.loc.start.line <= line &&
331-
path.node.loc.end.line >= line
332-
) {
333-
console.log(`variable declaration: node.type: ${path.node.init.type}, line: ${line}, `);
334-
335-
// Handle different id types
336-
if (path.node.id.type === 'Identifier') {
337-
functionName = path.node.id.name;
338-
}
339-
isAsync = (path.node.init as any).async;
340-
}
341-
},
342-
});
343-
} catch (traverseError) {
344-
console.error(`Failed to parse source code:`);
345-
console.error(traverseError);
346-
347-
HawkCatcher.send(traverseError);
348-
}
349-
350-
return functionName ? `${isAsync ? 'async ' : ''}${className ? `${className}.` : ''}${functionName}` : null;
351-
}
352-
353256
/**
354257
* Downloads source map file from Grid FS
355258
*
@@ -451,55 +354,4 @@ export default class JavascriptEventWorker extends EventWorker {
451354
this.logger.error(`Error on source-map consumer initialization: ${e}`);
452355
}
453356
}
454-
455-
/**
456-
* Choose babel parser plugins based on source file extension
457-
*
458-
* @param sourcePath - original file path from source map (e.g. "src/App.tsx")
459-
*/
460-
private getBabelParserPluginsForFile(sourcePath?: string): any[] {
461-
const basePlugins: string[] = [
462-
'classProperties',
463-
'decorators',
464-
'optionalChaining',
465-
'nullishCoalescingOperator',
466-
'dynamicImport',
467-
'bigInt',
468-
'topLevelAwait',
469-
];
470-
471-
/**
472-
* Default - use only typescript plugin because it's more stable and less likely will produce errors
473-
*/
474-
let enableTypeScript = true;
475-
let enableJSX = false;
476-
477-
if (sourcePath) {
478-
// remove query/hash if there is any
479-
const cleanPath = sourcePath.split('?')[0].split('#')[0];
480-
const ext = extname(cleanPath).toLowerCase();
481-
482-
const isTs = ext === '.ts' || ext === '.d.ts';
483-
const isTsx = ext === '.tsx';
484-
const isJs = ext === '.js' || ext === '.mjs' || ext === '.cjs';
485-
const isJsx = ext === '.jsx';
486-
487-
enableTypeScript = isTs || isTsx;
488-
// JSX:
489-
// - for .ts/.d.ts — DISABLE
490-
// - for .tsx/.jsx — ENABLE
491-
// - for .js — keep enabled, to not break App.js with JSX
492-
enableJSX = isTsx || isJsx || isJs;
493-
}
494-
495-
if (enableTypeScript) {
496-
basePlugins.push('typescript');
497-
}
498-
499-
if (enableJSX) {
500-
basePlugins.push('jsx');
501-
}
502-
503-
return basePlugins;
504-
}
505357
}

0 commit comments

Comments
 (0)