1+ /**
2+ * @fileoverview Analytics source detection for Swift tracking calls
3+ * @module analyze/swift/detectors/analytics-source
4+ */
5+
6+ const { ANALYTICS_PROVIDERS } = require ( '../constants' ) ;
7+
8+ /**
9+ * Detects the analytics source from a Swift function call expression
10+ * @param {Object } callExpression - Swift AST call expression node
11+ * @param {string|null } customFunction - Optional custom function name to detect
12+ * @returns {string|null } - The detected source or null
13+ */
14+ function detectAnalyticsSource ( callExpression , customFunction = null ) {
15+ if ( ! callExpression ) return null ;
16+
17+ // Handle custom function detection first
18+ if ( customFunction && isCustomFunction ( callExpression , customFunction ) ) {
19+ return 'custom' ;
20+ }
21+
22+ // Handle built-in analytics providers
23+ return detectBuiltinProvider ( callExpression ) ;
24+ }
25+
26+ /**
27+ * Detects if a call expression matches a custom function
28+ * @param {Object } callExpression - Swift AST call expression node
29+ * @param {string } customFunction - Custom function name or pattern
30+ * @returns {boolean }
31+ */
32+ function isCustomFunction ( callExpression , customFunction ) {
33+ if ( ! callExpression . calledExpression ) return false ;
34+
35+ // Extract function name from call expression
36+ const functionName = extractFunctionName ( callExpression . calledExpression ) ;
37+
38+ if ( ! functionName ) return false ;
39+
40+ // Handle simple function names
41+ if ( typeof customFunction === 'string' ) {
42+ return functionName === customFunction ;
43+ }
44+
45+ // Handle custom function object with functionName property
46+ if ( customFunction . functionName ) {
47+ return functionName === customFunction . functionName ;
48+ }
49+
50+ return false ;
51+ }
52+
53+ /**
54+ * Detects built-in analytics providers from a call expression
55+ * @param {Object } callExpression - Swift AST call expression node
56+ * @returns {string|null }
57+ */
58+ function detectBuiltinProvider ( callExpression ) {
59+ if ( ! callExpression . calledExpression ) return null ;
60+
61+ const expr = callExpression . calledExpression ;
62+
63+ // Handle method calls (object.method())
64+ if ( expr . kind === 'MemberAccessExpr' ) {
65+ return detectMethodCall ( expr ) ;
66+ }
67+
68+ // Handle direct function calls (functionName())
69+ if ( expr . kind === 'UnresolvedDeclRefExpr' || expr . kind === 'DeclRefExpr' ) {
70+ return detectDirectFunctionCall ( expr ) ;
71+ }
72+
73+ // Handle chained calls (Object.shared().method())
74+ if ( expr . kind === 'DotSyntaxCallExpr' ) {
75+ return detectChainedCall ( expr ) ;
76+ }
77+
78+ return null ;
79+ }
80+
81+ /**
82+ * Detects analytics provider from method call expressions
83+ * @param {Object } memberExpr - Swift AST member access expression
84+ * @returns {string|null }
85+ */
86+ function detectMethodCall ( memberExpr ) {
87+ if ( ! memberExpr . member || ! memberExpr . base ) return null ;
88+
89+ const methodName = extractIdentifierName ( memberExpr . member ) ;
90+ const baseName = extractBaseName ( memberExpr . base ) ;
91+
92+ // Check each provider
93+ for ( const provider of Object . values ( ANALYTICS_PROVIDERS ) ) {
94+ if ( provider . methodName === methodName ) {
95+ // Check if base matches the provider's object name
96+ if ( baseName === provider . objectName ) {
97+ return provider . name ;
98+ }
99+
100+ // Check for property access (e.g., PostHogSDK.shared.capture)
101+ if ( provider . property && memberExpr . base . kind === 'MemberAccessExpr' ) {
102+ const baseBase = extractBaseName ( memberExpr . base . base ) ;
103+ const baseProperty = extractIdentifierName ( memberExpr . base . member ) ;
104+
105+ if ( baseBase === provider . objectName && baseProperty === provider . property ) {
106+ return provider . name ;
107+ }
108+ }
109+ }
110+ }
111+
112+ return null ;
113+ }
114+
115+ /**
116+ * Detects analytics provider from direct function calls
117+ * @param {Object } declRefExpr - Swift AST declaration reference expression
118+ * @returns {string|null }
119+ */
120+ function detectDirectFunctionCall ( declRefExpr ) {
121+ const functionName = extractIdentifierName ( declRefExpr ) ;
122+
123+ // Check for Firebase Analytics.logEvent pattern
124+ if ( functionName === 'logEvent' ) {
125+ return ANALYTICS_PROVIDERS . FIREBASE . name ;
126+ }
127+
128+ return null ;
129+ }
130+
131+ /**
132+ * Detects analytics provider from chained method calls
133+ * @param {Object } chainExpr - Swift AST chained call expression
134+ * @returns {string|null }
135+ */
136+ function detectChainedCall ( chainExpr ) {
137+ // Handle patterns like Analytics.shared().track()
138+ if ( ! chainExpr . fn || ! chainExpr . argument ) return null ;
139+
140+ const methodName = extractFunctionName ( chainExpr . fn ) ;
141+ const chainBase = extractChainBase ( chainExpr . argument ) ;
142+
143+ for ( const provider of Object . values ( ANALYTICS_PROVIDERS ) ) {
144+ if ( provider . methodName === methodName && provider . chainMethods ) {
145+ // Check if the chain matches the provider pattern
146+ if ( chainBase === provider . objectName ) {
147+ return provider . name ;
148+ }
149+ }
150+ }
151+
152+ return null ;
153+ }
154+
155+ /**
156+ * Extracts function name from various expression types
157+ * @param {Object } expr - Swift AST expression
158+ * @returns {string|null }
159+ */
160+ function extractFunctionName ( expr ) {
161+ if ( ! expr ) return null ;
162+
163+ if ( expr . kind === 'UnresolvedDeclRefExpr' || expr . kind === 'DeclRefExpr' ) {
164+ return extractIdentifierName ( expr ) ;
165+ }
166+
167+ if ( expr . kind === 'MemberAccessExpr' ) {
168+ return extractIdentifierName ( expr . member ) ;
169+ }
170+
171+ return null ;
172+ }
173+
174+ /**
175+ * Extracts identifier name from declaration reference
176+ * @param {Object } declRef - Swift AST declaration reference
177+ * @returns {string|null }
178+ */
179+ function extractIdentifierName ( declRef ) {
180+ if ( ! declRef ) return null ;
181+
182+ // Handle different identifier structures
183+ if ( declRef . identifier && declRef . identifier . name ) {
184+ return declRef . identifier . name ;
185+ }
186+
187+ if ( declRef . name ) {
188+ return declRef . name ;
189+ }
190+
191+ if ( typeof declRef === 'string' ) {
192+ return declRef ;
193+ }
194+
195+ return null ;
196+ }
197+
198+ /**
199+ * Extracts base name from expressions
200+ * @param {Object } base - Swift AST base expression
201+ * @returns {string|null }
202+ */
203+ function extractBaseName ( base ) {
204+ if ( ! base ) return null ;
205+
206+ if ( base . kind === 'UnresolvedDeclRefExpr' || base . kind === 'DeclRefExpr' ) {
207+ return extractIdentifierName ( base ) ;
208+ }
209+
210+ if ( base . kind === 'MemberAccessExpr' ) {
211+ return extractBaseName ( base . base ) ;
212+ }
213+
214+ return null ;
215+ }
216+
217+ /**
218+ * Extracts the base object from a chained call
219+ * @param {Object } chainArg - Swift AST chain argument
220+ * @returns {string|null }
221+ */
222+ function extractChainBase ( chainArg ) {
223+ if ( ! chainArg ) return null ;
224+
225+ if ( chainArg . kind === 'UnresolvedDeclRefExpr' || chainArg . kind === 'DeclRefExpr' ) {
226+ return extractIdentifierName ( chainArg ) ;
227+ }
228+
229+ if ( chainArg . kind === 'MemberAccessExpr' ) {
230+ return extractBaseName ( chainArg . base ) ;
231+ }
232+
233+ return null ;
234+ }
235+
236+ module . exports = {
237+ detectAnalyticsSource
238+ } ;
0 commit comments