@flisk/analyze-tracking 0.7.4 → 0.7.5
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.
- package/package.json +1 -1
- package/src/analyze/ruby/detectors.js +12 -1
- package/src/analyze/ruby/extractors.js +24 -12
- package/src/analyze/ruby/visitor.js +10 -1
- package/src/analyze/typescript/detectors/analytics-source.js +2 -2
- package/src/analyze/typescript/extractors/property-extractor.js +1 -0
- package/src/analyze/typescript/utils/function-finder.js +1 -1
package/package.json
CHANGED
|
@@ -36,7 +36,18 @@ function detectSource(node, customFunction = null) {
|
|
|
36
36
|
if (node.name === 'track_struct_event') return 'snowplow';
|
|
37
37
|
|
|
38
38
|
// Custom tracking function
|
|
39
|
-
if (customFunction
|
|
39
|
+
if (customFunction) {
|
|
40
|
+
// Handle simple function names (e.g., 'customTrackFunction')
|
|
41
|
+
if (node.name === customFunction) return 'custom';
|
|
42
|
+
|
|
43
|
+
// Handle module-scoped function names (e.g., 'CustomModule.track')
|
|
44
|
+
if (customFunction.includes('.')) {
|
|
45
|
+
const [moduleName, methodName] = customFunction.split('.');
|
|
46
|
+
if (node.receiver && node.receiver.name === moduleName && node.name === methodName) {
|
|
47
|
+
return 'custom';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
40
51
|
|
|
41
52
|
return null;
|
|
42
53
|
}
|
|
@@ -14,14 +14,17 @@ const { getValueType } = require('./types');
|
|
|
14
14
|
function extractEventName(node, source) {
|
|
15
15
|
if (source === 'segment' || source === 'rudderstack') {
|
|
16
16
|
// Both Segment and Rudderstack use the same format
|
|
17
|
-
const params = node.arguments_
|
|
17
|
+
const params = node.arguments_?.arguments_?.[0]?.elements;
|
|
18
|
+
if (!params || !Array.isArray(params)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
18
21
|
const eventProperty = params.find(param => param?.key?.unescaped?.value === 'event');
|
|
19
22
|
return eventProperty?.value?.unescaped?.value || null;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
if (source === 'mixpanel') {
|
|
23
26
|
// Mixpanel Ruby SDK format: tracker.track('distinct_id', 'event_name', {...})
|
|
24
|
-
const args = node.arguments_
|
|
27
|
+
const args = node.arguments_?.arguments_;
|
|
25
28
|
if (args && args.length > 1 && args[1]?.unescaped?.value) {
|
|
26
29
|
return args[1].unescaped.value;
|
|
27
30
|
}
|
|
@@ -29,8 +32,8 @@ function extractEventName(node, source) {
|
|
|
29
32
|
|
|
30
33
|
if (source === 'posthog') {
|
|
31
34
|
// PostHog Ruby SDK format: posthog.capture({distinct_id: '...', event: '...', properties: {...}})
|
|
32
|
-
const hashArg = node.arguments_
|
|
33
|
-
if (hashArg && hashArg.elements) {
|
|
35
|
+
const hashArg = node.arguments_?.arguments_?.[0];
|
|
36
|
+
if (hashArg && hashArg.elements && Array.isArray(hashArg.elements)) {
|
|
34
37
|
const eventProperty = hashArg.elements.find(elem => elem?.key?.unescaped?.value === 'event');
|
|
35
38
|
return eventProperty?.value?.unescaped?.value || null;
|
|
36
39
|
}
|
|
@@ -38,14 +41,17 @@ function extractEventName(node, source) {
|
|
|
38
41
|
|
|
39
42
|
if (source === 'snowplow') {
|
|
40
43
|
// Snowplow Ruby SDK: tracker.track_struct_event(category: '...', action: '...', ...)
|
|
41
|
-
const params = node.arguments_
|
|
44
|
+
const params = node.arguments_?.arguments_?.[0]?.elements;
|
|
45
|
+
if (!params || !Array.isArray(params)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
42
48
|
const actionProperty = params.find(param => param?.key?.unescaped?.value === 'action');
|
|
43
49
|
return actionProperty?.value?.unescaped?.value || null;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
if (source === 'custom') {
|
|
47
53
|
// Custom function format: customFunction('event_name', {...})
|
|
48
|
-
const args = node.arguments_
|
|
54
|
+
const args = node.arguments_?.arguments_;
|
|
49
55
|
if (args && args.length > 0 && args[0]?.unescaped?.value) {
|
|
50
56
|
return args[0].unescaped.value;
|
|
51
57
|
}
|
|
@@ -65,7 +71,10 @@ async function extractProperties(node, source) {
|
|
|
65
71
|
|
|
66
72
|
if (source === 'segment' || source === 'rudderstack') {
|
|
67
73
|
// Both Segment and Rudderstack use the same format
|
|
68
|
-
const params = node.arguments_
|
|
74
|
+
const params = node.arguments_?.arguments_?.[0]?.elements;
|
|
75
|
+
if (!params || !Array.isArray(params)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
69
78
|
const properties = {};
|
|
70
79
|
|
|
71
80
|
// Process all top-level fields except 'event'
|
|
@@ -108,7 +117,7 @@ async function extractProperties(node, source) {
|
|
|
108
117
|
|
|
109
118
|
if (source === 'mixpanel') {
|
|
110
119
|
// Mixpanel Ruby SDK: tracker.track('distinct_id', 'event_name', {properties})
|
|
111
|
-
const args = node.arguments_
|
|
120
|
+
const args = node.arguments_?.arguments_;
|
|
112
121
|
const properties = {};
|
|
113
122
|
|
|
114
123
|
// Add distinct_id as property (even if it's a variable)
|
|
@@ -129,10 +138,10 @@ async function extractProperties(node, source) {
|
|
|
129
138
|
|
|
130
139
|
if (source === 'posthog') {
|
|
131
140
|
// PostHog Ruby SDK: posthog.capture({distinct_id: '...', event: '...', properties: {...}})
|
|
132
|
-
const hashArg = node.arguments_
|
|
141
|
+
const hashArg = node.arguments_?.arguments_?.[0];
|
|
133
142
|
const properties = {};
|
|
134
143
|
|
|
135
|
-
if (hashArg && hashArg.elements) {
|
|
144
|
+
if (hashArg && hashArg.elements && Array.isArray(hashArg.elements)) {
|
|
136
145
|
// Extract distinct_id if present
|
|
137
146
|
const distinctIdProperty = hashArg.elements.find(elem => elem?.key?.unescaped?.value === 'distinct_id');
|
|
138
147
|
if (distinctIdProperty?.value) {
|
|
@@ -154,7 +163,10 @@ async function extractProperties(node, source) {
|
|
|
154
163
|
|
|
155
164
|
if (source === 'snowplow') {
|
|
156
165
|
// Snowplow Ruby SDK: tracker.track_struct_event(category: '...', action: '...', ...)
|
|
157
|
-
const params = node.arguments_
|
|
166
|
+
const params = node.arguments_?.arguments_?.[0]?.elements;
|
|
167
|
+
if (!params || !Array.isArray(params)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
158
170
|
const properties = {};
|
|
159
171
|
|
|
160
172
|
// Extract all struct event parameters except 'action' (which is used as the event name)
|
|
@@ -172,7 +184,7 @@ async function extractProperties(node, source) {
|
|
|
172
184
|
|
|
173
185
|
if (source === 'custom') {
|
|
174
186
|
// Custom function format: customFunction('event_name', {properties})
|
|
175
|
-
const args = node.arguments_
|
|
187
|
+
const args = node.arguments_?.arguments_;
|
|
176
188
|
if (args && args.length > 1 && args[1] instanceof HashNode) {
|
|
177
189
|
return await extractHashProperties(args[1]);
|
|
178
190
|
}
|
|
@@ -29,7 +29,16 @@ class TrackingVisitor {
|
|
|
29
29
|
if (!eventName) return;
|
|
30
30
|
|
|
31
31
|
const line = getLineNumber(this.code, node.location);
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
// For module-scoped custom functions, use the custom function name as the functionName
|
|
34
|
+
// For simple custom functions, use the wrapping function name
|
|
35
|
+
let functionName;
|
|
36
|
+
if (source === 'custom' && this.customFunction && this.customFunction.includes('.')) {
|
|
37
|
+
functionName = this.customFunction;
|
|
38
|
+
} else {
|
|
39
|
+
functionName = await findWrappingFunction(node, ancestors);
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
const properties = await extractProperties(node, source);
|
|
34
43
|
|
|
35
44
|
this.events.push({
|
|
@@ -84,8 +84,8 @@ function detectMemberBasedProvider(node) {
|
|
|
84
84
|
return 'unknown';
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const objectName = node.expression.expression
|
|
88
|
-
const methodName = node.expression.name
|
|
87
|
+
const objectName = node.expression.expression?.escapedText;
|
|
88
|
+
const methodName = node.expression.name?.escapedText;
|
|
89
89
|
|
|
90
90
|
if (!objectName || !methodName) {
|
|
91
91
|
return 'unknown';
|
|
@@ -350,6 +350,7 @@ function resolveTypeSchema(checker, typeString) {
|
|
|
350
350
|
* @returns {string|null} Literal type or null
|
|
351
351
|
*/
|
|
352
352
|
function getLiteralType(node) {
|
|
353
|
+
if (!node) return null;
|
|
353
354
|
if (ts.isStringLiteral(node)) return 'string';
|
|
354
355
|
if (ts.isNumericLiteral(node)) return 'number';
|
|
355
356
|
if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return 'boolean';
|
|
@@ -93,7 +93,7 @@ function findParentFunctionName(node) {
|
|
|
93
93
|
parent.initializer &&
|
|
94
94
|
ts.isCallExpression(parent.initializer) &&
|
|
95
95
|
ts.isIdentifier(parent.initializer.expression) &&
|
|
96
|
-
|
|
96
|
+
isReactHookCall(parent.initializer)
|
|
97
97
|
) {
|
|
98
98
|
return `${parent.initializer.expression.escapedText}(${parent.name.escapedText})`;
|
|
99
99
|
}
|