@@ -10,7 +10,7 @@ import { JavaScriptEventWorkerTask } from '../types/javascript-event-worker-task
1010import { BeautifyBacktracePayload } from '../types/beautify-backtrace-payload' ;
1111import HawkCatcher from '@hawk.so/nodejs' ;
1212import { BacktraceFrame , CatcherMessagePayload , CatcherMessageType , ErrorsCatcherType , SourceCodeLine , SourceMapDataExtended } from '@hawk.so/types' ;
13- import { beautifyUserAgent } from './utils' ;
13+ import { beautifyUserAgent , cleanSourcePath , countLineBreaks } from './utils' ;
1414import { Collection } from 'mongodb' ;
1515import { parse } from '@babel/parser' ;
1616import traverse from '@babel/traverse' ;
@@ -262,14 +262,24 @@ export default class JavascriptEventWorker extends EventWorker {
262262 * @returns {string | null } - string of the function context or null if it could not be parsed
263263 */
264264 private getFunctionContext ( sourceCode : string , line : number , sourcePath ?: string ) : string | null {
265+ if ( ! sourceCode ) {
266+ return null ;
267+ }
268+
269+ const {
270+ code : codeToParse ,
271+ targetLine,
272+ hasTypeScriptLang,
273+ } = this . prepareSourceForParsing ( sourceCode , line , sourcePath ) ;
274+
265275 let functionName : string | null = null ;
266276 let className : string | null = null ;
267277 let isAsync = false ;
268278
269279 try {
270- const parserPlugins = this . getBabelParserPluginsForFile ( sourcePath ) ;
280+ const parserPlugins = this . getBabelParserPluginsForFile ( sourcePath , hasTypeScriptLang ) ;
271281
272- const ast = parse ( sourceCode , {
282+ const ast = parse ( codeToParse , {
273283 sourceType : 'module' ,
274284 plugins : parserPlugins ,
275285 } ) ;
@@ -281,8 +291,8 @@ export default class JavascriptEventWorker extends EventWorker {
281291 * @param path
282292 */
283293 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 } ` ) ;
294+ if ( path . node . loc && path . node . loc . start . line <= targetLine && path . node . loc . end . line >= targetLine ) {
295+ console . log ( `class declaration: loc: ${ path . node . loc } , targetLine : ${ targetLine } , node.start.line: ${ path . node . loc . start . line } , node.end.line: ${ path . node . loc . end . line } ` ) ;
286296
287297 className = path . node . id . name || null ;
288298 }
@@ -294,8 +304,8 @@ export default class JavascriptEventWorker extends EventWorker {
294304 * @param path
295305 */
296306 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 } ` ) ;
307+ if ( path . node . loc && path . node . loc . start . line <= targetLine && path . node . loc . end . line >= targetLine ) {
308+ console . log ( `class declaration: loc: ${ path . node . loc } , targetLine : ${ targetLine } , node.start.line: ${ path . node . loc . start . line } , node.end.line: ${ path . node . loc . end . line } ` ) ;
299309
300310 // Handle different key types
301311 if ( path . node . key . type === 'Identifier' ) {
@@ -310,8 +320,8 @@ export default class JavascriptEventWorker extends EventWorker {
310320 * @param path
311321 */
312322 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 } ` ) ;
323+ if ( path . node . loc && path . node . loc . start . line <= targetLine && path . node . loc . end . line >= targetLine ) {
324+ console . log ( `function declaration: loc: ${ path . node . loc } , targetLine : ${ targetLine } , node.start.line: ${ path . node . loc . start . line } , node.end.line: ${ path . node . loc . end . line } ` ) ;
315325
316326 functionName = path . node . id . name || null ;
317327 isAsync = path . node . async ;
@@ -327,10 +337,10 @@ export default class JavascriptEventWorker extends EventWorker {
327337 path . node . init &&
328338 ( path . node . init . type === 'FunctionExpression' || path . node . init . type === 'ArrowFunctionExpression' ) &&
329339 path . node . loc &&
330- path . node . loc . start . line <= line &&
331- path . node . loc . end . line >= line
340+ path . node . loc . start . line <= targetLine &&
341+ path . node . loc . end . line >= targetLine
332342 ) {
333- console . log ( `variable declaration: node.type: ${ path . node . init . type } , line : ${ line } , ` ) ;
343+ console . log ( `variable declaration: node.type: ${ path . node . init . type } , targetLine : ${ targetLine } , ` ) ;
334344
335345 // Handle different id types
336346 if ( path . node . id . type === 'Identifier' ) {
@@ -350,6 +360,64 @@ export default class JavascriptEventWorker extends EventWorker {
350360 return functionName ? `${ isAsync ? 'async ' : '' } ${ className ? `${ className } .` : '' } ${ functionName } ` : null ;
351361 }
352362
363+ /**
364+ * Method that extracts source code and target line from the source code related to js frameworks
365+ * It is used to extract inner part of the <script> tag with its lang specifier
366+ *
367+ * @param sourceCode - content of the source file
368+ * @param originalLine - number of the line from the stack trace where the error occurred
369+ * @param sourcePath - original source path from the source map (used to pick parser plugins)
370+ * @returns - object with source code, target line and if it has TypeScript language specifier
371+ */
372+ private prepareSourceForParsing (
373+ sourceCode : string ,
374+ originalLine : number ,
375+ sourcePath ?: string
376+ ) : { code : string ; targetLine : number ; hasTypeScriptLang : boolean } {
377+ const defaultResult = {
378+ code : sourceCode ,
379+ targetLine : originalLine ,
380+ hasTypeScriptLang : false ,
381+ } ;
382+
383+ if ( ! sourcePath ) {
384+ return defaultResult ;
385+ }
386+
387+ const cleanPath = cleanSourcePath ( sourcePath ) ;
388+ const ext = extname ( cleanPath ) . toLowerCase ( ) ;
389+ const frameworkExtensions = new Set ( [ '.vue' , '.svelte' ] ) ;
390+
391+ if ( ! frameworkExtensions . has ( ext ) ) {
392+ return defaultResult ;
393+ }
394+
395+ const scriptRegex = / < s c r i p t \b ( [ ^ > ] * ) > ( [ \s \S ] * ?) < \/ s c r i p t > / gi;
396+ let match : RegExpExecArray | null ;
397+
398+ while ( ( match = scriptRegex . exec ( sourceCode ) ) !== null ) {
399+ const attrs = match [ 1 ] ?? '' ;
400+ const content = match [ 2 ] ?? '' ;
401+ const before = sourceCode . slice ( 0 , match . index ) ;
402+ const startLine = countLineBreaks ( before ) + 1 ;
403+ const linesInBlock = countLineBreaks ( content ) + 1 ;
404+ const endLine = startLine + linesInBlock - 1 ;
405+
406+ if ( originalLine >= startLine && originalLine <= endLine ) {
407+ const relativeLine = originalLine - startLine + 1 ;
408+ const hasTypeScriptLang = / l a n g \s * = \s * [ " ' ] ? ( t s | t y p e s c r i p t ) [ " ' ] ? / i. test ( attrs ) ;
409+
410+ return {
411+ code : content ,
412+ targetLine : relativeLine ,
413+ hasTypeScriptLang,
414+ } ;
415+ }
416+ }
417+
418+ return defaultResult ;
419+ }
420+
353421 /**
354422 * Downloads source map file from Grid FS
355423 *
@@ -457,7 +525,7 @@ export default class JavascriptEventWorker extends EventWorker {
457525 *
458526 * @param sourcePath - original file path from source map (e.g. "src/App.tsx")
459527 */
460- private getBabelParserPluginsForFile ( sourcePath ?: string ) : any [ ] {
528+ private getBabelParserPluginsForFile ( sourcePath ?: string , hasTypeScriptLang ?: boolean ) : any [ ] {
461529 const basePlugins : string [ ] = [
462530 'classProperties' ,
463531 'decorators' ,
@@ -468,28 +536,34 @@ export default class JavascriptEventWorker extends EventWorker {
468536 'topLevelAwait' ,
469537 ] ;
470538
471- /**
472- * Default - use only typescript plugin because it's more stable and less likely will produce errors
473- */
474- let enableTypeScript = true ;
539+ let enableTypeScript = Boolean ( hasTypeScriptLang ) ;
475540 let enableJSX = false ;
476541
477542 if ( sourcePath ) {
478- // remove query/hash if there is any
479- const cleanPath = sourcePath . split ( '?' ) [ 0 ] . split ( '#' ) [ 0 ] ;
543+ const hasTypeScriptQuery = / ( l a n g \s * = \s * [ " ' ] ? t s [ " ' ] ? ) | ( l a n g \. t s ) / i . test ( sourcePath ) ;
544+ const cleanPath = cleanSourcePath ( sourcePath ) ;
480545 const ext = extname ( cleanPath ) . toLowerCase ( ) ;
481546
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 ;
547+ const isTypeScript = ext === '.ts' || ext === '.d.ts' ;
548+ const isTypeScriptWithJsx = ext === '.tsx' ;
549+ const isJavaScript = ext === '.js' || ext === '.mjs' || ext === '.cjs' ;
550+ const isJavaScriptWithJsx = ext === '.jsx' ;
551+ const isFrameworkFile = ext === '.vue' || ext === '.svelte' ;
552+
553+ if ( isTypeScriptWithJsx ) {
554+ enableTypeScript = true ;
555+ enableJSX = true ;
556+ } else {
557+ if ( isTypeScript || hasTypeScriptQuery || enableTypeScript ) {
558+ enableTypeScript = true ;
559+ }
560+
561+ if ( isJavaScript || isJavaScriptWithJsx || isFrameworkFile ) {
562+ enableJSX = true ;
563+ }
564+ }
565+ } else {
566+ enableTypeScript = true ;
493567 }
494568
495569 if ( enableTypeScript ) {
0 commit comments