@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,120 @@
1
+ /**
2
+ * @fileoverview Analytics source detection module
3
+ * @module analyze/typescript/detectors/analytics-source
4
+ */
5
+
6
+ const ts = require('typescript');
7
+ const { ANALYTICS_PROVIDERS } = require('../constants');
8
+
9
+ /**
10
+ * Detects the analytics provider from a CallExpression node
11
+ * @param {Object} node - TypeScript CallExpression node
12
+ * @param {string} [customFunction] - Custom function name to detect
13
+ * @returns {string} The detected analytics source or 'unknown'
14
+ */
15
+ function detectAnalyticsSource(node, customFunction) {
16
+ if (!node.expression) {
17
+ return 'unknown';
18
+ }
19
+
20
+ // Check for custom function first
21
+ if (customFunction && isCustomFunction(node, customFunction)) {
22
+ return 'custom';
23
+ }
24
+
25
+ // Check for function-based providers (e.g., gtag)
26
+ const functionSource = detectFunctionBasedProvider(node);
27
+ if (functionSource !== 'unknown') {
28
+ return functionSource;
29
+ }
30
+
31
+ // Check for member-based providers (e.g., analytics.track)
32
+ const memberSource = detectMemberBasedProvider(node);
33
+ if (memberSource !== 'unknown') {
34
+ return memberSource;
35
+ }
36
+
37
+ return 'unknown';
38
+ }
39
+
40
+ /**
41
+ * Checks if the node is a custom function call
42
+ * @param {Object} node - TypeScript CallExpression node
43
+ * @param {string} customFunction - Custom function name
44
+ * @returns {boolean}
45
+ */
46
+ function isCustomFunction(node, customFunction) {
47
+ return ts.isIdentifier(node.expression) &&
48
+ node.expression.escapedText === customFunction;
49
+ }
50
+
51
+ /**
52
+ * Detects function-based analytics providers
53
+ * @param {Object} node - TypeScript CallExpression node
54
+ * @returns {string} Provider name or 'unknown'
55
+ */
56
+ function detectFunctionBasedProvider(node) {
57
+ if (!ts.isIdentifier(node.expression)) {
58
+ return 'unknown';
59
+ }
60
+
61
+ const functionName = node.expression.escapedText;
62
+
63
+ for (const provider of Object.values(ANALYTICS_PROVIDERS)) {
64
+ if (provider.type === 'function' && provider.functionName === functionName) {
65
+ return provider.name;
66
+ }
67
+ }
68
+
69
+ return 'unknown';
70
+ }
71
+
72
+ /**
73
+ * Detects member expression-based analytics providers
74
+ * @param {Object} node - TypeScript CallExpression node
75
+ * @returns {string} Provider name or 'unknown'
76
+ */
77
+ function detectMemberBasedProvider(node) {
78
+ if (!ts.isPropertyAccessExpression(node.expression)) {
79
+ return 'unknown';
80
+ }
81
+
82
+ const objectName = node.expression.expression.escapedText;
83
+ const methodName = node.expression.name.escapedText;
84
+
85
+ if (!objectName || !methodName) {
86
+ return 'unknown';
87
+ }
88
+
89
+ for (const provider of Object.values(ANALYTICS_PROVIDERS)) {
90
+ if (provider.type === 'member' && matchesMemberProvider(provider, objectName, methodName)) {
91
+ return provider.name;
92
+ }
93
+ }
94
+
95
+ return 'unknown';
96
+ }
97
+
98
+ /**
99
+ * Checks if object and method names match a provider configuration
100
+ * @param {Object} provider - Provider configuration
101
+ * @param {string} objectName - Object name from TypeScript AST
102
+ * @param {string} methodName - Method name from TypeScript AST
103
+ * @returns {boolean}
104
+ */
105
+ function matchesMemberProvider(provider, objectName, methodName) {
106
+ if (provider.methodName !== methodName) {
107
+ return false;
108
+ }
109
+
110
+ // Handle providers with multiple possible object names (e.g., mParticle/mparticle)
111
+ if (provider.objectNames) {
112
+ return provider.objectNames.includes(objectName);
113
+ }
114
+
115
+ return provider.objectName === objectName;
116
+ }
117
+
118
+ module.exports = {
119
+ detectAnalyticsSource
120
+ };
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @fileoverview Central export for all detector modules
3
+ * @module analyze/typescript/detectors
4
+ */
5
+
6
+ const { detectAnalyticsSource } = require('./analytics-source');
7
+
8
+ module.exports = {
9
+ detectAnalyticsSource
10
+ };
@@ -0,0 +1,269 @@
1
+ /**
2
+ * @fileoverview Event extraction logic for different analytics providers
3
+ * @module analyze/typescript/extractors/event-extractor
4
+ */
5
+
6
+ const ts = require('typescript');
7
+ const { extractProperties } = require('./property-extractor');
8
+ const { resolveIdentifierToInitializer } = require('../utils/type-resolver');
9
+
10
+ /**
11
+ * Event data structure
12
+ * @typedef {Object} EventData
13
+ * @property {string|null} eventName - The event name
14
+ * @property {Object|null} propertiesNode - AST node containing event properties
15
+ */
16
+
17
+ /**
18
+ * Provider-specific extraction strategies
19
+ */
20
+ const EXTRACTION_STRATEGIES = {
21
+ googleanalytics: extractGoogleAnalyticsEvent,
22
+ snowplow: extractSnowplowEvent,
23
+ mparticle: extractMparticleEvent,
24
+ default: extractDefaultEvent
25
+ };
26
+
27
+ /**
28
+ * Extracts event information from a CallExpression node
29
+ * @param {Object} node - TypeScript CallExpression node
30
+ * @param {string} source - Analytics provider source
31
+ * @param {Object} checker - TypeScript type checker
32
+ * @param {Object} sourceFile - TypeScript source file
33
+ * @returns {EventData} Extracted event data
34
+ */
35
+ function extractEventData(node, source, checker, sourceFile) {
36
+ const strategy = EXTRACTION_STRATEGIES[source] || EXTRACTION_STRATEGIES.default;
37
+ return strategy(node, checker, sourceFile);
38
+ }
39
+
40
+ /**
41
+ * Extracts Google Analytics event data
42
+ * @param {Object} node - CallExpression node
43
+ * @param {Object} checker - TypeScript type checker
44
+ * @param {Object} sourceFile - TypeScript source file
45
+ * @returns {EventData}
46
+ */
47
+ function extractGoogleAnalyticsEvent(node, checker, sourceFile) {
48
+ if (!node.arguments || node.arguments.length < 3) {
49
+ return { eventName: null, propertiesNode: null };
50
+ }
51
+
52
+ // gtag('event', 'event_name', { properties })
53
+ const eventName = getStringValue(node.arguments[1]);
54
+ const propertiesNode = node.arguments[2];
55
+
56
+ return { eventName, propertiesNode };
57
+ }
58
+
59
+ /**
60
+ * Extracts Snowplow event data
61
+ * @param {Object} node - CallExpression node
62
+ * @param {Object} checker - TypeScript type checker
63
+ * @param {Object} sourceFile - TypeScript source file
64
+ * @returns {EventData}
65
+ */
66
+ function extractSnowplowEvent(node, checker, sourceFile) {
67
+ if (!node.arguments || node.arguments.length === 0) {
68
+ return { eventName: null, propertiesNode: null };
69
+ }
70
+
71
+ // tracker.track(buildStructEvent({ action: 'event_name', ... }))
72
+ const firstArg = node.arguments[0];
73
+
74
+ // Check if it's a direct buildStructEvent call
75
+ if (ts.isCallExpression(firstArg) &&
76
+ ts.isIdentifier(firstArg.expression) &&
77
+ firstArg.expression.escapedText === 'buildStructEvent' &&
78
+ firstArg.arguments.length > 0) {
79
+ const structEventArg = firstArg.arguments[0];
80
+ if (ts.isObjectLiteralExpression(structEventArg)) {
81
+ const actionProperty = findPropertyByKey(structEventArg, 'action');
82
+ const eventName = actionProperty ? getStringValue(actionProperty.initializer) : null;
83
+ return { eventName, propertiesNode: structEventArg };
84
+ }
85
+ }
86
+ // Check if it's a variable reference
87
+ else if (ts.isIdentifier(firstArg)) {
88
+ const resolvedNode = resolveIdentifierToInitializer(checker, firstArg, sourceFile);
89
+ if (resolvedNode && ts.isCallExpression(resolvedNode) &&
90
+ ts.isIdentifier(resolvedNode.expression) &&
91
+ resolvedNode.expression.escapedText === 'buildStructEvent' &&
92
+ resolvedNode.arguments.length > 0) {
93
+ const structEventArg = resolvedNode.arguments[0];
94
+ if (ts.isObjectLiteralExpression(structEventArg)) {
95
+ const actionProperty = findPropertyByKey(structEventArg, 'action');
96
+ const eventName = actionProperty ? getStringValue(actionProperty.initializer) : null;
97
+ return { eventName, propertiesNode: structEventArg };
98
+ }
99
+ }
100
+ }
101
+
102
+ return { eventName: null, propertiesNode: null };
103
+ }
104
+
105
+ /**
106
+ * Extracts mParticle event data
107
+ * @param {Object} node - CallExpression node
108
+ * @param {Object} checker - TypeScript type checker
109
+ * @param {Object} sourceFile - TypeScript source file
110
+ * @returns {EventData}
111
+ */
112
+ function extractMparticleEvent(node, checker, sourceFile) {
113
+ if (!node.arguments || node.arguments.length < 3) {
114
+ return { eventName: null, propertiesNode: null };
115
+ }
116
+
117
+ // mParticle.logEvent('event_name', mParticle.EventType.Navigation, { properties })
118
+ const eventName = getStringValue(node.arguments[0]);
119
+ const propertiesNode = node.arguments[2];
120
+
121
+ return { eventName, propertiesNode };
122
+ }
123
+
124
+ /**
125
+ * Default event extraction for standard providers
126
+ * @param {Object} node - CallExpression node
127
+ * @param {Object} checker - TypeScript type checker
128
+ * @param {Object} sourceFile - TypeScript source file
129
+ * @returns {EventData}
130
+ */
131
+ function extractDefaultEvent(node, checker, sourceFile) {
132
+ if (!node.arguments || node.arguments.length < 2) {
133
+ return { eventName: null, propertiesNode: null };
134
+ }
135
+
136
+ // provider.track('event_name', { properties })
137
+ const eventName = getStringValue(node.arguments[0]);
138
+ const propertiesNode = node.arguments[1];
139
+
140
+ return { eventName, propertiesNode };
141
+ }
142
+
143
+ /**
144
+ * Processes extracted event data into final event object
145
+ * @param {EventData} eventData - Raw event data
146
+ * @param {string} source - Analytics source
147
+ * @param {string} filePath - File path
148
+ * @param {number} line - Line number
149
+ * @param {string} functionName - Containing function name
150
+ * @param {Object} checker - TypeScript type checker
151
+ * @param {Object} sourceFile - TypeScript source file
152
+ * @returns {Object|null} Processed event object or null
153
+ */
154
+ function processEventData(eventData, source, filePath, line, functionName, checker, sourceFile) {
155
+ const { eventName, propertiesNode } = eventData;
156
+
157
+ if (!eventName || !propertiesNode) {
158
+ return null;
159
+ }
160
+
161
+ let properties = null;
162
+
163
+ // Check if properties is an object literal
164
+ if (ts.isObjectLiteralExpression(propertiesNode)) {
165
+ properties = extractProperties(checker, propertiesNode);
166
+ }
167
+ // Check if properties is an identifier (variable reference)
168
+ else if (ts.isIdentifier(propertiesNode)) {
169
+ const resolvedNode = resolveIdentifierToInitializer(checker, propertiesNode, sourceFile);
170
+ if (resolvedNode && ts.isObjectLiteralExpression(resolvedNode)) {
171
+ properties = extractProperties(checker, resolvedNode);
172
+ }
173
+ }
174
+
175
+ if (!properties) {
176
+ return null;
177
+ }
178
+
179
+ // Special handling for Snowplow: remove 'action' from properties
180
+ if (source === 'snowplow' && properties.action) {
181
+ delete properties.action;
182
+ }
183
+
184
+ // Clean up any unresolved type markers
185
+ const cleanedProperties = cleanupProperties(properties);
186
+
187
+ return {
188
+ eventName,
189
+ source,
190
+ properties: cleanedProperties,
191
+ filePath,
192
+ line,
193
+ functionName
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Gets string value from a TypeScript AST node
199
+ * @param {Object} node - TypeScript AST node
200
+ * @returns {string|null} String value or null
201
+ */
202
+ function getStringValue(node) {
203
+ if (!node) return null;
204
+ if (ts.isStringLiteral(node)) {
205
+ return node.text;
206
+ }
207
+ return null;
208
+ }
209
+
210
+ /**
211
+ * Finds a property by key in an ObjectLiteralExpression
212
+ * @param {Object} objectNode - ObjectLiteralExpression node
213
+ * @param {string} key - Property key to find
214
+ * @returns {Object|null} Property node or null
215
+ */
216
+ function findPropertyByKey(objectNode, key) {
217
+ if (!objectNode.properties) return null;
218
+
219
+ return objectNode.properties.find(prop => {
220
+ if (prop.name) {
221
+ if (ts.isIdentifier(prop.name)) {
222
+ return prop.name.escapedText === key;
223
+ }
224
+ if (ts.isStringLiteral(prop.name)) {
225
+ return prop.name.text === key;
226
+ }
227
+ }
228
+ return false;
229
+ });
230
+ }
231
+
232
+ /**
233
+ * Cleans up properties by removing unresolved type markers
234
+ * @param {Object} properties - Properties object
235
+ * @returns {Object} Cleaned properties
236
+ */
237
+ function cleanupProperties(properties) {
238
+ const cleaned = {};
239
+
240
+ for (const [key, value] of Object.entries(properties)) {
241
+ if (value && typeof value === 'object') {
242
+ // Remove __unresolved marker
243
+ if (value.__unresolved) {
244
+ delete value.__unresolved;
245
+ }
246
+
247
+ // Recursively clean nested properties
248
+ if (value.properties) {
249
+ value.properties = cleanupProperties(value.properties);
250
+ }
251
+
252
+ // Clean array item properties
253
+ if (value.type === 'array' && value.items && value.items.properties) {
254
+ value.items.properties = cleanupProperties(value.items.properties);
255
+ }
256
+
257
+ cleaned[key] = value;
258
+ } else {
259
+ cleaned[key] = value;
260
+ }
261
+ }
262
+
263
+ return cleaned;
264
+ }
265
+
266
+ module.exports = {
267
+ extractEventData,
268
+ processEventData
269
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @fileoverview Central export for all extractor modules
3
+ * @module analyze/typescript/extractors
4
+ */
5
+
6
+ const { extractEventData, processEventData } = require('./event-extractor');
7
+ const { extractProperties, extractInterfaceProperties } = require('./property-extractor');
8
+
9
+ module.exports = {
10
+ extractEventData,
11
+ processEventData,
12
+ extractProperties,
13
+ extractInterfaceProperties
14
+ };