@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,46 @@
1
+ /**
2
+ * @fileoverview Analytics source detection for Ruby tracking calls
3
+ * @module analyze/ruby/detectors
4
+ */
5
+
6
+ /**
7
+ * Detects the analytics source from a Ruby AST CallNode
8
+ * @param {Object} node - The AST CallNode to analyze
9
+ * @param {string} customFunction - Optional custom function name to detect
10
+ * @returns {string|null} - The detected source or null
11
+ */
12
+ function detectSource(node, customFunction = null) {
13
+ if (!node) return null;
14
+
15
+ // Check for analytics libraries
16
+ if (node.receiver) {
17
+ const objectName = node.receiver.name;
18
+ const methodName = node.name;
19
+
20
+ // Segment and Rudderstack (both use similar format)
21
+ // Analytics.track (Segment) or analytics.track (Rudderstack)
22
+ if ((objectName === 'Analytics' || objectName === 'analytics') && methodName === 'track') {
23
+ // Try to determine if it's Rudderstack based on context
24
+ // For now, we'll treat lowercase 'analytics' as Rudderstack
25
+ return objectName === 'analytics' ? 'rudderstack' : 'segment';
26
+ }
27
+
28
+ // Mixpanel (Ruby SDK uses Mixpanel::Tracker instance)
29
+ if (methodName === 'track' && objectName === 'tracker') return 'mixpanel';
30
+
31
+ // PostHog
32
+ if (objectName === 'posthog' && methodName === 'capture') return 'posthog';
33
+ }
34
+
35
+ // Snowplow (typically tracker.track_struct_event)
36
+ if (node.name === 'track_struct_event') return 'snowplow';
37
+
38
+ // Custom tracking function
39
+ if (customFunction && node.name === customFunction) return 'custom';
40
+
41
+ return null;
42
+ }
43
+
44
+ module.exports = {
45
+ detectSource
46
+ };
@@ -0,0 +1,258 @@
1
+ /**
2
+ * @fileoverview Event and property extraction utilities for Ruby analytics
3
+ * @module analyze/ruby/extractors
4
+ */
5
+
6
+ const { getValueType } = require('./types');
7
+
8
+ /**
9
+ * Extracts the event name from a tracking call based on the source
10
+ * @param {Object} node - The AST CallNode
11
+ * @param {string} source - The detected analytics source
12
+ * @returns {string|null} - The extracted event name or null
13
+ */
14
+ function extractEventName(node, source) {
15
+ if (source === 'segment' || source === 'rudderstack') {
16
+ // Both Segment and Rudderstack use the same format
17
+ const params = node.arguments_.arguments_[0].elements;
18
+ const eventProperty = params.find(param => param?.key?.unescaped?.value === 'event');
19
+ return eventProperty?.value?.unescaped?.value || null;
20
+ }
21
+
22
+ if (source === 'mixpanel') {
23
+ // Mixpanel Ruby SDK format: tracker.track('distinct_id', 'event_name', {...})
24
+ const args = node.arguments_.arguments_;
25
+ if (args && args.length > 1 && args[1]?.unescaped?.value) {
26
+ return args[1].unescaped.value;
27
+ }
28
+ }
29
+
30
+ if (source === 'posthog') {
31
+ // PostHog Ruby SDK format: posthog.capture({distinct_id: '...', event: '...', properties: {...}})
32
+ const hashArg = node.arguments_.arguments_[0];
33
+ if (hashArg && hashArg.elements) {
34
+ const eventProperty = hashArg.elements.find(elem => elem?.key?.unescaped?.value === 'event');
35
+ return eventProperty?.value?.unescaped?.value || null;
36
+ }
37
+ }
38
+
39
+ if (source === 'snowplow') {
40
+ // Snowplow Ruby SDK: tracker.track_struct_event(category: '...', action: '...', ...)
41
+ const params = node.arguments_.arguments_[0].elements;
42
+ const actionProperty = params.find(param => param?.key?.unescaped?.value === 'action');
43
+ return actionProperty?.value?.unescaped?.value || null;
44
+ }
45
+
46
+ if (source === 'custom') {
47
+ // Custom function format: customFunction('event_name', {...})
48
+ const args = node.arguments_.arguments_;
49
+ if (args && args.length > 0 && args[0]?.unescaped?.value) {
50
+ return args[0].unescaped.value;
51
+ }
52
+ }
53
+
54
+ return null;
55
+ }
56
+
57
+ /**
58
+ * Extracts properties from a tracking call based on the source
59
+ * @param {Object} node - The AST CallNode
60
+ * @param {string} source - The detected analytics source
61
+ * @returns {Object|null} - The extracted properties or null
62
+ */
63
+ async function extractProperties(node, source) {
64
+ const { HashNode, ArrayNode } = await import('@ruby/prism');
65
+
66
+ if (source === 'segment' || source === 'rudderstack') {
67
+ // Both Segment and Rudderstack use the same format
68
+ const params = node.arguments_.arguments_[0].elements;
69
+ const properties = {};
70
+
71
+ // Process all top-level fields except 'event'
72
+ for (const param of params) {
73
+ const key = param?.key?.unescaped?.value;
74
+
75
+ if (key && key !== 'event') {
76
+ const value = param?.value;
77
+
78
+ if (key === 'properties' && value instanceof HashNode) {
79
+ // Merge properties from the 'properties' hash into the top level
80
+ const nestedProperties = await extractHashProperties(value);
81
+ Object.assign(properties, nestedProperties);
82
+ } else if (value instanceof HashNode) {
83
+ // Handle other nested hash objects
84
+ const hashProperties = await extractHashProperties(value);
85
+ properties[key] = {
86
+ type: 'object',
87
+ properties: hashProperties
88
+ };
89
+ } else if (value instanceof ArrayNode) {
90
+ // Handle arrays
91
+ const arrayItems = await extractArrayItemProperties(value);
92
+ properties[key] = {
93
+ type: 'array',
94
+ items: arrayItems
95
+ };
96
+ } else {
97
+ // Handle primitive values
98
+ const valueType = await getValueType(value);
99
+ properties[key] = {
100
+ type: valueType
101
+ };
102
+ }
103
+ }
104
+ }
105
+
106
+ return properties;
107
+ }
108
+
109
+ if (source === 'mixpanel') {
110
+ // Mixpanel Ruby SDK: tracker.track('distinct_id', 'event_name', {properties})
111
+ const args = node.arguments_.arguments_;
112
+ const properties = {};
113
+
114
+ // Add distinct_id as property (even if it's a variable)
115
+ if (args && args.length > 0) {
116
+ properties.distinct_id = {
117
+ type: await getValueType(args[0])
118
+ };
119
+ }
120
+
121
+ // Extract properties from third argument if it exists
122
+ if (args && args.length > 2 && args[2] instanceof HashNode) {
123
+ const propsHash = await extractHashProperties(args[2]);
124
+ Object.assign(properties, propsHash);
125
+ }
126
+
127
+ return properties;
128
+ }
129
+
130
+ if (source === 'posthog') {
131
+ // PostHog Ruby SDK: posthog.capture({distinct_id: '...', event: '...', properties: {...}})
132
+ const hashArg = node.arguments_.arguments_[0];
133
+ const properties = {};
134
+
135
+ if (hashArg && hashArg.elements) {
136
+ // Extract distinct_id if present
137
+ const distinctIdProperty = hashArg.elements.find(elem => elem?.key?.unescaped?.value === 'distinct_id');
138
+ if (distinctIdProperty?.value) {
139
+ properties.distinct_id = {
140
+ type: await getValueType(distinctIdProperty.value)
141
+ };
142
+ }
143
+
144
+ // Extract properties
145
+ const propsProperty = hashArg.elements.find(elem => elem?.key?.unescaped?.value === 'properties');
146
+ if (propsProperty?.value instanceof HashNode) {
147
+ const props = await extractHashProperties(propsProperty.value);
148
+ Object.assign(properties, props);
149
+ }
150
+ }
151
+
152
+ return properties;
153
+ }
154
+
155
+ if (source === 'snowplow') {
156
+ // Snowplow Ruby SDK: tracker.track_struct_event(category: '...', action: '...', ...)
157
+ const params = node.arguments_.arguments_[0].elements;
158
+ const properties = {};
159
+
160
+ // Extract all struct event parameters except 'action' (which is used as the event name)
161
+ for (const param of params) {
162
+ const key = param?.key?.unescaped?.value;
163
+ if (key && key !== 'action') {
164
+ properties[key] = {
165
+ type: await getValueType(param.value)
166
+ };
167
+ }
168
+ }
169
+
170
+ return properties;
171
+ }
172
+
173
+ if (source === 'custom') {
174
+ // Custom function format: customFunction('event_name', {properties})
175
+ const args = node.arguments_.arguments_;
176
+ if (args && args.length > 1 && args[1] instanceof HashNode) {
177
+ return await extractHashProperties(args[1]);
178
+ }
179
+ }
180
+
181
+ return null;
182
+ }
183
+
184
+ /**
185
+ * Extracts properties from a HashNode
186
+ * @param {Object} hashNode - The HashNode to extract properties from
187
+ * @returns {Object} - The extracted properties
188
+ */
189
+ async function extractHashProperties(hashNode) {
190
+ const { AssocNode, HashNode, ArrayNode } = await import('@ruby/prism');
191
+ const properties = {};
192
+
193
+ for (const element of hashNode.elements) {
194
+ if (element instanceof AssocNode) {
195
+ const key = element.key.unescaped?.value;
196
+ const value = element.value;
197
+
198
+ if (key) {
199
+ if (value instanceof HashNode) {
200
+ // Handle nested hash objects
201
+ const nestedProperties = await extractHashProperties(value);
202
+ properties[key] = {
203
+ type: 'object',
204
+ properties: nestedProperties
205
+ };
206
+ } else if (value instanceof ArrayNode) {
207
+ // Handle arrays
208
+ const items = await extractArrayItemProperties(value);
209
+ properties[key] = {
210
+ type: 'array',
211
+ items
212
+ };
213
+ } else {
214
+ // Handle primitive values
215
+ const valueType = await getValueType(value);
216
+ properties[key] = {
217
+ type: valueType
218
+ };
219
+ }
220
+ }
221
+ }
222
+ }
223
+
224
+ return properties;
225
+ }
226
+
227
+ /**
228
+ * Extracts property information from array items
229
+ * @param {Object} arrayNode - The ArrayNode to analyze
230
+ * @returns {Object} - Type information for array items
231
+ */
232
+ async function extractArrayItemProperties(arrayNode) {
233
+ const { HashNode } = await import('@ruby/prism');
234
+
235
+ if (arrayNode.elements.length === 0) {
236
+ return { type: 'any' };
237
+ }
238
+
239
+ const firstItem = arrayNode.elements[0];
240
+ if (firstItem instanceof HashNode) {
241
+ return {
242
+ type: 'object',
243
+ properties: await extractHashProperties(firstItem)
244
+ };
245
+ } else {
246
+ const valueType = await getValueType(firstItem);
247
+ return {
248
+ type: valueType
249
+ };
250
+ }
251
+ }
252
+
253
+ module.exports = {
254
+ extractEventName,
255
+ extractProperties,
256
+ extractHashProperties,
257
+ extractArrayItemProperties
258
+ };
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @fileoverview Ruby analytics tracking analyzer - main entry point
3
+ * @module analyze/ruby
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const TrackingVisitor = require('./visitor');
8
+
9
+ // Lazy-loaded parse function from Ruby Prism
10
+ let parse = null;
11
+
12
+ /**
13
+ * Analyzes a Ruby file for analytics tracking calls
14
+ * @param {string} filePath - Path to the Ruby file to analyze
15
+ * @param {string} customFunction - Optional custom tracking function name
16
+ * @returns {Promise<Array>} Array of tracking events found in the file
17
+ * @throws {Error} If the file cannot be read or parsed
18
+ */
19
+ async function analyzeRubyFile(filePath, customFunction) {
20
+ // Lazy load the Ruby Prism parser
21
+ if (!parse) {
22
+ const { loadPrism } = await import('@ruby/prism');
23
+ parse = await loadPrism();
24
+ }
25
+
26
+ try {
27
+ // Read the file content
28
+ const code = fs.readFileSync(filePath, 'utf8');
29
+
30
+ // Parse the Ruby code into an AST
31
+ let ast;
32
+ try {
33
+ ast = await parse(code);
34
+ } catch (parseError) {
35
+ console.error(`Error parsing file ${filePath}:`, parseError.message);
36
+ return []; // Return empty events array if parsing fails
37
+ }
38
+
39
+ // Create a visitor and analyze the AST
40
+ const visitor = new TrackingVisitor(code, filePath, customFunction);
41
+ const events = await visitor.analyze(ast);
42
+
43
+ return events;
44
+
45
+ } catch (fileError) {
46
+ console.error(`Error reading or processing file ${filePath}:`, fileError.message);
47
+ return [];
48
+ }
49
+ }
50
+
51
+ module.exports = { analyzeRubyFile };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview AST traversal utilities for Ruby code analysis
3
+ * @module analyze/ruby/traversal
4
+ */
5
+
6
+ /**
7
+ * Finds the wrapping function for a given node
8
+ * @param {Object} node - The current AST node
9
+ * @param {Array} ancestors - The ancestor nodes stack
10
+ * @returns {string} - The function name or 'global'/'block'
11
+ */
12
+ async function findWrappingFunction(node, ancestors) {
13
+ const { DefNode, BlockNode, LambdaNode } = await import('@ruby/prism');
14
+
15
+ for (let i = ancestors.length - 1; i >= 0; i--) {
16
+ const current = ancestors[i];
17
+
18
+ // Handle method definitions
19
+ if (current instanceof DefNode) {
20
+ return current.name;
21
+ }
22
+
23
+ // Handle blocks and lambdas
24
+ if (current instanceof BlockNode || current instanceof LambdaNode) {
25
+ return 'block';
26
+ }
27
+ }
28
+ return 'global';
29
+ }
30
+
31
+ /**
32
+ * Recursively traverses the AST tree
33
+ * @param {Object} node - The current AST node
34
+ * @param {Function} nodeVisitor - Function to call for each node
35
+ * @param {Array} ancestors - The ancestor nodes stack
36
+ */
37
+ async function traverseNode(node, nodeVisitor, ancestors = []) {
38
+ const {
39
+ ProgramNode,
40
+ StatementsNode,
41
+ DefNode,
42
+ IfNode,
43
+ BlockNode,
44
+ ArgumentsNode,
45
+ HashNode,
46
+ AssocNode,
47
+ ClassNode,
48
+ ModuleNode,
49
+ CallNode
50
+ } = await import('@ruby/prism');
51
+
52
+ if (!node) return;
53
+
54
+ ancestors.push(node);
55
+
56
+ // Call the visitor for this node
57
+ if (node instanceof CallNode) {
58
+ await nodeVisitor(node, ancestors);
59
+ }
60
+
61
+ // Visit all child nodes based on node type
62
+ if (node instanceof ProgramNode) {
63
+ await traverseNode(node.statements, nodeVisitor, ancestors);
64
+ } else if (node instanceof StatementsNode) {
65
+ for (const child of node.body) {
66
+ await traverseNode(child, nodeVisitor, ancestors);
67
+ }
68
+ } else if (node instanceof ClassNode) {
69
+ if (node.body) {
70
+ await traverseNode(node.body, nodeVisitor, ancestors);
71
+ }
72
+ } else if (node instanceof ModuleNode) {
73
+ if (node.body) {
74
+ await traverseNode(node.body, nodeVisitor, ancestors);
75
+ }
76
+ } else if (node instanceof DefNode) {
77
+ if (node.body) {
78
+ await traverseNode(node.body, nodeVisitor, ancestors);
79
+ }
80
+ } else if (node instanceof IfNode) {
81
+ if (node.statements) {
82
+ await traverseNode(node.statements, nodeVisitor, ancestors);
83
+ }
84
+ if (node.subsequent) {
85
+ await traverseNode(node.subsequent, nodeVisitor, ancestors);
86
+ }
87
+ } else if (node instanceof BlockNode) {
88
+ if (node.body) {
89
+ await traverseNode(node.body, nodeVisitor, ancestors);
90
+ }
91
+ } else if (node instanceof ArgumentsNode) {
92
+ for (const arg of node.arguments) {
93
+ await traverseNode(arg, nodeVisitor, ancestors);
94
+ }
95
+ } else if (node instanceof HashNode) {
96
+ for (const element of node.elements) {
97
+ await traverseNode(element, nodeVisitor, ancestors);
98
+ }
99
+ } else if (node instanceof AssocNode) {
100
+ await traverseNode(node.key, nodeVisitor, ancestors);
101
+ await traverseNode(node.value, nodeVisitor, ancestors);
102
+ }
103
+
104
+ ancestors.pop();
105
+ }
106
+
107
+ /**
108
+ * Gets the line number for a given location in the code
109
+ * @param {string} code - The full source code
110
+ * @param {Object} location - The location object with startOffset
111
+ * @returns {number} - The line number (1-indexed)
112
+ */
113
+ function getLineNumber(code, location) {
114
+ // Count the number of newlines before the start offset
115
+ const beforeStart = code.slice(0, location.startOffset);
116
+ return beforeStart.split('\n').length;
117
+ }
118
+
119
+ module.exports = {
120
+ findWrappingFunction,
121
+ traverseNode,
122
+ getLineNumber
123
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @fileoverview Type definitions and constants for Ruby analytics analysis
3
+ * @module analyze/ruby/types
4
+ */
5
+
6
+ async function getValueType(node) {
7
+ const {
8
+ StringNode,
9
+ IntegerNode,
10
+ FloatNode,
11
+ TrueNode,
12
+ FalseNode,
13
+ NilNode,
14
+ SymbolNode,
15
+ CallNode
16
+ } = await import('@ruby/prism');
17
+
18
+ if (node instanceof StringNode) return 'string';
19
+ if (node instanceof IntegerNode || node instanceof FloatNode) return 'number';
20
+ if (node instanceof TrueNode || node instanceof FalseNode) return 'boolean';
21
+ if (node instanceof NilNode) return 'null';
22
+ if (node instanceof SymbolNode) return 'string';
23
+ if (node instanceof CallNode) return 'any'; // Dynamic values
24
+
25
+ return 'any'; // Default type
26
+ }
27
+
28
+ module.exports = {
29
+ getValueType
30
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @fileoverview AST visitor for analyzing Ruby tracking events
3
+ * @module analyze/ruby/visitor
4
+ */
5
+
6
+ const { detectSource } = require('./detectors');
7
+ const { extractEventName, extractProperties } = require('./extractors');
8
+ const { findWrappingFunction, traverseNode, getLineNumber } = require('./traversal');
9
+
10
+ class TrackingVisitor {
11
+ constructor(code, filePath, customFunction = null) {
12
+ this.code = code;
13
+ this.filePath = filePath;
14
+ this.customFunction = customFunction;
15
+ this.events = [];
16
+ }
17
+
18
+ /**
19
+ * Processes a call node to extract tracking event information
20
+ * @param {Object} node - The CallNode to process
21
+ * @param {Array} ancestors - The ancestor nodes stack
22
+ */
23
+ async processCallNode(node, ancestors) {
24
+ try {
25
+ const source = detectSource(node, this.customFunction);
26
+ if (!source) return;
27
+
28
+ const eventName = extractEventName(node, source);
29
+ if (!eventName) return;
30
+
31
+ const line = getLineNumber(this.code, node.location);
32
+ const functionName = await findWrappingFunction(node, ancestors);
33
+ const properties = await extractProperties(node, source);
34
+
35
+ this.events.push({
36
+ eventName,
37
+ source,
38
+ properties,
39
+ filePath: this.filePath,
40
+ line,
41
+ functionName
42
+ });
43
+ } catch (nodeError) {
44
+ console.error(`Error processing node in ${this.filePath}:`, nodeError.message);
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Analyzes the AST to find tracking events
50
+ * @param {Object} ast - The parsed AST
51
+ * @returns {Array} - Array of tracking events found
52
+ */
53
+ async analyze(ast) {
54
+ // Create a visitor function that will be called for each CallNode
55
+ const nodeVisitor = async (node, ancestors) => {
56
+ await this.processCallNode(node, ancestors);
57
+ };
58
+
59
+ // Traverse the AST starting from the program node
60
+ await traverseNode(ast.value, nodeVisitor);
61
+
62
+ return this.events;
63
+ }
64
+ }
65
+
66
+ module.exports = TrackingVisitor;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @fileoverview Constants and configurations for analytics tracking providers
3
+ * @module analyze/typescript/constants
4
+ */
5
+
6
+ /**
7
+ * Analytics provider configurations
8
+ * @typedef {Object} ProviderConfig
9
+ * @property {string} name - Provider display name
10
+ * @property {string} objectName - Object name in JavaScript
11
+ * @property {string} methodName - Method name for tracking
12
+ * @property {string} type - Type of detection (member|function)
13
+ */
14
+
15
+ /**
16
+ * Supported analytics providers and their detection patterns
17
+ * @type {Object.<string, ProviderConfig>}
18
+ */
19
+ const ANALYTICS_PROVIDERS = {
20
+ SEGMENT: {
21
+ name: 'segment',
22
+ objectName: 'analytics',
23
+ methodName: 'track',
24
+ type: 'member'
25
+ },
26
+ MIXPANEL: {
27
+ name: 'mixpanel',
28
+ objectName: 'mixpanel',
29
+ methodName: 'track',
30
+ type: 'member'
31
+ },
32
+ AMPLITUDE: {
33
+ name: 'amplitude',
34
+ objectName: 'amplitude',
35
+ methodName: 'track',
36
+ type: 'member'
37
+ },
38
+ RUDDERSTACK: {
39
+ name: 'rudderstack',
40
+ objectName: 'rudderanalytics',
41
+ methodName: 'track',
42
+ type: 'member'
43
+ },
44
+ MPARTICLE: {
45
+ name: 'mparticle',
46
+ objectNames: ['mParticle', 'mparticle'],
47
+ methodName: 'logEvent',
48
+ type: 'member'
49
+ },
50
+ POSTHOG: {
51
+ name: 'posthog',
52
+ objectName: 'posthog',
53
+ methodName: 'capture',
54
+ type: 'member'
55
+ },
56
+ PENDO: {
57
+ name: 'pendo',
58
+ objectName: 'pendo',
59
+ methodName: 'track',
60
+ type: 'member'
61
+ },
62
+ HEAP: {
63
+ name: 'heap',
64
+ objectName: 'heap',
65
+ methodName: 'track',
66
+ type: 'member'
67
+ },
68
+ SNOWPLOW: {
69
+ name: 'snowplow',
70
+ objectName: 'tracker',
71
+ methodName: 'track',
72
+ type: 'member'
73
+ },
74
+ GOOGLE_ANALYTICS: {
75
+ name: 'googleanalytics',
76
+ functionName: 'gtag',
77
+ type: 'function'
78
+ }
79
+ };
80
+
81
+ /**
82
+ * TypeScript syntax kinds for node types we care about
83
+ * @enum {number}
84
+ */
85
+ const TS_NODE_KINDS = {
86
+ CALL_EXPRESSION: 'CallExpression',
87
+ PROPERTY_ACCESS: 'PropertyAccessExpression',
88
+ IDENTIFIER: 'Identifier',
89
+ OBJECT_LITERAL: 'ObjectLiteralExpression',
90
+ ARRAY_LITERAL: 'ArrayLiteralExpression',
91
+ STRING_LITERAL: 'StringLiteral',
92
+ NUMERIC_LITERAL: 'NumericLiteral',
93
+ TRUE_KEYWORD: 'TrueKeyword',
94
+ FALSE_KEYWORD: 'FalseKeyword',
95
+ NULL_KEYWORD: 'NullKeyword',
96
+ UNDEFINED_KEYWORD: 'UndefinedKeyword',
97
+ FUNCTION_DECLARATION: 'FunctionDeclaration',
98
+ METHOD_DECLARATION: 'MethodDeclaration',
99
+ ARROW_FUNCTION: 'ArrowFunction',
100
+ VARIABLE_DECLARATION: 'VariableDeclaration',
101
+ PROPERTY_ASSIGNMENT: 'PropertyAssignment',
102
+ SHORTHAND_PROPERTY: 'ShorthandPropertyAssignment',
103
+ PARAMETER: 'Parameter'
104
+ };
105
+
106
+ module.exports = {
107
+ ANALYTICS_PROVIDERS,
108
+ TS_NODE_KINDS
109
+ };