@flisk/analyze-tracking 0.7.1 → 0.7.3

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 (71) hide show
  1. package/README.md +35 -61
  2. package/bin/cli.js +1 -1
  3. package/package.json +18 -3
  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 -7
  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 +120 -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 +395 -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 +114 -0
  48. package/src/analyze/typescript/utils/type-resolver.js +193 -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 +7 -2
  54. package/src/{fileProcessor.js → utils/fileProcessor.js} +5 -0
  55. package/src/{repoDetails.js → utils/repoDetails.js} +5 -0
  56. package/src/{yamlGenerator.js → utils/yamlGenerator.js} +5 -0
  57. package/.github/workflows/npm-publish.yml +0 -33
  58. package/.github/workflows/pr-check.yml +0 -17
  59. package/jest.config.js +0 -7
  60. package/src/analyze/analyzeGoFile.js +0 -1164
  61. package/src/analyze/analyzeJsFile.js +0 -72
  62. package/src/analyze/analyzePythonFile.js +0 -41
  63. package/src/analyze/analyzeRubyFile.js +0 -409
  64. package/src/analyze/analyzeTsFile.js +0 -69
  65. package/src/analyze/go2json.js +0 -1069
  66. package/src/analyze/helpers.js +0 -217
  67. package/src/analyze/pythonTrackingAnalyzer.py +0 -439
  68. package/src/generateDescriptions.js +0 -196
  69. package/tests/detectSource.test.js +0 -20
  70. package/tests/extractProperties.test.js +0 -109
  71. package/tests/findWrappingFunction.test.js +0 -30
