@@ -186,26 +186,27 @@ function extractDefaultEvent(node, checker, sourceFile) {
186186function processEventData ( eventData , source , filePath , line , functionName , checker , sourceFile , customConfig ) {
187187 const { eventName, propertiesNode } = eventData ;
188188
189- if ( ! eventName || ! propertiesNode ) {
189+ if ( ! eventName ) {
190190 return null ;
191191 }
192192
193193 let properties = null ;
194194
195195 // Check if properties is an object literal
196- if ( ts . isObjectLiteralExpression ( propertiesNode ) ) {
196+ if ( propertiesNode && ts . isObjectLiteralExpression ( propertiesNode ) ) {
197197 properties = extractProperties ( checker , propertiesNode ) ;
198198 }
199199 // Check if properties is an identifier (variable reference)
200- else if ( ts . isIdentifier ( propertiesNode ) ) {
200+ else if ( propertiesNode && ts . isIdentifier ( propertiesNode ) ) {
201201 const resolvedNode = resolveIdentifierToInitializer ( checker , propertiesNode , sourceFile ) ;
202202 if ( resolvedNode && ts . isObjectLiteralExpression ( resolvedNode ) ) {
203203 properties = extractProperties ( checker , resolvedNode ) ;
204204 }
205205 }
206206
207207 if ( ! properties ) {
208- return null ;
208+ // If propertiesNode was missing or could not be resolved, default to empty object
209+ properties = { } ;
209210 }
210211
211212 // Special handling for Snowplow: remove 'action' from properties
@@ -296,27 +297,78 @@ function resolvePropertyAccessToString(node, checker, sourceFile) {
296297 try {
297298 // Get the symbol for the property access
298299 const symbol = checker . getSymbolAtLocation ( node ) ;
299- if ( ! symbol || ! symbol . valueDeclaration ) {
300- return null ;
301- }
302-
303- // Check if it's a property assignment with a string initializer
304- if ( ts . isPropertyAssignment ( symbol . valueDeclaration ) &&
305- symbol . valueDeclaration . initializer &&
306- ts . isStringLiteral ( symbol . valueDeclaration . initializer ) ) {
307- return symbol . valueDeclaration . initializer . text ;
300+ if ( symbol && symbol . valueDeclaration ) {
301+ // Check if it's a property assignment with a string initializer
302+ if ( ts . isPropertyAssignment ( symbol . valueDeclaration ) &&
303+ symbol . valueDeclaration . initializer &&
304+ ts . isStringLiteral ( symbol . valueDeclaration . initializer ) ) {
305+ return symbol . valueDeclaration . initializer . text ;
306+ }
307+
308+ // Check if it's a variable declaration property
309+ if ( ts . isPropertySignature ( symbol . valueDeclaration ) ||
310+ ts . isMethodSignature ( symbol . valueDeclaration ) ) {
311+ const type = checker . getTypeAtLocation ( node ) ;
312+ if ( type && type . isStringLiteral && type . isStringLiteral ( ) ) {
313+ return type . value ;
314+ }
315+ }
316+ // If we haven't returned by now, fall through to the manual fallback below.
308317 }
309-
310- // Check if it's a variable declaration property
311- if ( ts . isPropertySignature ( symbol . valueDeclaration ) ||
312- ts . isMethodSignature ( symbol . valueDeclaration ) ) {
313- // Try to get the type and see if it's a string literal type
314- const type = checker . getTypeAtLocation ( node ) ;
315- if ( type . isStringLiteral && type . isStringLiteral ( ) ) {
316- return type . value ;
318+
319+ // ---------------------------------------------------------------------
320+ // Fallback – manually resolve patterns like:
321+ // const CONST = { KEY: 'value' };
322+ // const CONST = Object.freeze({ KEY: 'value' });
323+ // And later accessed as CONST.KEY
324+ // ---------------------------------------------------------------------
325+
326+ if ( ts . isIdentifier ( node . expression ) ) {
327+ const objIdentifier = node . expression ;
328+ const initializer = resolveIdentifierToInitializer ( checker , objIdentifier , sourceFile ) ;
329+
330+ if ( initializer ) {
331+ let objectLiteral = null ;
332+
333+ // Direct object literal: const CONST = { KEY: 'value' }
334+ if ( ts . isObjectLiteralExpression ( initializer ) ) {
335+ objectLiteral = initializer ;
336+ }
337+ // Object.freeze pattern: const CONST = Object.freeze({ KEY: 'value' })
338+ else if ( ts . isCallExpression ( initializer ) ) {
339+ const callee = initializer . expression ;
340+ if (
341+ ts . isPropertyAccessExpression ( callee ) &&
342+ ts . isIdentifier ( callee . expression ) &&
343+ callee . expression . escapedText === 'Object' &&
344+ callee . name . escapedText === 'freeze' &&
345+ initializer . arguments . length > 0 &&
346+ ts . isObjectLiteralExpression ( initializer . arguments [ 0 ] )
347+ ) {
348+ objectLiteral = initializer . arguments [ 0 ] ;
349+ }
350+ }
351+
352+ if ( objectLiteral ) {
353+ const propNode = findPropertyByKey ( objectLiteral , node . name . escapedText || node . name . text ) ;
354+ if ( propNode && propNode . initializer && ts . isStringLiteral ( propNode . initializer ) ) {
355+ return propNode . initializer . text ;
356+ }
357+ }
317358 }
318359 }
319-
360+
361+ // Final fallback – attempt to use type information (works for imported frozen constants)
362+ try {
363+ const t = checker . getTypeAtLocation ( node ) ;
364+ if ( t && t . isStringLiteral && typeof t . isStringLiteral === 'function' && t . isStringLiteral ( ) ) {
365+ return t . value ;
366+ }
367+ if ( t && t . flags && ( t . flags & ts . TypeFlags . StringLiteral ) ) {
368+ return t . value ;
369+ }
370+ } catch ( _ ) { /* ignore */ }
371+
320372 return null ;
321373 } catch ( error ) {
322374 return null ;
0 commit comments