@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flisk/analyze-tracking",
3
- "version": "0.7.4",
3
+ "version": "0.7.5",
4
4
  "description": "Analyzes tracking code in a project and generates data schemas",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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 && node.name === customFunction) return 'custom';
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_.arguments_[0].elements;
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_.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_.arguments_[0];
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_.arguments_[0].elements;
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_.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_.arguments_[0].elements;
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_.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_.arguments_[0];
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_.arguments_[0].elements;
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_.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
- const functionName = await findWrappingFunction(node, ancestors);
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.escapedText;
88
- const methodName = node.expression.name.escapedText;
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
- REACT_HOOKS.has(parent.initializer.expression.escapedText)
96
+ isReactHookCall(parent.initializer)
97
97
  ) {
98
98
  return `${parent.initializer.expression.escapedText}(${parent.name.escapedText})`;
99
99
  }