@flisk/analyze-tracking 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +4 -0
  2. package/bin/cli.js +30 -2
  3. package/package.json +12 -8
  4. package/src/analyze/go/astTraversal.js +121 -0
  5. package/src/analyze/go/constants.js +20 -0
  6. package/src/analyze/go/eventDeduplicator.js +47 -0
  7. package/src/analyze/go/eventExtractor.js +156 -0
  8. package/src/analyze/go/goAstParser/constants.js +39 -0
  9. package/src/analyze/go/goAstParser/expressionParser.js +281 -0
  10. package/src/analyze/go/goAstParser/index.js +52 -0
  11. package/src/analyze/go/goAstParser/statementParser.js +387 -0
  12. package/src/analyze/go/goAstParser/tokenizer.js +196 -0
  13. package/src/analyze/go/goAstParser/typeParser.js +202 -0
  14. package/src/analyze/go/goAstParser/utils.js +99 -0
  15. package/src/analyze/go/index.js +55 -0
  16. package/src/analyze/go/propertyExtractor.js +670 -0
  17. package/src/analyze/go/trackingDetector.js +71 -0
  18. package/src/analyze/go/trackingExtractor.js +54 -0
  19. package/src/analyze/go/typeContext.js +88 -0
  20. package/src/analyze/go/utils.js +215 -0
  21. package/src/analyze/index.js +11 -6
  22. package/src/analyze/javascript/constants.js +115 -0
  23. package/src/analyze/javascript/detectors/analytics-source.js +119 -0
  24. package/src/analyze/javascript/detectors/index.js +10 -0
  25. package/src/analyze/javascript/extractors/event-extractor.js +179 -0
  26. package/src/analyze/javascript/extractors/index.js +13 -0
  27. package/src/analyze/javascript/extractors/property-extractor.js +172 -0
  28. package/src/analyze/javascript/index.js +38 -0
  29. package/src/analyze/javascript/parser.js +126 -0
  30. package/src/analyze/javascript/utils/function-finder.js +123 -0
  31. package/src/analyze/python/index.js +111 -0
  32. package/src/analyze/python/pythonTrackingAnalyzer.py +814 -0
  33. package/src/analyze/ruby/detectors.js +46 -0
  34. package/src/analyze/ruby/extractors.js +258 -0
  35. package/src/analyze/ruby/index.js +51 -0
  36. package/src/analyze/ruby/traversal.js +123 -0
  37. package/src/analyze/ruby/types.js +30 -0
  38. package/src/analyze/ruby/visitor.js +66 -0
  39. package/src/analyze/typescript/constants.js +109 -0
  40. package/src/analyze/typescript/detectors/analytics-source.js +125 -0
  41. package/src/analyze/typescript/detectors/index.js +10 -0
  42. package/src/analyze/typescript/extractors/event-extractor.js +269 -0
  43. package/src/analyze/typescript/extractors/index.js +14 -0
  44. package/src/analyze/typescript/extractors/property-extractor.js +427 -0
  45. package/src/analyze/typescript/index.js +48 -0
  46. package/src/analyze/typescript/parser.js +131 -0
  47. package/src/analyze/typescript/utils/function-finder.js +139 -0
  48. package/src/analyze/typescript/utils/type-resolver.js +208 -0
  49. package/src/generateDescriptions/index.js +81 -0
  50. package/src/generateDescriptions/llmUtils.js +33 -0
  51. package/src/generateDescriptions/promptUtils.js +62 -0
  52. package/src/generateDescriptions/schemaUtils.js +61 -0
  53. package/src/index.js +13 -4
  54. package/src/{fileProcessor.js → utils/fileProcessor.js} +5 -0
  55. package/src/{repoDetails.js → utils/repoDetails.js} +5 -0
  56. package/src/utils/yamlGenerator.js +47 -0
  57. package/src/analyze/analyzeGoFile.js +0 -1164
  58. package/src/analyze/analyzeJsFile.js +0 -87
  59. package/src/analyze/analyzePythonFile.js +0 -42
  60. package/src/analyze/analyzeRubyFile.js +0 -419
  61. package/src/analyze/analyzeTsFile.js +0 -192
  62. package/src/analyze/go2json.js +0 -1069
  63. package/src/analyze/helpers.js +0 -656
  64. package/src/analyze/pythonTrackingAnalyzer.py +0 -541
  65. package/src/generateDescriptions.js +0 -196
  66. package/src/yamlGenerator.js +0 -23
