@@ -518,25 +518,37 @@ test.describe('analyzeTsFile', () => {
518518 // Sort events by line number for consistent ordering
519519 events . sort ( ( a , b ) => a . line - b . line ) ;
520520
521- assert . strictEqual ( events . length , 7 ) ;
521+ // Updated count - 8 events for regular analysis (complex_operation is only detected with custom function)
522+ assert . strictEqual ( events . length , 8 ) ;
523+
524+ // Test new Segment event from ComplexUploadComponent (regression test pattern)
525+ const complexSegmentEvent = events . find ( e => e . source === 'segment' && e . eventName === 'document_upload_clicked' ) ;
526+ assert . ok ( complexSegmentEvent ) ;
527+ assert . strictEqual ( complexSegmentEvent . eventName , 'document_upload_clicked' ) ;
528+ assert . strictEqual ( complexSegmentEvent . functionName , 'onFileUploadClick' ) ; // Arrow function methods now show proper names
529+ assert . strictEqual ( complexSegmentEvent . line , 53 ) ;
530+ assert . deepStrictEqual ( complexSegmentEvent . properties , {
531+ documentId : { type : 'any' } ,
532+ documentType : { type : 'string' }
533+ } ) ;
522534
523535 // Test PostHog event
524536 const posthogEvent = events . find ( e => e . source === 'posthog' ) ;
525537 assert . ok ( posthogEvent ) ;
526538 assert . strictEqual ( posthogEvent . eventName , 'cart_viewed' ) ;
527539 assert . strictEqual ( posthogEvent . functionName , 'useEffect()' ) ;
528- assert . strictEqual ( posthogEvent . line , 15 ) ;
540+ assert . strictEqual ( posthogEvent . line , 89 ) ;
529541 assert . deepStrictEqual ( posthogEvent . properties , {
530542 item_count : { type : 'number' } ,
531543 total_value : { type : 'number' }
532544 } ) ;
533545
534- // Test Segment event
535- const segmentEvent = events . find ( e => e . source === 'segment' ) ;
546+ // Test Segment event from useCallback
547+ const segmentEvent = events . find ( e => e . source === 'segment' && e . eventName === 'add_to_cart' ) ;
536548 assert . ok ( segmentEvent ) ;
537549 assert . strictEqual ( segmentEvent . eventName , 'add_to_cart' ) ;
538550 assert . strictEqual ( segmentEvent . functionName , 'useCallback(handleAddToCart)' ) ;
539- assert . strictEqual ( segmentEvent . line , 27 ) ;
551+ assert . strictEqual ( segmentEvent . line , 101 ) ;
540552 assert . deepStrictEqual ( segmentEvent . properties , {
541553 product_id : { type : 'string' } ,
542554 product_name : { type : 'string' } ,
@@ -548,7 +560,7 @@ test.describe('analyzeTsFile', () => {
548560 assert . ok ( amplitudeEvent ) ;
549561 assert . strictEqual ( amplitudeEvent . eventName , 'item_added' ) ;
550562 assert . strictEqual ( amplitudeEvent . functionName , 'useCallback(handleAddToCart)' ) ;
551- assert . strictEqual ( amplitudeEvent . line , 34 ) ;
563+ assert . strictEqual ( amplitudeEvent . line , 108 ) ;
552564 assert . deepStrictEqual ( amplitudeEvent . properties , {
553565 item_details : {
554566 type : 'object' ,
@@ -567,7 +579,7 @@ test.describe('analyzeTsFile', () => {
567579 assert . ok ( mixpanelEvent ) ;
568580 assert . strictEqual ( mixpanelEvent . eventName , 'remove_from_cart' ) ;
569581 assert . strictEqual ( mixpanelEvent . functionName , 'removeFromCart' ) ;
570- assert . strictEqual ( mixpanelEvent . line , 45 ) ;
582+ assert . strictEqual ( mixpanelEvent . line , 119 ) ;
571583 assert . deepStrictEqual ( mixpanelEvent . properties , {
572584 product_id : { type : 'string' } ,
573585 timestamp : { type : 'string' }
@@ -578,7 +590,7 @@ test.describe('analyzeTsFile', () => {
578590 assert . ok ( gaEvent ) ;
579591 assert . strictEqual ( gaEvent . eventName , 'begin_checkout' ) ;
580592 assert . strictEqual ( gaEvent . functionName , 'handleCheckout' ) ;
581- assert . strictEqual ( gaEvent . line , 56 ) ;
593+ assert . strictEqual ( gaEvent . line , 130 ) ;
582594 assert . deepStrictEqual ( gaEvent . properties , {
583595 items : {
584596 type : 'array' ,
@@ -596,7 +608,7 @@ test.describe('analyzeTsFile', () => {
596608 assert . ok ( rudderstackEvent ) ;
597609 assert . strictEqual ( rudderstackEvent . eventName , 'checkout_started' ) ;
598610 assert . strictEqual ( rudderstackEvent . functionName , 'handleCheckout' ) ;
599- assert . strictEqual ( rudderstackEvent . line , 63 ) ;
611+ assert . strictEqual ( rudderstackEvent . line , 137 ) ;
600612 assert . deepStrictEqual ( rudderstackEvent . properties , {
601613 products : {
602614 type : 'array' ,
@@ -613,7 +625,7 @@ test.describe('analyzeTsFile', () => {
613625 assert . ok ( mparticleEvent ) ;
614626 assert . strictEqual ( mparticleEvent . eventName , 'InitiateCheckout' ) ;
615627 assert . strictEqual ( mparticleEvent . functionName , 'handleCheckout' ) ;
616- assert . strictEqual ( mparticleEvent . line , 69 ) ;
628+ assert . strictEqual ( mparticleEvent . line , 143 ) ;
617629 assert . deepStrictEqual ( mparticleEvent . properties , {
618630 cart_items : {
619631 type : 'array' ,
@@ -624,21 +636,215 @@ test.describe('analyzeTsFile', () => {
624636 } ,
625637 checkout_step : { type : 'number' }
626638 } ) ;
639+
640+ // Note: cart_update event is only detected with custom function detection
641+ // Note: complex_operation event is only detected with custom function detection
627642 } ) ;
628643
629644 test ( 'should correctly analyze React TypeScript file with custom function' , ( ) => {
630645 const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
631646 const program = createProgram ( reactFilePath ) ;
632647 const events = analyzeTsFile ( reactFilePath , program , 'tracker.track' ) ;
633648
634- console . log ( { events } ) ;
635- const trackEvent = events . find ( e => e . source === 'custom' ) ;
649+ // Should find both tracker.track events (cart_update and complex_operation)
650+ const trackEvents = events . filter ( e => e . source === 'custom' ) ;
651+ assert . strictEqual ( trackEvents . length , 2 ) ;
636652
637- assert . strictEqual ( trackEvent . eventName , 'cart_update' ) ;
638- assert . strictEqual ( trackEvent . functionName , 'trackCartUpdate' ) ;
639- assert . strictEqual ( trackEvent . line , 81 ) ;
640- assert . deepStrictEqual ( trackEvent . properties , {
653+ const cartUpdateEvent = trackEvents . find ( e => e . eventName === 'cart_update' ) ;
654+ assert . ok ( cartUpdateEvent ) ;
655+ assert . strictEqual ( cartUpdateEvent . eventName , 'cart_update' ) ;
656+ assert . strictEqual ( cartUpdateEvent . functionName , 'trackCartUpdate' ) ;
657+ assert . strictEqual ( cartUpdateEvent . line , 155 ) ;
658+ assert . deepStrictEqual ( cartUpdateEvent . properties , {
641659 cart_size : { type : 'number' }
642660 } ) ;
661+
662+ const complexOpEvent = trackEvents . find ( e => e . eventName === 'complex_operation' ) ;
663+ assert . ok ( complexOpEvent ) ;
664+ assert . strictEqual ( complexOpEvent . eventName , 'complex_operation' ) ;
665+ assert . strictEqual ( complexOpEvent . functionName , 'handleComplexOperation' ) ;
666+ assert . strictEqual ( complexOpEvent . line , 62 ) ;
667+ assert . deepStrictEqual ( complexOpEvent . properties , {
668+ hasRef : { type : 'boolean' } ,
669+ timestamp : { type : 'number' }
670+ } ) ;
671+ } ) ;
672+
673+ // Regression tests for "Cannot read properties of undefined (reading 'kind')" fix
674+ test ( 'should handle complex React class component patterns without crashing (regression test)' , ( ) => {
675+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
676+ const program = createProgram ( reactFilePath ) ;
677+
678+ // This should not throw any errors - the main test is that it doesn't crash
679+ assert . doesNotThrow ( ( ) => {
680+ const events = analyzeTsFile ( reactFilePath , program ) ;
681+ // Should complete analysis without throwing undefined .kind errors
682+ assert . ok ( Array . isArray ( events ) ) ;
683+ assert . ok ( events . length > 0 ) ;
684+ } ) ;
685+ } ) ;
686+
687+ test ( 'should handle complex class component with custom function detection without crashing' , ( ) => {
688+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
689+ const program = createProgram ( reactFilePath ) ;
690+
691+ // This was the specific case that was causing "Cannot read properties of undefined (reading 'kind')"
692+ assert . doesNotThrow ( ( ) => {
693+ const events = analyzeTsFile ( reactFilePath , program , 'track' ) ;
694+ assert . ok ( Array . isArray ( events ) ) ;
695+
696+ // Should find the analytics.track call when looking for 'track' custom function
697+ const analyticsEvent = events . find ( e => e . eventName === 'document_upload_clicked' ) ;
698+ assert . ok ( analyticsEvent ) ;
699+ assert . strictEqual ( analyticsEvent . source , 'segment' ) ;
700+ assert . strictEqual ( analyticsEvent . line , 53 ) ;
701+ assert . deepStrictEqual ( analyticsEvent . properties , {
702+ documentId : { type : 'any' } ,
703+ documentType : { type : 'string' }
704+ } ) ;
705+ } ) ;
706+ } ) ;
707+
708+ test ( 'should handle various custom function detection patterns without undefined errors' , ( ) => {
709+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
710+ const program = createProgram ( reactFilePath ) ;
711+
712+ // Test various custom function patterns that could trigger the bug
713+ const customFunctionTests = [
714+ 'track' ,
715+ 'analytics.track' ,
716+ 'tracker.track' ,
717+ 'this.track' ,
718+ 'mixpanel.track' ,
719+ 'nonexistent.function'
720+ ] ;
721+
722+ customFunctionTests . forEach ( customFunction => {
723+ assert . doesNotThrow ( ( ) => {
724+ const events = analyzeTsFile ( reactFilePath , program , customFunction ) ;
725+ assert . ok ( Array . isArray ( events ) ) ;
726+ } , `Should not throw error with custom function: ${ customFunction } ` ) ;
727+ } ) ;
728+ } ) ;
729+
730+ test ( 'should handle nested property access expressions in custom function detection' , ( ) => {
731+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
732+ const program = createProgram ( reactFilePath ) ;
733+
734+ // Test deeply nested property access that could cause undefined node traversal
735+ const complexCustomFunctions = [
736+ 'this.props.analytics.track' ,
737+ 'window.analytics.track' ,
738+ 'deep.nested.property.track' ,
739+ 'undefined.property.access'
740+ ] ;
741+
742+ complexCustomFunctions . forEach ( customFunction => {
743+ assert . doesNotThrow ( ( ) => {
744+ const events = analyzeTsFile ( reactFilePath , program , customFunction ) ;
745+ assert . ok ( Array . isArray ( events ) ) ;
746+ } , `Should not crash with complex custom function: ${ customFunction } ` ) ;
747+ } ) ;
748+ } ) ;
749+
750+ test ( 'should correctly identify React class method contexts without undefined errors' , ( ) => {
751+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
752+ const program = createProgram ( reactFilePath ) ;
753+
754+ const events = analyzeTsFile ( reactFilePath , program ) ;
755+
756+ // Should find the analytics.track call in the arrow function method
757+ const analyticsEvent = events . find ( e => e . eventName === 'document_upload_clicked' ) ;
758+ assert . ok ( analyticsEvent ) ;
759+ assert . strictEqual ( analyticsEvent . functionName , 'onFileUploadClick' ) ; // Arrow function methods now show proper names
760+ assert . strictEqual ( analyticsEvent . source , 'segment' ) ;
761+ } ) ;
762+
763+ test ( 'should handle TypeScript React component with complex type intersections' , ( ) => {
764+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
765+ const program = createProgram ( reactFilePath ) ;
766+
767+ // The file has complex type intersections: MappedProps & ExplicitProps & ActionProps
768+ // This should not cause AST traversal issues
769+ assert . doesNotThrow ( ( ) => {
770+ const events = analyzeTsFile ( reactFilePath , program , 'uploadError' ) ;
771+ assert . ok ( Array . isArray ( events ) ) ;
772+ } ) ;
773+ } ) ;
774+
775+ test ( 'should handle React refs and generic type parameters without errors' , ( ) => {
776+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
777+ const program = createProgram ( reactFilePath ) ;
778+
779+ // The file uses React.createRef<any>() which creates complex AST nodes
780+ assert . doesNotThrow ( ( ) => {
781+ const events = analyzeTsFile ( reactFilePath , program , 'open' ) ;
782+ assert . ok ( Array . isArray ( events ) ) ;
783+ } ) ;
784+ } ) ;
785+
786+ test ( 'should handle both React functional and class components correctly' , ( ) => {
787+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
788+ const program = createProgram ( reactFilePath ) ;
789+
790+ // Should work without errors for file containing both patterns
791+ assert . doesNotThrow ( ( ) => {
792+ const events = analyzeTsFile ( reactFilePath , program , 'track' ) ;
793+
794+ assert . ok ( Array . isArray ( events ) ) ;
795+
796+ // Should have events from both functional and class components
797+ assert . ok ( events . length > 0 ) ;
798+
799+ // Should have functional component events (from hooks)
800+ const functionalEvents = events . filter ( e => e . functionName . includes ( 'useCallback' ) || e . functionName . includes ( 'useEffect' ) ) ;
801+ assert . ok ( functionalEvents . length > 0 ) ;
802+
803+ // Should have class component events (they now show proper method names)
804+ const classEvents = events . filter ( e => e . functionName === 'onFileUploadClick' || e . functionName === 'handleComplexOperation' ) ;
805+ assert . ok ( classEvents . length > 0 ) ;
806+ } ) ;
807+ } ) ;
808+
809+ test ( 'should handle edge cases in isCustomFunction without undefined property access' , ( ) => {
810+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
811+ const program = createProgram ( reactFilePath ) ;
812+
813+ // These edge cases were specifically causing the "reading 'kind'" error
814+ const edgeCaseCustomFunctions = [
815+ 'track' , // matches .track in analytics.track
816+ 'current' , // matches .current in dropzoneRef.current
817+ 'props' , // matches this.props
818+ 'state' // common React property
819+ ] ;
820+
821+ edgeCaseCustomFunctions . forEach ( customFunction => {
822+ assert . doesNotThrow ( ( ) => {
823+ const events = analyzeTsFile ( reactFilePath , program , customFunction ) ;
824+ assert . ok ( Array . isArray ( events ) ) ;
825+ } , `Should handle edge case custom function: ${ customFunction } ` ) ;
826+ } ) ;
827+ } ) ;
828+
829+ test ( 'should preserve correct event extraction while fixing undefined errors' , ( ) => {
830+ const reactFilePath = path . join ( fixturesDir , 'typescript-react' , 'main.tsx' ) ;
831+ const program = createProgram ( reactFilePath ) ;
832+
833+ // Verify that our fix doesn't break the actual tracking detection
834+ const events = analyzeTsFile ( reactFilePath , program ) ;
835+
836+ // Should correctly identify multiple tracking events including the complex class component
837+ assert . ok ( events . length >= 8 ) ;
838+
839+ // Should still correctly identify the analytics.track call from complex component
840+ const complexEvent = events . find ( e => e . eventName === 'document_upload_clicked' ) ;
841+ assert . ok ( complexEvent ) ;
842+ assert . strictEqual ( complexEvent . source , 'segment' ) ;
843+ assert . strictEqual ( complexEvent . functionName , 'onFileUploadClick' ) ;
844+ assert . strictEqual ( complexEvent . line , 53 ) ;
845+ assert . deepStrictEqual ( complexEvent . properties , {
846+ documentId : { type : 'any' } ,
847+ documentType : { type : 'string' }
848+ } ) ;
643849 } ) ;
644850} ) ;
0 commit comments