@@ -66,22 +66,95 @@ function parseFile(filePath) {
6666 }
6767}
6868
69+ // ---------------------------------------------
70+ // Helper – custom function matcher
71+ // ---------------------------------------------
72+
6973/**
70- * Walks the AST and finds analytics tracking calls
71- * @param {Object } ast - Parsed AST
72- * @param {string } filePath - Path to the file being analyzed
73- * @param {Object } [customConfig] - Custom function configuration object
74- * @returns {Array<Object> } Array of found events
74+ * Determines whether a CallExpression node matches the provided custom function name.
75+ * Supports both simple identifiers (e.g. myTrack) and dot-separated members (e.g. Custom.track).
76+ * The logic mirrors isCustomFunction from detectors/analytics-source.js but is kept local to avoid
77+ * circular dependencies.
78+ * @param {Object } node – CallExpression AST node
79+ * @param {string } fnName – Custom function name (could include dots)
80+ * @returns {boolean }
7581 */
76- function findTrackingEvents ( ast , filePath , customConfig ) {
82+ function nodeMatchesCustomFunction ( node , fnName ) {
83+ if ( ! fnName || ! node . callee ) return false ;
84+
85+ const parts = fnName . split ( '.' ) ;
86+
87+ // Simple identifier case
88+ if ( parts . length === 1 ) {
89+ return node . callee . type === NODE_TYPES . IDENTIFIER && node . callee . name === fnName ;
90+ }
91+
92+ // Member expression chain case
93+ if ( node . callee . type !== NODE_TYPES . MEMBER_EXPRESSION ) {
94+ return false ;
95+ }
96+
97+ // Walk the chain from the right-most property to the leftmost object
98+ let currentNode = node . callee ;
99+ let idx = parts . length - 1 ;
100+
101+ while ( currentNode && idx >= 0 ) {
102+ const expected = parts [ idx ] ;
103+
104+ if ( currentNode . type === NODE_TYPES . MEMBER_EXPRESSION ) {
105+ if (
106+ currentNode . property . type !== NODE_TYPES . IDENTIFIER ||
107+ currentNode . property . name !== expected
108+ ) {
109+ return false ;
110+ }
111+ currentNode = currentNode . object ;
112+ idx -= 1 ;
113+ } else if ( currentNode . type === NODE_TYPES . IDENTIFIER ) {
114+ return idx === 0 && currentNode . name === expected ;
115+ } else {
116+ return false ;
117+ }
118+ }
119+
120+ return false ;
121+ }
122+
123+ /**
124+ * Walk the AST once and find tracking events for built-in providers plus any number of custom
125+ * function configurations. This avoids the previous O(n * customConfigs) behaviour.
126+ *
127+ * @param {Object } ast – Parsed AST of the source file
128+ * @param {string } filePath – Absolute/relative path to the source file
129+ * @param {Object[] } [customConfigs=[]] – Array of parsed custom function configurations
130+ * @returns {Array<Object> } – List of extracted tracking events
131+ */
132+ function findTrackingEvents ( ast , filePath , customConfigs = [ ] ) {
77133 const events = [ ] ;
78134
79135 walk . ancestor ( ast , {
80136 [ NODE_TYPES . CALL_EXPRESSION ] : ( node , ancestors ) => {
81137 try {
82- const event = extractTrackingEvent ( node , ancestors , filePath , customConfig ) ;
83- if ( event ) {
84- events . push ( event ) ;
138+ let matchedCustomConfig = null ;
139+
140+ // Attempt to match any custom function first to avoid mis-classifying built-in providers
141+ if ( Array . isArray ( customConfigs ) && customConfigs . length > 0 ) {
142+ for ( const cfg of customConfigs ) {
143+ if ( cfg && nodeMatchesCustomFunction ( node , cfg . functionName ) ) {
144+ matchedCustomConfig = cfg ;
145+ break ;
146+ }
147+ }
148+ }
149+
150+ if ( matchedCustomConfig ) {
151+ // Force source to 'custom' and use matched config
152+ const event = extractTrackingEvent ( node , ancestors , filePath , matchedCustomConfig ) ;
153+ if ( event ) events . push ( event ) ;
154+ } else {
155+ // Let built-in detector figure out source (pass undefined customFunction)
156+ const event = extractTrackingEvent ( node , ancestors , filePath , null ) ;
157+ if ( event ) events . push ( event ) ;
85158 }
86159 } catch ( error ) {
87160 console . error ( `Error processing node in ${ filePath } :` , error . message ) ;
0 commit comments