@@ -0,0 +1,427 @@
1
+ /**
2
+ * @fileoverview Property extraction from TypeScript AST nodes
3
+ * @module analyze/typescript/extractors/property-extractor
4
+ */
5
+
6
+ const ts = require('typescript');
7
+ const {
8
+ getTypeOfNode,
9
+ resolveTypeToProperties,
10
+ getBasicTypeOfArrayElement,
11
+ isCustomType
12
+ } = require('../utils/type-resolver');
13
+
14
+ /**
15
+ * Property structure representation
16
+ * @typedef {Object} PropertySchema
17
+ * @property {string} type - The property type (string, number, boolean, object, array, any)
18
+ * @property {PropertySchema} [properties] - Nested properties for objects
19
+ * @property {Object} [items] - Item type information for arrays
20
+ * @property {string} [__unresolved] - Unresolved type name marker
21
+ */
22
+
23
+ /**
24
+ * Extracts properties from a TypeScript ObjectLiteralExpression node
25
+ * @param {Object} checker - TypeScript type checker
26
+ * @param {Object} node - ObjectLiteralExpression node
27
+ * @returns {Object.<string, PropertySchema>} Extracted properties with their schemas
28
+ */
29
+ function extractProperties(checker, node) {
30
+ if (!node || !ts.isObjectLiteralExpression(node)) {
31
+ return {};
32
+ }
33
+
34
+ const properties = {};
35
+
36
+ for (const prop of node.properties) {
37
+ const key = getPropertyKey(prop);
38
+ if (!key) continue;
39
+
40
+ const schema = extractPropertySchema(checker, prop);
41
+ if (schema) {
42
+ properties[key] = schema;
43
+ }
44
+ }
45
+
46
+ return properties;
47
+ }
48
+
49
+ /**
50
+ * Gets the key name from a property node
51
+ * @param {Object} prop - Property node
52
+ * @returns {string|null} Property key or null
53
+ */
54
+ function getPropertyKey(prop) {
55
+ if (!prop.name) {
56
+ // Shorthand property assignment
57
+ if (ts.isShorthandPropertyAssignment(prop)) {
58
+ return prop.name.escapedText;
59
+ }
60
+ return null;
61
+ }
62
+
63
+ // Regular property with name
64
+ if (ts.isIdentifier(prop.name)) {
65
+ return prop.name.escapedText;
66
+ }
67
+
68
+ if (ts.isStringLiteral(prop.name)) {
69
+ return prop.name.text;
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ /**
76
+ * Extracts schema information from a property
77
+ * @param {Object} checker - TypeScript type checker
78
+ * @param {Object} prop - Property node
79
+ * @returns {PropertySchema|null} Property schema or null
80
+ */
81
+ function extractPropertySchema(checker, prop) {
82
+ // Handle shorthand property assignments
83
+ if (ts.isShorthandPropertyAssignment(prop)) {
84
+ return extractShorthandPropertySchema(checker, prop);
85
+ }
86
+
87
+ // Handle property assignments with initializers
88
+ if (ts.isPropertyAssignment(prop)) {
89
+ if (prop.initializer) {
90
+ return extractValueSchema(checker, prop.initializer);
91
+ }
92
+
93
+ // Property with type annotation but no initializer
94
+ if (prop.type) {
95
+ const typeString = checker.typeToString(checker.getTypeFromTypeNode(prop.type));
96
+ return resolveTypeSchema(checker, typeString);
97
+ }
98
+ }
99
+
100
+ // Handle method declarations
101
+ if (ts.isMethodDeclaration(prop)) {
102
+ return { type: 'function' };
103
+ }
104
+
105
+ return null;
106
+ }
107
+
108
+ /**
109
+ * Extracts schema for shorthand property assignments
110
+ * @param {Object} checker - TypeScript type checker
111
+ * @param {Object} prop - ShorthandPropertyAssignment node
112
+ * @returns {PropertySchema}
113
+ */
114
+ function extractShorthandPropertySchema(checker, prop) {
115
+ const symbol = checker.getSymbolAtLocation(prop.name);
116
+ if (!symbol) {
117
+ return { type: 'any' };
118
+ }
119
+ const declarations = symbol.declarations || [];
120
+ for (const decl of declarations) {
121
+ // Detect destructuring from useState: const [state, setState] = useState<Type>(...)
122
+ if (
123
+ ts.isBindingElement(decl) &&
124
+ decl.parent &&
125
+ ts.isArrayBindingPattern(decl.parent) &&
126
+ decl.parent.parent &&
127
+ ts.isVariableDeclaration(decl.parent.parent) &&
128
+ decl.parent.parent.initializer &&
129
+ ts.isCallExpression(decl.parent.parent.initializer) &&
130
+ ts.isIdentifier(decl.parent.parent.initializer.expression) &&
131
+ decl.parent.parent.initializer.expression.escapedText === 'useState'
132
+ ) {
133
+ // Try to get type from generic argument
134
+ const callExpr = decl.parent.parent.initializer;
135
+ if (callExpr.typeArguments && callExpr.typeArguments.length > 0) {
136
+ const typeNode = callExpr.typeArguments[0];
137
+ const type = checker.getTypeFromTypeNode(typeNode);
138
+ const typeString = checker.typeToString(type);
139
+ return resolveTypeToProperties(checker, typeString);
140
+ }
141
+ // Fallback: get type from initial value
142
+ if (callExpr.arguments && callExpr.arguments.length > 0) {
143
+ const initType = checker.getTypeAtLocation(callExpr.arguments[0]);
144
+ const typeString = checker.typeToString(initType);
145
+ return resolveTypeToProperties(checker, typeString);
146
+ }
147
+ // Default to any
148
+ return { type: 'any' };
149
+ }
150
+ }
151
+
152
+ const propType = checker.getTypeAtLocation(prop.name);
153
+ const typeString = checker.typeToString(propType);
154
+
155
+ // Handle array types
156
+ if (isArrayType(typeString)) {
157
+ return extractArrayTypeSchema(checker, propType, typeString);
158
+ }
159
+
160
+ // Handle other types
161
+ const resolvedType = resolveTypeToProperties(checker, typeString);
162
+
163
+ // If it's an unresolved custom type, try to extract interface properties
164
+ if (resolvedType.__unresolved) {
165
+ const interfaceProps = extractInterfaceProperties(checker, propType);
166
+ if (Object.keys(interfaceProps).length > 0) {
167
+ return {
168
+ type: 'object',
169
+ properties: interfaceProps
170
+ };
171
+ }
172
+ }
173
+
174
+ return resolvedType;
175
+ }
176
+
177
+ /**
178
+ * Extracts schema from a value node
179
+ * @param {Object} checker - TypeScript type checker
180
+ * @param {Object} valueNode - Value node to extract schema from
181
+ * @returns {PropertySchema}
182
+ */
183
+ function extractValueSchema(checker, valueNode) {
184
+ // Object literal
185
+ if (ts.isObjectLiteralExpression(valueNode)) {
186
+ return {
187
+ type: 'object',
188
+ properties: extractProperties(checker, valueNode)
189
+ };
190
+ }
191
+
192
+ // Array literal
193
+ if (ts.isArrayLiteralExpression(valueNode)) {
194
+ return extractArrayLiteralSchema(checker, valueNode);
195
+ }
196
+
197
+ // Identifier (variable reference)
198
+ if (ts.isIdentifier(valueNode)) {
199
+ return extractIdentifierSchema(checker, valueNode);
200
+ }
201
+
202
+ // Literal values
203
+ const literalType = getLiteralType(valueNode);
204
+ if (literalType) {
205
+ return { type: literalType };
206
+ }
207
+
208
+ // For other expressions, get the type from TypeChecker
209
+ const typeString = getTypeOfNode(checker, valueNode);
210
+ return resolveTypeSchema(checker, typeString);
211
+ }
212
+
213
+ /**
214
+ * Extracts schema for array literals
215
+ * @param {Object} checker - TypeScript type checker
216
+ * @param {Object} node - ArrayLiteralExpression node
217
+ * @returns {PropertySchema}
218
+ */
219
+ function extractArrayLiteralSchema(checker, node) {
220
+ if (node.elements.length === 0) {
221
+ return {
222
+ type: 'array',
223
+ items: { type: 'any' }
224
+ };
225
+ }
226
+
227
+ // Check types of all elements
228
+ const elementTypes = new Set();
229
+ for (const element of node.elements) {
230
+ const elemType = getBasicTypeOfArrayElement(checker, element);
231
+ elementTypes.add(elemType);
232
+ }
233
+
234
+ // If all elements are the same type, use that type
235
+ const itemType = elementTypes.size === 1 ? Array.from(elementTypes)[0] : 'any';
236
+
237
+ return {
238
+ type: 'array',
239
+ items: { type: itemType }
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Extracts schema for identifier references
245
+ * @param {Object} checker - TypeScript type checker
246
+ * @param {Object} identifier - Identifier node
247
+ * @returns {PropertySchema}
248
+ */
249
+ function extractIdentifierSchema(checker, identifier) {
250
+ const identifierType = checker.getTypeAtLocation(identifier);
251
+ const typeString = checker.typeToString(identifierType);
252
+
253
+ // Handle array types
254
+ if (isArrayType(typeString)) {
255
+ return extractArrayTypeSchema(checker, identifierType, typeString);
256
+ }
257
+
258
+ // Handle other types
259
+ const resolvedType = resolveTypeToProperties(checker, typeString);
260
+
261
+ // If it's an unresolved custom type, try to extract interface properties
262
+ if (resolvedType.__unresolved) {
263
+ const interfaceProps = extractInterfaceProperties(checker, identifierType);
264
+ if (Object.keys(interfaceProps).length > 0) {
265
+ return {
266
+ type: 'object',
267
+ properties: interfaceProps
268
+ };
269
+ }
270
+ }
271
+
272
+ return resolvedType;
273
+ }
274
+
275
+ /**
276
+ * Extracts schema for array types
277
+ * @param {Object} checker - TypeScript type checker
278
+ * @param {Object} type - TypeScript Type object
279
+ * @param {string} typeString - String representation of the type
280
+ * @returns {PropertySchema}
281
+ */
282
+ function extractArrayTypeSchema(checker, type, typeString) {
283
+ let elementType = null;
284
+
285
+ // Try to get type arguments for generic types
286
+ if (type.target && type.typeArguments && type.typeArguments.length > 0) {
287
+ elementType = type.typeArguments[0];
288
+ }
289
+ // Try indexed access for array types
290
+ else {
291
+ try {
292
+ const numberType = checker.getNumberType();
293
+ elementType = checker.getIndexedAccessType(type, numberType);
294
+ } catch (e) {
295
+ // Indexed access failed
296
+ }
297
+ }
298
+
299
+ if (elementType) {
300
+ const elementInterfaceProps = extractInterfaceProperties(checker, elementType);
301
+ if (Object.keys(elementInterfaceProps).length > 0) {
302
+ return {
303
+ type: 'array',
304
+ items: {
305
+ type: 'object',
306
+ properties: elementInterfaceProps
307
+ }
308
+ };
309
+ } else {
310
+ const elementTypeString = checker.typeToString(elementType);
311
+ if (isCustomType(elementTypeString)) {
312
+ return {
313
+ type: 'array',
314
+ items: { type: 'object' }
315
+ };
316
+ }
317
+ return {
318
+ type: 'array',
319
+ items: resolveTypeToProperties(checker, elementTypeString)
320
+ };
321
+ }
322
+ }
323
+
324
+ return {
325
+ type: 'array',
326
+ items: { type: 'any' }
327
+ };
328
+ }
329
+
330
+ /**
331
+ * Resolves a type string to a schema
332
+ * @param {Object} checker - TypeScript type checker
333
+ * @param {string} typeString - Type string
334
+ * @returns {PropertySchema}
335
+ */
336
+ function resolveTypeSchema(checker, typeString) {
337
+ const resolvedType = resolveTypeToProperties(checker, typeString);
338
+
339
+ // Clean up any unresolved markers for simple types
340
+ if (resolvedType.__unresolved) {
341
+ delete resolvedType.__unresolved;
342
+ }
343
+
344
+ return resolvedType;
345
+ }
346
+
347
+ /**
348
+ * Gets the literal type of a node
349
+ * @param {Object} node - AST node
350
+ * @returns {string|null} Literal type or null
351
+ */
352
+ function getLiteralType(node) {
353
+ if (ts.isStringLiteral(node)) return 'string';
354
+ if (ts.isNumericLiteral(node)) return 'number';
355
+ if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return 'boolean';
356
+ if (node.kind === ts.SyntaxKind.NullKeyword) return 'null';
357
+ if (node.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
358
+ return null;
359
+ }
360
+
361
+ /**
362
+ * Checks if a type string represents an array type
363
+ * @param {string} typeString - Type string to check
364
+ * @returns {boolean}
365
+ */
366
+ function isArrayType(typeString) {
367
+ return typeString.includes('[]') ||
368
+ typeString.startsWith('Array<') ||
369
+ typeString.startsWith('ReadonlyArray<') ||
370
+ typeString.startsWith('readonly ');
371
+ }
372
+
373
+ /**
374
+ * Extracts properties from a TypeScript interface or type
375
+ * @param {Object} checker - TypeScript type checker
376
+ * @param {Object} type - TypeScript Type object
377
+ * @returns {Object.<string, PropertySchema>}
378
+ */
379
+ function extractInterfaceProperties(checker, type) {
380
+ const properties = {};
381
+ const typeSymbol = type.getSymbol();
382
+
383
+ if (!typeSymbol) return properties;
384
+
385
+ // Get all properties of the type
386
+ const members = checker.getPropertiesOfType(type);
387
+
388
+ for (const member of members) {
389
+ try {
390
+ const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
391
+ const memberTypeString = checker.typeToString(memberType);
392
+
393
+ // Recursively resolve the member type
394
+ const resolvedType = resolveTypeToProperties(checker, memberTypeString);
395
+
396
+ // If it's an unresolved object type, try to extract its properties
397
+ if (resolvedType.__unresolved) {
398
+ const nestedProperties = extractInterfaceProperties(checker, memberType);
399
+ if (Object.keys(nestedProperties).length > 0) {
400
+ properties[member.name] = {
401
+ type: 'object',
402
+ properties: nestedProperties
403
+ };
404
+ } else {
405
+ properties[member.name] = resolvedType;
406
+ delete properties[member.name].__unresolved;
407
+ }
408
+ } else if (resolvedType.type === 'array' && memberType.target) {
409
+ // Handle array types in interfaces
410
+ const arraySchema = extractArrayTypeSchema(checker, memberType, memberTypeString);
411
+ properties[member.name] = arraySchema;
412
+ } else {
413
+ properties[member.name] = resolvedType;
414
+ }
415
+ } catch (error) {
416
+ // Skip properties that cause errors
417
+ properties[member.name] = { type: 'any' };
418
+ }
419
+ }
420
+
421
+ return properties;
422
+ }
423
+
424
+ module.exports = {
425
+ extractProperties,
426
+ extractInterfaceProperties
427
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @fileoverview TypeScript analytics tracking analyzer - main entry point
3
+ * @module analyze/typescript
4
+ */
5
+
6
+ const { getProgram, findTrackingEvents, ProgramError, SourceFileError } = require('./parser');
7
+
8
+ /**
9
+ * Analyzes a TypeScript file for analytics tracking calls
10
+ * @param {string} filePath - Path to the TypeScript file to analyze
11
+ * @param {Object} [program] - Optional existing TypeScript program to reuse
12
+ * @param {string} [customFunction] - Optional custom function name to detect
13
+ * @returns {Array<Object>} Array of tracking events found in the file
14
+ */
15
+ function analyzeTsFile(filePath, program, customFunction) {
16
+ const events = [];
17
+
18
+ try {
19
+ // Get or create TypeScript program
20
+ const tsProgram = getProgram(filePath, program);
21
+
22
+ // Get source file from program
23
+ const sourceFile = tsProgram.getSourceFile(filePath);
24
+ if (!sourceFile) {
25
+ throw new SourceFileError(filePath);
26
+ }
27
+
28
+ // Get type checker
29
+ const checker = tsProgram.getTypeChecker();
30
+
31
+ // Find and extract tracking events
32
+ const foundEvents = findTrackingEvents(sourceFile, checker, filePath, customFunction);
33
+ events.push(...foundEvents);
34
+
35
+ } catch (error) {
36
+ if (error instanceof ProgramError) {
37
+ console.error(`Error creating TypeScript program for ${filePath}: ${error.originalError?.message || error.message}`);
38
+ } else if (error instanceof SourceFileError) {
39
+ console.error(`Error: Unable to get source file for ${filePath}`);
40
+ } else {
41
+ console.error(`Error analyzing TypeScript file ${filePath}: ${error.message}`);
42
+ }
43
+ }
44
+
45
+ return events;
46
+ }
47
+
48
+ module.exports = { analyzeTsFile };
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview TypeScript AST parsing and walking module
3
+ * @module analyze/typescript/parser
4
+ */
5
+
6
+ const ts = require('typescript');
7
+ const { detectAnalyticsSource } = require('./detectors');
8
+ const { extractEventData, processEventData } = require('./extractors');
9
+ const { findWrappingFunction } = require('./utils/function-finder');
10
+
11
+ /**
12
+ * Error thrown when TypeScript program cannot be created
13
+ */
14
+ class ProgramError extends Error {
15
+ constructor(filePath, originalError) {
16
+ super(`Failed to create TypeScript program for: ${filePath}`);
17
+ this.name = 'ProgramError';
18
+ this.filePath = filePath;
19
+ this.originalError = originalError;
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Error thrown when source file cannot be retrieved
25
+ */
26
+ class SourceFileError extends Error {
27
+ constructor(filePath) {
28
+ super(`Failed to get source file: ${filePath}`);
29
+ this.name = 'SourceFileError';
30
+ this.filePath = filePath;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Gets or creates a TypeScript program for analysis
36
+ * @param {string} filePath - Path to the TypeScript file
37
+ * @param {Object} [existingProgram] - Existing TypeScript program to reuse
38
+ * @returns {Object} TypeScript program
39
+ * @throws {ProgramError} If program cannot be created
40
+ */
41
+ function getProgram(filePath, existingProgram) {
42
+ if (existingProgram) {
43
+ return existingProgram;
44
+ }
45
+
46
+ try {
47
+ // Create a minimal program for single file analysis
48
+ const options = {
49
+ target: ts.ScriptTarget.Latest,
50
+ module: ts.ModuleKind.CommonJS,
51
+ allowJs: true,
52
+ checkJs: false,
53
+ noEmit: true
54
+ };
55
+
56
+ const program = ts.createProgram([filePath], options);
57
+ return program;
58
+ } catch (error) {
59
+ throw new ProgramError(filePath, error);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Walks the TypeScript AST and finds analytics tracking calls
65
+ * @param {Object} sourceFile - TypeScript source file
66
+ * @param {Object} checker - TypeScript type checker
67
+ * @param {string} filePath - Path to the file being analyzed
68
+ * @param {string} [customFunction] - Custom function name to detect
69
+ * @returns {Array<Object>} Array of found events
70
+ */
71
+ function findTrackingEvents(sourceFile, checker, filePath, customFunction) {
72
+ const events = [];
73
+
74
+ /**
75
+ * Visitor function for AST traversal
76
+ * @param {Object} node - Current AST node
77
+ */
78
+ function visit(node) {
79
+ try {
80
+ if (ts.isCallExpression(node)) {
81
+ const event = extractTrackingEvent(node, sourceFile, checker, filePath, customFunction);
82
+ if (event) {
83
+ events.push(event);
84
+ }
85
+ }
86
+ // Continue traversing the AST
87
+ ts.forEachChild(node, visit);
88
+ } catch (error) {
89
+ console.error(`Error processing node in ${filePath}:`, error.message);
90
+ }
91
+ }
92
+
93
+ // Start traversal from the root
94
+ ts.forEachChild(sourceFile, visit);
95
+
96
+ return events;
97
+ }
98
+
99
+ /**
100
+ * Extracts tracking event from a CallExpression node
101
+ * @param {Object} node - CallExpression node
102
+ * @param {Object} sourceFile - TypeScript source file
103
+ * @param {Object} checker - TypeScript type checker
104
+ * @param {string} filePath - File path
105
+ * @param {string} [customFunction] - Custom function name
106
+ * @returns {Object|null} Extracted event or null
107
+ */
108
+ function extractTrackingEvent(node, sourceFile, checker, filePath, customFunction) {
109
+ // Detect the analytics source
110
+ const source = detectAnalyticsSource(node, customFunction);
111
+ if (source === 'unknown') {
112
+ return null;
113
+ }
114
+
115
+ // Extract event data based on the source
116
+ const eventData = extractEventData(node, source, checker, sourceFile);
117
+
118
+ // Get location and context information
119
+ const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
120
+ const functionName = findWrappingFunction(node);
121
+
122
+ // Process the event data into final format
123
+ return processEventData(eventData, source, filePath, line, functionName, checker, sourceFile);
124
+ }
125
+
126
+ module.exports = {
127
+ getProgram,
128
+ findTrackingEvents,
129
+ ProgramError,
130
+ SourceFileError
131
+ };