@@ -14,6 +14,7 @@ import { beautifyUserAgent } from './utils';
1414import { Collection } from 'mongodb' ;
1515import { parse } from '@babel/parser' ;
1616import traverse from '@babel/traverse' ;
17+ import { extname } from 'path' ;
1718/* eslint-disable-next-line no-unused-vars */
1819import { memoize } from '../../../lib/memoize' ;
1920
@@ -231,7 +232,11 @@ export default class JavascriptEventWorker extends EventWorker {
231232
232233 const originalContent = consumer . sourceContentFor ( originalLocation . source ) ;
233234
234- functionContext = await this . getFunctionContext ( originalContent , originalLocation . line ) ?? originalLocation . name ;
235+ functionContext = await this . getFunctionContext (
236+ originalContent ,
237+ originalLocation . line ,
238+ originalLocation . source
239+ ) ?? originalLocation . name ;
235240 } catch ( e ) {
236241 HawkCatcher . send ( e ) ;
237242 this . logger . error ( 'Can\'t get function context' ) ;
@@ -253,28 +258,20 @@ export default class JavascriptEventWorker extends EventWorker {
253258 *
254259 * @param sourceCode - content of the source file
255260 * @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)
256262 * @returns {string | null } - string of the function context or null if it could not be parsed
257263 */
258- private getFunctionContext ( sourceCode : string , line : number ) : string | null {
264+ private getFunctionContext ( sourceCode : string , line : number , sourcePath ?: string ) : string | null {
259265 let functionName : string | null = null ;
260266 let className : string | null = null ;
261267 let isAsync = false ;
262268
263269 try {
264- // @todo choose plugins based on source code file extention (related to possible jsx parser usage in future)
270+ const parserPlugins = this . getBabelParserPluginsForFile ( sourcePath ) ;
271+
265272 const ast = parse ( sourceCode , {
266273 sourceType : 'module' ,
267- plugins : [
268- 'jsx' ,
269- 'typescript' ,
270- 'classProperties' ,
271- 'decorators' ,
272- 'optionalChaining' ,
273- 'nullishCoalescingOperator' ,
274- 'dynamicImport' ,
275- 'bigInt' ,
276- 'topLevelAwait' ,
277- ] ,
274+ plugins : parserPlugins ,
278275 } ) ;
279276
280277 traverse ( ast as any , {
@@ -454,4 +451,55 @@ export default class JavascriptEventWorker extends EventWorker {
454451 this . logger . error ( `Error on source-map consumer initialization: ${ e } ` ) ;
455452 }
456453 }
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+ }
457505}
0 commit comments