@@ -43,8 +43,61 @@ function detectAnalyticsSource(node, customFunction) {
4343 * @returns {boolean }
4444 */
4545function isCustomFunction ( node , customFunction ) {
46- return node . callee . type === NODE_TYPES . IDENTIFIER &&
47- node . callee . name === customFunction ;
46+ if ( ! customFunction ) return false ;
47+
48+ // Support dot-separated names like "CustomModule.track"
49+ const parts = customFunction . split ( '.' ) ;
50+
51+ // Simple identifier (no dot)
52+ if ( parts . length === 1 ) {
53+ return node . callee . type === NODE_TYPES . IDENTIFIER && node . callee . name === customFunction ;
54+ }
55+
56+ // For dot-separated names, the callee should be a MemberExpression chain.
57+ if ( node . callee . type !== NODE_TYPES . MEMBER_EXPRESSION ) {
58+ return false ;
59+ }
60+
61+ return matchesMemberChain ( node . callee , parts ) ;
62+ }
63+
64+ /**
65+ * Recursively verifies that a MemberExpression chain matches the expected parts.
66+ * Example: parts ["CustomModule", "track"] should match `CustomModule.track()`.
67+ * @param {Object } memberExpr - AST MemberExpression node
68+ * @param {string[] } parts - Expected name segments (left -> right)
69+ * @returns {boolean }
70+ */
71+ function matchesMemberChain ( memberExpr , parts ) {
72+ let currentNode = memberExpr ;
73+ let idx = parts . length - 1 ; // start from the rightmost property
74+
75+ while ( currentNode && idx >= 0 ) {
76+ const expectedPart = parts [ idx ] ;
77+
78+ // property should match current expectedPart
79+ if ( currentNode . type === NODE_TYPES . MEMBER_EXPRESSION ) {
80+ // Ensure property is Identifier and matches
81+ if (
82+ currentNode . property . type !== NODE_TYPES . IDENTIFIER ||
83+ currentNode . property . name !== expectedPart
84+ ) {
85+ return false ;
86+ }
87+
88+ // Move to the object of the MemberExpression
89+ currentNode = currentNode . object ;
90+ idx -= 1 ;
91+ } else if ( currentNode . type === NODE_TYPES . IDENTIFIER ) {
92+ // We reached the leftmost Identifier; it should match the first part
93+ return idx === 0 && currentNode . name === expectedPart ;
94+ } else {
95+ // Unexpected node type (e.g., ThisExpression, CallExpression, etc.)
96+ return false ;
97+ }
98+ }
99+
100+ return false ;
48101}
49102
50103/**
0 commit comments