@@ -45,20 +45,23 @@ function detectAnalyticsSource(node, customFunction) {
4545function isCustomFunction ( node , customFunction ) {
4646 if ( ! customFunction ) return false ;
4747
48- // Support dot-separated names like "CustomModule.track"
49- const parts = customFunction . split ( '.' ) ;
48+ // Support dot-separated names like "CustomModule.track" and chained calls like "getTrackingService().track"
49+ // Normalize each segment by stripping trailing parentheses
50+ const parts = customFunction . split ( '.' ) . map ( p => p . replace ( / \( \s * \) $ / , '' ) ) ;
5051
5152 // Simple identifier (no dot)
5253 if ( parts . length === 1 ) {
53- return node . callee . type === NODE_TYPES . IDENTIFIER && node . callee . name === customFunction ;
54+ return node . callee . type === NODE_TYPES . IDENTIFIER && node . callee . name === parts [ 0 ] ;
5455 }
5556
56- // For dot-separated names, the callee should be a MemberExpression chain.
57- if ( node . callee . type !== NODE_TYPES . MEMBER_EXPRESSION ) {
57+ // For dot-separated names, the callee should be a MemberExpression chain,
58+ // but we also allow CallExpression in the chain (e.g., getService().track)
59+ const callee = node . callee ;
60+ if ( callee . type !== NODE_TYPES . MEMBER_EXPRESSION && callee . type !== NODE_TYPES . CALL_EXPRESSION ) {
5861 return false ;
5962 }
6063
61- return matchesMemberChain ( node . callee , parts ) ;
64+ return matchesMemberChain ( callee , parts ) ;
6265}
6366
6467/**
@@ -75,26 +78,34 @@ function matchesMemberChain(memberExpr, parts) {
7578 while ( currentNode && idx >= 0 ) {
7679 const expectedPart = parts [ idx ] ;
7780
78- // property should match current expectedPart
7981 if ( currentNode . type === NODE_TYPES . MEMBER_EXPRESSION ) {
80- // Ensure property is Identifier and matches
82+ // Ensure property is Identifier and matches the expected part
8183 if (
8284 currentNode . property . type !== NODE_TYPES . IDENTIFIER ||
8385 currentNode . property . name !== expectedPart
8486 ) {
8587 return false ;
8688 }
8789
88- // Move to the object of the MemberExpression
90+ // Move to the object (which could itself be a MemberExpression, Identifier, or CallExpression)
8991 currentNode = currentNode . object ;
9092 idx -= 1 ;
91- } else if ( currentNode . type === NODE_TYPES . IDENTIFIER ) {
92- // We reached the leftmost Identifier; it should match the first part
93+ continue ;
94+ }
95+
96+ // If we encounter a CallExpression in the chain (e.g., getService().track),
97+ // step into its callee without consuming an expected part.
98+ if ( currentNode . type === NODE_TYPES . CALL_EXPRESSION ) {
99+ currentNode = currentNode . callee ;
100+ continue ;
101+ }
102+
103+ if ( currentNode . type === NODE_TYPES . IDENTIFIER ) {
93104 return idx === 0 && currentNode . name === expectedPart ;
94- } else {
95- // Unexpected node type (e.g., ThisExpression, CallExpression, etc.)
96- return false ;
97105 }
106+
107+ // Unexpected node type (e.g., ThisExpression, Literal, etc.)
108+ return false ;
98109 }
99110
100111 return false ;
0 commit comments