@flisk/analyze-tracking 0.7.0 → 0.7.1
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/schema.json +4 -0
- package/src/analyze/analyzeGoFile.js +285 -89
package/package.json
CHANGED
package/schema.json
CHANGED
|
@@ -10,6 +10,9 @@ async function analyzeGoFile(filePath, customFunction) {
|
|
|
10
10
|
// Parse the Go file using go2json
|
|
11
11
|
const ast = extractGoAST(source);
|
|
12
12
|
|
|
13
|
+
// First pass: build type information for functions and variables
|
|
14
|
+
const typeContext = buildTypeContext(ast);
|
|
15
|
+
|
|
13
16
|
// Extract tracking events from the AST
|
|
14
17
|
const events = [];
|
|
15
18
|
let currentFunction = 'global';
|
|
@@ -20,7 +23,7 @@ async function analyzeGoFile(filePath, customFunction) {
|
|
|
20
23
|
currentFunction = node.name;
|
|
21
24
|
// Process the function body
|
|
22
25
|
if (node.body) {
|
|
23
|
-
extractEventsFromBody(node.body, events, filePath, currentFunction, customFunction);
|
|
26
|
+
extractEventsFromBody(node.body, events, filePath, currentFunction, customFunction, typeContext, currentFunction);
|
|
24
27
|
}
|
|
25
28
|
}
|
|
26
29
|
}
|
|
@@ -60,43 +63,112 @@ async function analyzeGoFile(filePath, customFunction) {
|
|
|
60
63
|
}
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
|
|
66
|
+
// Build a context of type information from the AST
|
|
67
|
+
function buildTypeContext(ast) {
|
|
68
|
+
const context = {
|
|
69
|
+
functions: {},
|
|
70
|
+
globals: {}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
for (const node of ast) {
|
|
74
|
+
if (node.tag === 'func') {
|
|
75
|
+
// Store function parameter types
|
|
76
|
+
context.functions[node.name] = {
|
|
77
|
+
params: {},
|
|
78
|
+
locals: {}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
if (node.args) {
|
|
82
|
+
for (const arg of node.args) {
|
|
83
|
+
if (arg.name && arg.type) {
|
|
84
|
+
context.functions[node.name].params[arg.name] = { type: arg.type };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Scan function body for local variable declarations
|
|
90
|
+
if (node.body) {
|
|
91
|
+
scanForDeclarations(node.body, context.functions[node.name].locals);
|
|
92
|
+
}
|
|
93
|
+
} else if (node.tag === 'declare') {
|
|
94
|
+
// Global variable declarations
|
|
95
|
+
if (node.names && node.names.length > 0 && node.type) {
|
|
96
|
+
for (const name of node.names) {
|
|
97
|
+
context.globals[name] = { type: node.type, value: node.value };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return context;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Scan statements for variable declarations
|
|
107
|
+
function scanForDeclarations(body, locals) {
|
|
108
|
+
for (const stmt of body) {
|
|
109
|
+
if (stmt.tag === 'declare') {
|
|
110
|
+
if (stmt.names && stmt.type) {
|
|
111
|
+
for (const name of stmt.names) {
|
|
112
|
+
locals[name] = { type: stmt.type, value: stmt.value };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} else if (stmt.tag === 'if' && stmt.body) {
|
|
116
|
+
scanForDeclarations(stmt.body, locals);
|
|
117
|
+
} else if (stmt.tag === 'elseif' && stmt.body) {
|
|
118
|
+
scanForDeclarations(stmt.body, locals);
|
|
119
|
+
} else if (stmt.tag === 'else' && stmt.body) {
|
|
120
|
+
scanForDeclarations(stmt.body, locals);
|
|
121
|
+
} else if (stmt.tag === 'for' && stmt.body) {
|
|
122
|
+
scanForDeclarations(stmt.body, locals);
|
|
123
|
+
} else if (stmt.tag === 'foreach' && stmt.body) {
|
|
124
|
+
scanForDeclarations(stmt.body, locals);
|
|
125
|
+
} else if (stmt.tag === 'switch' && stmt.cases) {
|
|
126
|
+
for (const caseNode of stmt.cases) {
|
|
127
|
+
if (caseNode.body) {
|
|
128
|
+
scanForDeclarations(caseNode.body, locals);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function extractEventsFromBody(body, events, filePath, functionName, customFunction, typeContext, currentFunction) {
|
|
64
136
|
for (const stmt of body) {
|
|
65
137
|
if (stmt.tag === 'exec' && stmt.expr) {
|
|
66
|
-
processExpression(stmt.expr, events, filePath, functionName, customFunction);
|
|
138
|
+
processExpression(stmt.expr, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
67
139
|
} else if (stmt.tag === 'declare' && stmt.value) {
|
|
68
140
|
// Handle variable declarations with tracking calls
|
|
69
|
-
processExpression(stmt.value, events, filePath, functionName, customFunction);
|
|
141
|
+
processExpression(stmt.value, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
70
142
|
} else if (stmt.tag === 'assign' && stmt.rhs) {
|
|
71
143
|
// Handle assignments with tracking calls
|
|
72
|
-
processExpression(stmt.rhs, events, filePath, functionName, customFunction);
|
|
144
|
+
processExpression(stmt.rhs, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
73
145
|
} else if (stmt.tag === 'if' && stmt.body) {
|
|
74
|
-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction);
|
|
146
|
+
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
75
147
|
} else if (stmt.tag === 'elseif' && stmt.body) {
|
|
76
|
-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction);
|
|
148
|
+
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
77
149
|
} else if (stmt.tag === 'else' && stmt.body) {
|
|
78
|
-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction);
|
|
150
|
+
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
79
151
|
} else if (stmt.tag === 'for' && stmt.body) {
|
|
80
|
-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction);
|
|
152
|
+
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
81
153
|
} else if (stmt.tag === 'foreach' && stmt.body) {
|
|
82
|
-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction);
|
|
154
|
+
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
83
155
|
} else if (stmt.tag === 'switch' && stmt.cases) {
|
|
84
156
|
for (const caseNode of stmt.cases) {
|
|
85
157
|
if (caseNode.body) {
|
|
86
|
-
extractEventsFromBody(caseNode.body, events, filePath, functionName, customFunction);
|
|
158
|
+
extractEventsFromBody(caseNode.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
87
159
|
}
|
|
88
160
|
}
|
|
89
161
|
}
|
|
90
162
|
}
|
|
91
163
|
}
|
|
92
164
|
|
|
93
|
-
function processExpression(expr, events, filePath, functionName, customFunction, depth = 0) {
|
|
165
|
+
function processExpression(expr, events, filePath, functionName, customFunction, typeContext, currentFunction, depth = 0) {
|
|
94
166
|
if (!expr || depth > 20) return; // Prevent infinite recursion with depth limit
|
|
95
167
|
|
|
96
168
|
// Handle array of expressions
|
|
97
169
|
if (Array.isArray(expr)) {
|
|
98
170
|
for (const item of expr) {
|
|
99
|
-
processExpression(item, events, filePath, functionName, customFunction, depth + 1);
|
|
171
|
+
processExpression(item, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
|
|
100
172
|
}
|
|
101
173
|
return;
|
|
102
174
|
}
|
|
@@ -104,25 +176,25 @@ function processExpression(expr, events, filePath, functionName, customFunction,
|
|
|
104
176
|
// Handle single expression with body
|
|
105
177
|
if (expr.body) {
|
|
106
178
|
for (const item of expr.body) {
|
|
107
|
-
processExpression(item, events, filePath, functionName, customFunction, depth + 1);
|
|
179
|
+
processExpression(item, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
|
|
108
180
|
}
|
|
109
181
|
return;
|
|
110
182
|
}
|
|
111
183
|
|
|
112
184
|
// Handle specific node types
|
|
113
185
|
if (expr.tag === 'call') {
|
|
114
|
-
const trackingCall = extractTrackingCall(expr, filePath, functionName, customFunction);
|
|
186
|
+
const trackingCall = extractTrackingCall(expr, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
115
187
|
if (trackingCall) {
|
|
116
188
|
events.push(trackingCall);
|
|
117
189
|
}
|
|
118
190
|
|
|
119
191
|
// Also process call arguments
|
|
120
192
|
if (expr.args) {
|
|
121
|
-
processExpression(expr.args, events, filePath, functionName, customFunction, depth + 1);
|
|
193
|
+
processExpression(expr.args, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
|
|
122
194
|
}
|
|
123
195
|
} else if (expr.tag === 'structlit') {
|
|
124
196
|
// Check if this struct literal is a tracking event
|
|
125
|
-
const trackingCall = extractTrackingCall(expr, filePath, functionName, customFunction);
|
|
197
|
+
const trackingCall = extractTrackingCall(expr, filePath, functionName, customFunction, typeContext, currentFunction);
|
|
126
198
|
if (trackingCall) {
|
|
127
199
|
events.push(trackingCall);
|
|
128
200
|
}
|
|
@@ -131,7 +203,7 @@ function processExpression(expr, events, filePath, functionName, customFunction,
|
|
|
131
203
|
if (!trackingCall && expr.fields) {
|
|
132
204
|
for (const field of expr.fields) {
|
|
133
205
|
if (field.value) {
|
|
134
|
-
processExpression(field.value, events, filePath, functionName, customFunction, depth + 1);
|
|
206
|
+
processExpression(field.value, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
|
|
135
207
|
}
|
|
136
208
|
}
|
|
137
209
|
}
|
|
@@ -139,24 +211,24 @@ function processExpression(expr, events, filePath, functionName, customFunction,
|
|
|
139
211
|
|
|
140
212
|
// Process other common properties that might contain expressions
|
|
141
213
|
if (expr.value && expr.tag !== 'structlit') {
|
|
142
|
-
processExpression(expr.value, events, filePath, functionName, customFunction, depth + 1);
|
|
214
|
+
processExpression(expr.value, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
|
|
143
215
|
}
|
|
144
216
|
if (expr.lhs) {
|
|
145
|
-
processExpression(expr.lhs, events, filePath, functionName, customFunction, depth + 1);
|
|
217
|
+
processExpression(expr.lhs, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
|
|
146
218
|
}
|
|
147
219
|
if (expr.rhs) {
|
|
148
|
-
processExpression(expr.rhs, events, filePath, functionName, customFunction, depth + 1);
|
|
220
|
+
processExpression(expr.rhs, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
|
|
149
221
|
}
|
|
150
222
|
}
|
|
151
223
|
|
|
152
|
-
function extractTrackingCall(callNode, filePath, functionName, customFunction) {
|
|
224
|
+
function extractTrackingCall(callNode, filePath, functionName, customFunction, typeContext, currentFunction) {
|
|
153
225
|
const source = detectSource(callNode, customFunction);
|
|
154
226
|
if (!source) return null;
|
|
155
227
|
|
|
156
228
|
const eventName = extractEventName(callNode, source);
|
|
157
229
|
if (!eventName) return null;
|
|
158
230
|
|
|
159
|
-
const properties = extractProperties(callNode, source);
|
|
231
|
+
const properties = extractProperties(callNode, source, typeContext, currentFunction);
|
|
160
232
|
|
|
161
233
|
// Get line number based on source type
|
|
162
234
|
let line = 0;
|
|
@@ -329,12 +401,12 @@ function extractEventName(callNode, source) {
|
|
|
329
401
|
return null;
|
|
330
402
|
}
|
|
331
403
|
|
|
332
|
-
function extractProperties(callNode, source) {
|
|
404
|
+
function extractProperties(callNode, source, typeContext, currentFunction) {
|
|
333
405
|
const properties = {};
|
|
334
406
|
|
|
335
407
|
switch (source) {
|
|
336
408
|
case 'mixpanel':
|
|
337
|
-
// mp.Track(ctx, []*mixpanel.Event{mp.NewEvent("event", "", map[string]any{...})})
|
|
409
|
+
// mp.Track(ctx, []*mixpanel.Event{mp.NewEvent("event", "distinctId", map[string]any{...})})
|
|
338
410
|
if (callNode.args && callNode.args.length > 1) {
|
|
339
411
|
const arrayArg = callNode.args[1];
|
|
340
412
|
if (arrayArg.tag === 'expr' && arrayArg.body) {
|
|
@@ -342,19 +414,48 @@ function extractProperties(callNode, source) {
|
|
|
342
414
|
if (arrayLit && arrayLit.items && arrayLit.items.length > 0) {
|
|
343
415
|
const firstItem = arrayLit.items[0];
|
|
344
416
|
if (Array.isArray(firstItem)) {
|
|
345
|
-
// Look for pattern: mp.NewEvent("event", "", map[string]any{...})
|
|
346
|
-
// The third argument is the properties map
|
|
417
|
+
// Look for pattern: mp.NewEvent("event", "distinctId", map[string]any{...})
|
|
347
418
|
let foundNewEvent = false;
|
|
348
419
|
for (let i = 0; i < firstItem.length - 4; i++) {
|
|
349
420
|
if (firstItem[i].tag === 'ident' && firstItem[i].value === 'mp' &&
|
|
350
421
|
firstItem[i+1].tag === 'sigil' && firstItem[i+1].value === '.' &&
|
|
351
422
|
firstItem[i+2].tag === 'ident' && firstItem[i+2].value === 'NewEvent' &&
|
|
352
423
|
firstItem[i+3].tag === 'sigil' && firstItem[i+3].value === '(') {
|
|
353
|
-
// Found mp.NewEvent( -
|
|
424
|
+
// Found mp.NewEvent( - process arguments
|
|
354
425
|
let j = i + 4;
|
|
355
426
|
let commaCount = 0;
|
|
427
|
+
let distinctIdToken = null;
|
|
428
|
+
|
|
429
|
+
// Skip the first argument (event name)
|
|
430
|
+
while (j < firstItem.length && commaCount < 1) {
|
|
431
|
+
if (firstItem[j].tag === 'sigil' && firstItem[j].value === ',') {
|
|
432
|
+
commaCount++;
|
|
433
|
+
}
|
|
434
|
+
j++;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Extract the second argument (DistinctId)
|
|
438
|
+
if (j < firstItem.length) {
|
|
439
|
+
// Skip whitespace
|
|
440
|
+
while (j < firstItem.length && firstItem[j].tag === 'newline') {
|
|
441
|
+
j++;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (firstItem[j]) {
|
|
445
|
+
if (firstItem[j].tag === 'string') {
|
|
446
|
+
// It's a string literal
|
|
447
|
+
const distinctId = firstItem[j].value.slice(1, -1); // Remove quotes
|
|
448
|
+
if (distinctId !== '') { // Only add if not empty string
|
|
449
|
+
properties['DistinctId'] = { type: 'string' };
|
|
450
|
+
}
|
|
451
|
+
} else if (firstItem[j].tag === 'ident') {
|
|
452
|
+
// It's a variable reference - look up its type
|
|
453
|
+
properties['DistinctId'] = getPropertyInfo(firstItem[j], typeContext, currentFunction);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
356
457
|
|
|
357
|
-
//
|
|
458
|
+
// Continue to find the properties map (third argument)
|
|
358
459
|
while (j < firstItem.length && commaCount < 2) {
|
|
359
460
|
if (firstItem[j].tag === 'sigil' && firstItem[j].value === ',') {
|
|
360
461
|
commaCount++;
|
|
@@ -370,13 +471,15 @@ function extractProperties(callNode, source) {
|
|
|
370
471
|
while (j < firstItem.length) {
|
|
371
472
|
if (firstItem[j].tag === 'sigil' && firstItem[j].value === '{') {
|
|
372
473
|
// Simple property extraction from tokens
|
|
373
|
-
// Look for pattern: "key":
|
|
474
|
+
// Look for pattern: "key": value
|
|
374
475
|
for (let k = j + 1; k < firstItem.length - 2; k++) {
|
|
375
476
|
if (firstItem[k].tag === 'string' &&
|
|
376
|
-
firstItem[k+1].tag === 'sigil' && firstItem[k+1].value === ':'
|
|
377
|
-
firstItem[k+2].tag === 'string') {
|
|
477
|
+
firstItem[k+1].tag === 'sigil' && firstItem[k+1].value === ':') {
|
|
378
478
|
const key = firstItem[k].value.slice(1, -1);
|
|
379
|
-
|
|
479
|
+
|
|
480
|
+
// Use getPropertyInfo to determine the type
|
|
481
|
+
const valueToken = firstItem[k+2];
|
|
482
|
+
properties[key] = getPropertyInfo(valueToken, typeContext, currentFunction);
|
|
380
483
|
}
|
|
381
484
|
}
|
|
382
485
|
foundNewEvent = true;
|
|
@@ -405,16 +508,16 @@ function extractProperties(callNode, source) {
|
|
|
405
508
|
// Extract UserId/DistinctId
|
|
406
509
|
const idField = findStructField(callNode, source === 'segment' ? 'UserId' : 'DistinctId');
|
|
407
510
|
if (idField) {
|
|
408
|
-
properties[source === 'segment' ? '
|
|
511
|
+
properties[source === 'segment' ? 'UserId' : 'DistinctId'] = { type: 'string' };
|
|
409
512
|
}
|
|
410
513
|
|
|
411
514
|
// Extract Properties
|
|
412
515
|
const propsField = findStructField(callNode, 'Properties');
|
|
413
516
|
if (propsField && propsField.value) {
|
|
414
517
|
if (source === 'segment') {
|
|
415
|
-
extractSegmentProperties(propsField.value, properties);
|
|
518
|
+
extractSegmentProperties(propsField.value, properties, typeContext, currentFunction);
|
|
416
519
|
} else {
|
|
417
|
-
extractPostHogProperties(propsField.value, properties);
|
|
520
|
+
extractPostHogProperties(propsField.value, properties, typeContext, currentFunction);
|
|
418
521
|
}
|
|
419
522
|
}
|
|
420
523
|
}
|
|
@@ -426,13 +529,13 @@ function extractProperties(callNode, source) {
|
|
|
426
529
|
// Extract UserID
|
|
427
530
|
const userIdField = findStructField(callNode, 'UserID');
|
|
428
531
|
if (userIdField) {
|
|
429
|
-
properties
|
|
532
|
+
properties['UserID'] = { type: 'string' };
|
|
430
533
|
}
|
|
431
534
|
|
|
432
535
|
// Extract EventProperties
|
|
433
536
|
const eventPropsField = findStructField(callNode, 'EventProperties');
|
|
434
537
|
if (eventPropsField) {
|
|
435
|
-
extractPropertiesFromExpr(eventPropsField.value, properties, source);
|
|
538
|
+
extractPropertiesFromExpr(eventPropsField.value, properties, source, typeContext, currentFunction);
|
|
436
539
|
}
|
|
437
540
|
|
|
438
541
|
// Extract EventOptions
|
|
@@ -456,7 +559,7 @@ function extractProperties(callNode, source) {
|
|
|
456
559
|
if (value.tag === 'number') {
|
|
457
560
|
properties[fieldName] = { type: 'number' };
|
|
458
561
|
} else {
|
|
459
|
-
properties[fieldName] = getPropertyInfo(value);
|
|
562
|
+
properties[fieldName] = getPropertyInfo(value, typeContext, currentFunction);
|
|
460
563
|
}
|
|
461
564
|
}
|
|
462
565
|
}
|
|
@@ -472,13 +575,13 @@ function extractProperties(callNode, source) {
|
|
|
472
575
|
// Extract UserID
|
|
473
576
|
const userIdField = findStructField(eventStruct, 'UserID');
|
|
474
577
|
if (userIdField) {
|
|
475
|
-
properties
|
|
578
|
+
properties['UserID'] = { type: 'string' };
|
|
476
579
|
}
|
|
477
580
|
|
|
478
581
|
// Extract EventProperties
|
|
479
582
|
const eventPropsField = findStructField(eventStruct, 'EventProperties');
|
|
480
583
|
if (eventPropsField) {
|
|
481
|
-
extractPropertiesFromExpr(eventPropsField.value, properties, source);
|
|
584
|
+
extractPropertiesFromExpr(eventPropsField.value, properties, source, typeContext, currentFunction);
|
|
482
585
|
}
|
|
483
586
|
|
|
484
587
|
// Extract EventOptions
|
|
@@ -502,7 +605,7 @@ function extractProperties(callNode, source) {
|
|
|
502
605
|
if (value.tag === 'number') {
|
|
503
606
|
properties[fieldName] = { type: 'number' };
|
|
504
607
|
} else {
|
|
505
|
-
properties[fieldName] = getPropertyInfo(value);
|
|
608
|
+
properties[fieldName] = getPropertyInfo(value, typeContext, currentFunction);
|
|
506
609
|
}
|
|
507
610
|
}
|
|
508
611
|
}
|
|
@@ -523,9 +626,33 @@ function extractProperties(callNode, source) {
|
|
|
523
626
|
for (const field of structEvent.fields) {
|
|
524
627
|
const fieldName = extractFieldName(field);
|
|
525
628
|
if (fieldName && fieldName !== 'Action') {
|
|
526
|
-
|
|
527
|
-
if (value
|
|
528
|
-
|
|
629
|
+
// Handle both direct values and sphelp.NewString/NewFloat64 calls
|
|
630
|
+
if (field.value) {
|
|
631
|
+
if (field.value.tag === 'expr' && field.value.body) {
|
|
632
|
+
// Look for sphelp.NewString/NewFloat64 calls
|
|
633
|
+
const callNode = field.value.body.find(item =>
|
|
634
|
+
item.tag === 'call' &&
|
|
635
|
+
item.func &&
|
|
636
|
+
item.func.tag === 'access' &&
|
|
637
|
+
(item.func.member === 'NewString' || item.func.member === 'NewFloat64')
|
|
638
|
+
);
|
|
639
|
+
|
|
640
|
+
if (callNode && callNode.args && callNode.args.length > 0) {
|
|
641
|
+
const value = callNode.args[0];
|
|
642
|
+
|
|
643
|
+
// Handle case where value is an expr with the actual value in body[0]
|
|
644
|
+
let actualValue = value;
|
|
645
|
+
if (value.tag === 'expr' && value.body && value.body.length > 0) {
|
|
646
|
+
actualValue = value.body[0];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Use getPropertyInfo to handle all value types including variables
|
|
650
|
+
properties[fieldName] = getPropertyInfo(actualValue, typeContext, currentFunction);
|
|
651
|
+
}
|
|
652
|
+
} else {
|
|
653
|
+
// Handle direct values using getPropertyInfo
|
|
654
|
+
properties[fieldName] = getPropertyInfo(field.value, typeContext, currentFunction);
|
|
655
|
+
}
|
|
529
656
|
}
|
|
530
657
|
}
|
|
531
658
|
}
|
|
@@ -536,7 +663,7 @@ function extractProperties(callNode, source) {
|
|
|
536
663
|
case 'custom':
|
|
537
664
|
// customFunction("event", map[string]interface{}{...})
|
|
538
665
|
if (callNode.args && callNode.args.length > 1) {
|
|
539
|
-
extractPropertiesFromExpr(callNode.args[1], properties, source);
|
|
666
|
+
extractPropertiesFromExpr(callNode.args[1], properties, source, typeContext, currentFunction);
|
|
540
667
|
}
|
|
541
668
|
break;
|
|
542
669
|
}
|
|
@@ -598,7 +725,7 @@ function extractFieldName(field) {
|
|
|
598
725
|
}
|
|
599
726
|
|
|
600
727
|
// Helper function to extract Segment/PostHog properties from NewProperties().Set() chain
|
|
601
|
-
function extractSegmentProperties(expr, properties) {
|
|
728
|
+
function extractSegmentProperties(expr, properties, typeContext, currentFunction) {
|
|
602
729
|
if (!expr) return;
|
|
603
730
|
|
|
604
731
|
// Look for method calls in the expression
|
|
@@ -628,19 +755,9 @@ function extractSegmentProperties(expr, properties) {
|
|
|
628
755
|
// Handle different value types
|
|
629
756
|
if (value.tag === 'expr' && value.body) {
|
|
630
757
|
const firstItem = value.body[0];
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
if (firstItem.value === 'true' || firstItem.value === 'false') {
|
|
635
|
-
properties[key] = { type: 'boolean' };
|
|
636
|
-
} else if (firstItem.value === 'nil') {
|
|
637
|
-
properties[key] = { type: 'null' };
|
|
638
|
-
} else {
|
|
639
|
-
properties[key] = { type: 'any' };
|
|
640
|
-
}
|
|
641
|
-
} else if (firstItem.tag === 'number') {
|
|
642
|
-
properties[key] = { type: 'number' };
|
|
643
|
-
}
|
|
758
|
+
properties[key] = getPropertyInfo(firstItem, typeContext, currentFunction);
|
|
759
|
+
} else {
|
|
760
|
+
properties[key] = getPropertyInfo(value, typeContext, currentFunction);
|
|
644
761
|
}
|
|
645
762
|
}
|
|
646
763
|
}
|
|
@@ -654,19 +771,9 @@ function extractSegmentProperties(expr, properties) {
|
|
|
654
771
|
// Handle different value types
|
|
655
772
|
if (value.tag === 'expr' && value.body) {
|
|
656
773
|
const firstItem = value.body[0];
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (firstItem.value === 'true' || firstItem.value === 'false') {
|
|
661
|
-
properties[key] = { type: 'boolean' };
|
|
662
|
-
} else if (firstItem.value === 'nil') {
|
|
663
|
-
properties[key] = { type: 'null' };
|
|
664
|
-
} else {
|
|
665
|
-
properties[key] = { type: 'any' };
|
|
666
|
-
}
|
|
667
|
-
} else if (firstItem.tag === 'number') {
|
|
668
|
-
properties[key] = { type: 'number' };
|
|
669
|
-
}
|
|
774
|
+
properties[key] = getPropertyInfo(firstItem, typeContext, currentFunction);
|
|
775
|
+
} else {
|
|
776
|
+
properties[key] = getPropertyInfo(value, typeContext, currentFunction);
|
|
670
777
|
}
|
|
671
778
|
}
|
|
672
779
|
}
|
|
@@ -736,12 +843,12 @@ function extractStringValue(node) {
|
|
|
736
843
|
return null;
|
|
737
844
|
}
|
|
738
845
|
|
|
739
|
-
function extractPropertiesFromExpr(expr, properties, source) {
|
|
846
|
+
function extractPropertiesFromExpr(expr, properties, source, typeContext, currentFunction) {
|
|
740
847
|
// Handle struct literals (e.g., Type{field: value})
|
|
741
848
|
if (expr.tag === 'structlit' && expr.fields) {
|
|
742
849
|
for (const field of expr.fields) {
|
|
743
850
|
if (field.name) {
|
|
744
|
-
const propInfo = getPropertyInfo(field.value);
|
|
851
|
+
const propInfo = getPropertyInfo(field.value, typeContext, currentFunction);
|
|
745
852
|
properties[field.name] = propInfo;
|
|
746
853
|
} else if (field.value && field.value.tag === 'expr' && field.value.body) {
|
|
747
854
|
// Handle map literal fields that don't have explicit names
|
|
@@ -759,12 +866,12 @@ function extractPropertiesFromExpr(expr, properties, source) {
|
|
|
759
866
|
const remainingNodes = field.value.body.slice(3); // Skip key, :, and map declaration
|
|
760
867
|
const structlit = remainingNodes.find(node => node.tag === 'structlit');
|
|
761
868
|
if (structlit) {
|
|
762
|
-
properties[key] = getPropertyInfo(structlit);
|
|
869
|
+
properties[key] = getPropertyInfo(structlit, typeContext, currentFunction);
|
|
763
870
|
} else {
|
|
764
871
|
properties[key] = { type: 'object', properties: {} };
|
|
765
872
|
}
|
|
766
873
|
} else if (valueNode) {
|
|
767
|
-
properties[key] = getPropertyInfo(valueNode);
|
|
874
|
+
properties[key] = getPropertyInfo(valueNode, typeContext, currentFunction);
|
|
768
875
|
}
|
|
769
876
|
}
|
|
770
877
|
}
|
|
@@ -775,7 +882,7 @@ function extractPropertiesFromExpr(expr, properties, source) {
|
|
|
775
882
|
if (expr.tag === 'expr' && expr.body) {
|
|
776
883
|
for (const item of expr.body) {
|
|
777
884
|
if (item.tag === 'structlit') {
|
|
778
|
-
extractPropertiesFromExpr(item, properties, source);
|
|
885
|
+
extractPropertiesFromExpr(item, properties, source, typeContext, currentFunction);
|
|
779
886
|
} else if (item.tag === 'index' && item.container && item.container.value === 'map') {
|
|
780
887
|
// This is a map[string]interface{} type declaration
|
|
781
888
|
// Look for the following structlit
|
|
@@ -785,7 +892,7 @@ function extractPropertiesFromExpr(expr, properties, source) {
|
|
|
785
892
|
}
|
|
786
893
|
}
|
|
787
894
|
|
|
788
|
-
function getPropertyInfo(value) {
|
|
895
|
+
function getPropertyInfo(value, typeContext, currentFunction) {
|
|
789
896
|
if (!value) return { type: 'any' };
|
|
790
897
|
|
|
791
898
|
// Handle direct values
|
|
@@ -805,7 +912,41 @@ function getPropertyInfo(value) {
|
|
|
805
912
|
if (value.value === 'nil') {
|
|
806
913
|
return { type: 'null' };
|
|
807
914
|
}
|
|
808
|
-
//
|
|
915
|
+
// Look up the variable type in the context
|
|
916
|
+
const varName = value.value;
|
|
917
|
+
let varInfo = null;
|
|
918
|
+
|
|
919
|
+
// Check function parameters first
|
|
920
|
+
if (typeContext && currentFunction && typeContext.functions[currentFunction]) {
|
|
921
|
+
const funcContext = typeContext.functions[currentFunction];
|
|
922
|
+
if (funcContext.params[varName]) {
|
|
923
|
+
varInfo = funcContext.params[varName];
|
|
924
|
+
} else if (funcContext.locals[varName]) {
|
|
925
|
+
varInfo = funcContext.locals[varName];
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
// Check global variables
|
|
930
|
+
if (!varInfo && typeContext && typeContext.globals[varName]) {
|
|
931
|
+
varInfo = typeContext.globals[varName];
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (varInfo) {
|
|
935
|
+
// If we have a value stored for this variable, analyze it to get nested properties
|
|
936
|
+
if (varInfo.value && varInfo.type && varInfo.type.tag === 'map') {
|
|
937
|
+
// The variable has a map type and a value, extract properties from the value
|
|
938
|
+
const nestedProps = {};
|
|
939
|
+
extractPropertiesFromExpr(varInfo.value, nestedProps, null, typeContext, currentFunction);
|
|
940
|
+
return {
|
|
941
|
+
type: 'object',
|
|
942
|
+
properties: nestedProps
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
// Otherwise just return the type
|
|
946
|
+
return mapGoTypeToSchemaType(varInfo.type);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Otherwise it's an unknown variable reference
|
|
809
950
|
return { type: 'any' };
|
|
810
951
|
}
|
|
811
952
|
|
|
@@ -844,7 +985,7 @@ function getPropertyInfo(value) {
|
|
|
844
985
|
// Inline the property extraction for nested objects
|
|
845
986
|
for (const field of structlit.fields) {
|
|
846
987
|
if (field.name) {
|
|
847
|
-
nestedProps[field.name] = getPropertyInfo(field.value);
|
|
988
|
+
nestedProps[field.name] = getPropertyInfo(field.value, typeContext, currentFunction);
|
|
848
989
|
} else if (field.value && field.value.tag === 'expr' && field.value.body) {
|
|
849
990
|
// Handle map literal fields
|
|
850
991
|
const keyNode = field.value.body[0];
|
|
@@ -859,12 +1000,12 @@ function getPropertyInfo(value) {
|
|
|
859
1000
|
const remainingNodes = field.value.body.slice(3);
|
|
860
1001
|
const structlit = remainingNodes.find(node => node.tag === 'structlit');
|
|
861
1002
|
if (structlit) {
|
|
862
|
-
nestedProps[key] = getPropertyInfo(structlit);
|
|
1003
|
+
nestedProps[key] = getPropertyInfo(structlit, typeContext, currentFunction);
|
|
863
1004
|
} else {
|
|
864
1005
|
nestedProps[key] = { type: 'object', properties: {} };
|
|
865
1006
|
}
|
|
866
1007
|
} else if (valueNode) {
|
|
867
|
-
nestedProps[key] = getPropertyInfo(valueNode);
|
|
1008
|
+
nestedProps[key] = getPropertyInfo(valueNode, typeContext, currentFunction);
|
|
868
1009
|
}
|
|
869
1010
|
}
|
|
870
1011
|
}
|
|
@@ -882,7 +1023,7 @@ function getPropertyInfo(value) {
|
|
|
882
1023
|
if (firstItem.fields) {
|
|
883
1024
|
for (const field of firstItem.fields) {
|
|
884
1025
|
if (field.name) {
|
|
885
|
-
nestedProps[field.name] = getPropertyInfo(field.value);
|
|
1026
|
+
nestedProps[field.name] = getPropertyInfo(field.value, typeContext, currentFunction);
|
|
886
1027
|
} else if (field.value && field.value.tag === 'expr' && field.value.body) {
|
|
887
1028
|
// Handle map literal fields
|
|
888
1029
|
const keyNode = field.value.body[0];
|
|
@@ -897,12 +1038,12 @@ function getPropertyInfo(value) {
|
|
|
897
1038
|
const remainingNodes = field.value.body.slice(3);
|
|
898
1039
|
const structlit = remainingNodes.find(node => node.tag === 'structlit');
|
|
899
1040
|
if (structlit) {
|
|
900
|
-
nestedProps[key] = getPropertyInfo(structlit);
|
|
1041
|
+
nestedProps[key] = getPropertyInfo(structlit, typeContext, currentFunction);
|
|
901
1042
|
} else {
|
|
902
1043
|
nestedProps[key] = { type: 'object', properties: {} };
|
|
903
1044
|
}
|
|
904
1045
|
} else if (valueNode) {
|
|
905
|
-
nestedProps[key] = getPropertyInfo(valueNode);
|
|
1046
|
+
nestedProps[key] = getPropertyInfo(valueNode, typeContext, currentFunction);
|
|
906
1047
|
}
|
|
907
1048
|
}
|
|
908
1049
|
}
|
|
@@ -929,7 +1070,7 @@ function getPropertyInfo(value) {
|
|
|
929
1070
|
if (value.fields) {
|
|
930
1071
|
for (const field of value.fields) {
|
|
931
1072
|
if (field.name) {
|
|
932
|
-
nestedProps[field.name] = getPropertyInfo(field.value);
|
|
1073
|
+
nestedProps[field.name] = getPropertyInfo(field.value, typeContext, currentFunction);
|
|
933
1074
|
} else if (field.value && field.value.tag === 'expr' && field.value.body) {
|
|
934
1075
|
// Handle map literal fields
|
|
935
1076
|
const keyNode = field.value.body[0];
|
|
@@ -944,12 +1085,12 @@ function getPropertyInfo(value) {
|
|
|
944
1085
|
const remainingNodes = field.value.body.slice(3);
|
|
945
1086
|
const structlit = remainingNodes.find(node => node.tag === 'structlit');
|
|
946
1087
|
if (structlit) {
|
|
947
|
-
nestedProps[key] = getPropertyInfo(structlit);
|
|
1088
|
+
nestedProps[key] = getPropertyInfo(structlit, typeContext, currentFunction);
|
|
948
1089
|
} else {
|
|
949
1090
|
nestedProps[key] = { type: 'object', properties: {} };
|
|
950
1091
|
}
|
|
951
1092
|
} else if (valueNode) {
|
|
952
|
-
nestedProps[key] = getPropertyInfo(valueNode);
|
|
1093
|
+
nestedProps[key] = getPropertyInfo(valueNode, typeContext, currentFunction);
|
|
953
1094
|
}
|
|
954
1095
|
}
|
|
955
1096
|
}
|
|
@@ -965,4 +1106,59 @@ function getPropertyInfo(value) {
|
|
|
965
1106
|
return { type: 'any' };
|
|
966
1107
|
}
|
|
967
1108
|
|
|
1109
|
+
// Helper function to map Go types to schema types
|
|
1110
|
+
function mapGoTypeToSchemaType(goType) {
|
|
1111
|
+
if (!goType) return { type: 'any' };
|
|
1112
|
+
|
|
1113
|
+
// Handle case where goType might be an object with a type property
|
|
1114
|
+
if (goType.type) {
|
|
1115
|
+
goType = goType.type;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Handle simple types
|
|
1119
|
+
if (goType.tag === 'string') return { type: 'string' };
|
|
1120
|
+
if (goType.tag === 'bool') return { type: 'boolean' };
|
|
1121
|
+
if (goType.tag === 'int' || goType.tag === 'int8' || goType.tag === 'int16' ||
|
|
1122
|
+
goType.tag === 'int32' || goType.tag === 'int64' || goType.tag === 'uint' ||
|
|
1123
|
+
goType.tag === 'uint8' || goType.tag === 'uint16' || goType.tag === 'uint32' ||
|
|
1124
|
+
goType.tag === 'uint64' || goType.tag === 'float32' || goType.tag === 'float64' ||
|
|
1125
|
+
goType.tag === 'byte' || goType.tag === 'rune') {
|
|
1126
|
+
return { type: 'number' };
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Handle array types
|
|
1130
|
+
if (goType.tag === 'array') {
|
|
1131
|
+
const itemType = mapGoTypeToSchemaType(goType.item);
|
|
1132
|
+
return {
|
|
1133
|
+
type: 'array',
|
|
1134
|
+
items: itemType
|
|
1135
|
+
};
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
// Handle slice types (arrays without fixed size)
|
|
1139
|
+
if (goType.tag === 'array' && !goType.size) {
|
|
1140
|
+
const itemType = mapGoTypeToSchemaType(goType.item);
|
|
1141
|
+
return {
|
|
1142
|
+
type: 'array',
|
|
1143
|
+
items: itemType
|
|
1144
|
+
};
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// Handle map types
|
|
1148
|
+
if (goType.tag === 'map') {
|
|
1149
|
+
return {
|
|
1150
|
+
type: 'object',
|
|
1151
|
+
properties: {}
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Handle pointer types by dereferencing
|
|
1156
|
+
if (goType.tag === 'ptr') {
|
|
1157
|
+
return mapGoTypeToSchemaType(goType.item);
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Default to any for complex or unknown types
|
|
1161
|
+
return { type: 'any' };
|
|
1162
|
+
}
|
|
1163
|
+
|
|
968
1164
|
module.exports = { analyzeGoFile };
|