@@ -0,0 +1,395 @@
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
+
120
+ const propType = checker.getTypeAtLocation(prop.name);
121
+ const typeString = checker.typeToString(propType);
122
+
123
+ // Handle array types
124
+ if (isArrayType(typeString)) {
125
+ return extractArrayTypeSchema(checker, propType, typeString);
126
+ }
127
+
128
+ // Handle other types
129
+ const resolvedType = resolveTypeToProperties(checker, typeString);
130
+
131
+ // If it's an unresolved custom type, try to extract interface properties
132
+ if (resolvedType.__unresolved) {
133
+ const interfaceProps = extractInterfaceProperties(checker, propType);
134
+ if (Object.keys(interfaceProps).length > 0) {
135
+ return {
136
+ type: 'object',
137
+ properties: interfaceProps
138
+ };
139
+ }
140
+ }
141
+
142
+ return resolvedType;
143
+ }
144
+
145
+ /**
146
+ * Extracts schema from a value node
147
+ * @param {Object} checker - TypeScript type checker
148
+ * @param {Object} valueNode - Value node to extract schema from
149
+ * @returns {PropertySchema}
150
+ */
151
+ function extractValueSchema(checker, valueNode) {
152
+ // Object literal
153
+ if (ts.isObjectLiteralExpression(valueNode)) {
154
+ return {
155
+ type: 'object',
156
+ properties: extractProperties(checker, valueNode)
157
+ };
158
+ }
159
+
160
+ // Array literal
161
+ if (ts.isArrayLiteralExpression(valueNode)) {
162
+ return extractArrayLiteralSchema(checker, valueNode);
163
+ }
164
+
165
+ // Identifier (variable reference)
166
+ if (ts.isIdentifier(valueNode)) {
167
+ return extractIdentifierSchema(checker, valueNode);
168
+ }
169
+
170
+ // Literal values
171
+ const literalType = getLiteralType(valueNode);
172
+ if (literalType) {
173
+ return { type: literalType };
174
+ }
175
+
176
+ // For other expressions, get the type from TypeChecker
177
+ const typeString = getTypeOfNode(checker, valueNode);
178
+ return resolveTypeSchema(checker, typeString);
179
+ }
180
+
181
+ /**
182
+ * Extracts schema for array literals
183
+ * @param {Object} checker - TypeScript type checker
184
+ * @param {Object} node - ArrayLiteralExpression node
185
+ * @returns {PropertySchema}
186
+ */
187
+ function extractArrayLiteralSchema(checker, node) {
188
+ if (node.elements.length === 0) {
189
+ return {
190
+ type: 'array',
191
+ items: { type: 'any' }
192
+ };
193
+ }
194
+
195
+ // Check types of all elements
196
+ const elementTypes = new Set();
197
+ for (const element of node.elements) {
198
+ const elemType = getBasicTypeOfArrayElement(checker, element);
199
+ elementTypes.add(elemType);
200
+ }
201
+
202
+ // If all elements are the same type, use that type
203
+ const itemType = elementTypes.size === 1 ? Array.from(elementTypes)[0] : 'any';
204
+
205
+ return {
206
+ type: 'array',
207
+ items: { type: itemType }
208
+ };
209
+ }
210
+
211
+ /**
212
+ * Extracts schema for identifier references
213
+ * @param {Object} checker - TypeScript type checker
214
+ * @param {Object} identifier - Identifier node
215
+ * @returns {PropertySchema}
216
+ */
217
+ function extractIdentifierSchema(checker, identifier) {
218
+ const identifierType = checker.getTypeAtLocation(identifier);
219
+ const typeString = checker.typeToString(identifierType);
220
+
221
+ // Handle array types
222
+ if (isArrayType(typeString)) {
223
+ return extractArrayTypeSchema(checker, identifierType, typeString);
224
+ }
225
+
226
+ // Handle other types
227
+ const resolvedType = resolveTypeToProperties(checker, typeString);
228
+
229
+ // If it's an unresolved custom type, try to extract interface properties
230
+ if (resolvedType.__unresolved) {
231
+ const interfaceProps = extractInterfaceProperties(checker, identifierType);
232
+ if (Object.keys(interfaceProps).length > 0) {
233
+ return {
234
+ type: 'object',
235
+ properties: interfaceProps
236
+ };
237
+ }
238
+ }
239
+
240
+ return resolvedType;
241
+ }
242
+
243
+ /**
244
+ * Extracts schema for array types
245
+ * @param {Object} checker - TypeScript type checker
246
+ * @param {Object} type - TypeScript Type object
247
+ * @param {string} typeString - String representation of the type
248
+ * @returns {PropertySchema}
249
+ */
250
+ function extractArrayTypeSchema(checker, type, typeString) {
251
+ let elementType = null;
252
+
253
+ // Try to get type arguments for generic types
254
+ if (type.target && type.typeArguments && type.typeArguments.length > 0) {
255
+ elementType = type.typeArguments[0];
256
+ }
257
+ // Try indexed access for array types
258
+ else {
259
+ try {
260
+ const numberType = checker.getNumberType();
261
+ elementType = checker.getIndexedAccessType(type, numberType);
262
+ } catch (e) {
263
+ // Indexed access failed
264
+ }
265
+ }
266
+
267
+ if (elementType) {
268
+ const elementInterfaceProps = extractInterfaceProperties(checker, elementType);
269
+ if (Object.keys(elementInterfaceProps).length > 0) {
270
+ return {
271
+ type: 'array',
272
+ items: {
273
+ type: 'object',
274
+ properties: elementInterfaceProps
275
+ }
276
+ };
277
+ } else {
278
+ const elementTypeString = checker.typeToString(elementType);
279
+ if (isCustomType(elementTypeString)) {
280
+ return {
281
+ type: 'array',
282
+ items: { type: 'object' }
283
+ };
284
+ }
285
+ return {
286
+ type: 'array',
287
+ items: resolveTypeToProperties(checker, elementTypeString)
288
+ };
289
+ }
290
+ }
291
+
292
+ return {
293
+ type: 'array',
294
+ items: { type: 'any' }
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Resolves a type string to a schema
300
+ * @param {Object} checker - TypeScript type checker
301
+ * @param {string} typeString - Type string
302
+ * @returns {PropertySchema}
303
+ */
304
+ function resolveTypeSchema(checker, typeString) {
305
+ const resolvedType = resolveTypeToProperties(checker, typeString);
306
+
307
+ // Clean up any unresolved markers for simple types
308
+ if (resolvedType.__unresolved) {
309
+ delete resolvedType.__unresolved;
310
+ }
311
+
312
+ return resolvedType;
313
+ }
314
+
315
+ /**
316
+ * Gets the literal type of a node
317
+ * @param {Object} node - AST node
318
+ * @returns {string|null} Literal type or null
319
+ */
320
+ function getLiteralType(node) {
321
+ if (ts.isStringLiteral(node)) return 'string';
322
+ if (ts.isNumericLiteral(node)) return 'number';
323
+ if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return 'boolean';
324
+ if (node.kind === ts.SyntaxKind.NullKeyword) return 'null';
325
+ if (node.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
326
+ return null;
327
+ }
328
+
329
+ /**
330
+ * Checks if a type string represents an array type
331
+ * @param {string} typeString - Type string to check
332
+ * @returns {boolean}
333
+ */
334
+ function isArrayType(typeString) {
335
+ return typeString.includes('[]') ||
336
+ typeString.startsWith('Array<') ||
337
+ typeString.startsWith('ReadonlyArray<') ||
338
+ typeString.startsWith('readonly ');
339
+ }
340
+
341
+ /**
342
+ * Extracts properties from a TypeScript interface or type
343
+ * @param {Object} checker - TypeScript type checker
344
+ * @param {Object} type - TypeScript Type object
345
+ * @returns {Object.<string, PropertySchema>}
346
+ */
347
+ function extractInterfaceProperties(checker, type) {
348
+ const properties = {};
349
+ const typeSymbol = type.getSymbol();
350
+
351
+ if (!typeSymbol) return properties;
352
+
353
+ // Get all properties of the type
354
+ const members = checker.getPropertiesOfType(type);
355
+
356
+ for (const member of members) {
357
+ try {
358
+ const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
359
+ const memberTypeString = checker.typeToString(memberType);
360
+
361
+ // Recursively resolve the member type
362
+ const resolvedType = resolveTypeToProperties(checker, memberTypeString);
363
+
364
+ // If it's an unresolved object type, try to extract its properties
365
+ if (resolvedType.__unresolved) {
366
+ const nestedProperties = extractInterfaceProperties(checker, memberType);
367
+ if (Object.keys(nestedProperties).length > 0) {
368
+ properties[member.name] = {
369
+ type: 'object',
370
+ properties: nestedProperties
371
+ };
372
+ } else {
373
+ properties[member.name] = resolvedType;
374
+ delete properties[member.name].__unresolved;
375
+ }
376
+ } else if (resolvedType.type === 'array' && memberType.target) {
377
+ // Handle array types in interfaces
378
+ const arraySchema = extractArrayTypeSchema(checker, memberType, memberTypeString);
379
+ properties[member.name] = arraySchema;
380
+ } else {
381
+ properties[member.name] = resolvedType;
382
+ }
383
+ } catch (error) {
384
+ // Skip properties that cause errors
385
+ properties[member.name] = { type: 'any' };
386
+ }
387
+ }
388
+
389
+ return properties;
390
+ }
391
+
392
+ module.exports = {
393
+ extractProperties,
394
+ extractInterfaceProperties
395
+ };
@@ -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
+ };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * @fileoverview Utilities for finding function context in TypeScript AST
3
+ * @module analyze/typescript/utils/function-finder
4
+ */
5
+
6
+ const ts = require('typescript');
7
+
8
+ /**
9
+ * Finds the name of the function that wraps a given node
10
+ * @param {Object} node - The TypeScript AST node to find the wrapper for
11
+ * @returns {string} The function name or 'global' if not in a function
12
+ */
13
+ function findWrappingFunction(node) {
14
+ let current = node;
15
+
16
+ while (current) {
17
+ const functionName = extractFunctionName(current);
18
+
19
+ if (functionName) {
20
+ return functionName;
21
+ }
22
+
23
+ current = current.parent;
24
+ }
25
+
26
+ return 'global';
27
+ }
28
+
29
+ /**
30
+ * Extracts function name from different TypeScript AST node types
31
+ * @param {Object} node - Current TypeScript node
32
+ * @returns {string|null} Function name or null if not a function context
33
+ */
34
+ function extractFunctionName(node) {
35
+ // Function declaration
36
+ if (ts.isFunctionDeclaration(node)) {
37
+ return node.name ? node.name.escapedText : 'anonymous';
38
+ }
39
+
40
+ // Method declaration in class
41
+ if (ts.isMethodDeclaration(node)) {
42
+ return node.name ? node.name.escapedText : 'anonymous';
43
+ }
44
+
45
+ // Arrow function or function expression
46
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
47
+ return findParentFunctionName(node) || 'anonymous';
48
+ }
49
+
50
+ // Constructor
51
+ if (ts.isConstructorDeclaration(node)) {
52
+ return 'constructor';
53
+ }
54
+
55
+ // Getter/Setter
56
+ if (ts.isGetAccessorDeclaration(node) || ts.isSetAccessorDeclaration(node)) {
57
+ return node.name ? `${ts.isGetAccessorDeclaration(node) ? 'get' : 'set'} ${node.name.escapedText}` : 'anonymous';
58
+ }
59
+
60
+ return null;
61
+ }
62
+
63
+ /**
64
+ * Finds the parent function name for arrow functions or function expressions
65
+ * @param {Object} node - Arrow function or function expression node
66
+ * @returns {string|null} Parent function name or null
67
+ */
68
+ function findParentFunctionName(node) {
69
+ const parent = node.parent;
70
+
71
+ if (!parent) return null;
72
+
73
+ // Variable declaration: const myFunc = () => {}
74
+ if (ts.isVariableDeclaration(parent) && parent.name) {
75
+ return parent.name.escapedText;
76
+ }
77
+
78
+ // Property assignment: { myFunc: () => {} }
79
+ if (ts.isPropertyAssignment(parent) && parent.name) {
80
+ if (ts.isIdentifier(parent.name)) {
81
+ return parent.name.escapedText;
82
+ }
83
+ if (ts.isStringLiteral(parent.name)) {
84
+ return parent.name.text;
85
+ }
86
+ }
87
+
88
+ // Method property in object literal: { myFunc() {} }
89
+ if (ts.isMethodDeclaration(parent) && parent.name) {
90
+ return parent.name.escapedText;
91
+ }
92
+
93
+ // Binary expression assignment: obj.myFunc = () => {}
94
+ if (ts.isBinaryExpression(parent) &&
95
+ parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
96
+ if (ts.isPropertyAccessExpression(parent.left)) {
97
+ return parent.left.name.escapedText;
98
+ }
99
+ }
100
+
101
+ // Call expression argument: someFunc(() => {})
102
+ if (ts.isCallExpression(parent)) {
103
+ const argIndex = parent.arguments.indexOf(node);
104
+ if (argIndex >= 0) {
105
+ return `anonymous-callback-${argIndex}`;
106
+ }
107
+ }
108
+
109
+ return null;
110
+ }
111
+
112
+ module.exports = {
113
+ findWrappingFunction
114
+ };