Skip to content

Commit f2957dc

Browse files
committed
custom function signatures implementation
1 parent 2204697 commit f2957dc

36 files changed

+654
-268
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ Your function signature should be in the following format:
4444
yourCustomTrackFunctionName(EVENT_NAME, PROPERTIES, customFieldOne, customFieldTwo)
4545
```
4646

47-
- `EVENT_NAME` is the name of the event you are tracking. It should be a string or a pointer to a string.
48-
- `PROPERTIES` is an object of properties for that event. It should be an object / dictionary.
47+
- `EVENT_NAME` is the name of the event you are tracking. It should be a string or a pointer to a string. This is required.
48+
- `PROPERTIES` is an object of properties for that event. It should be an object / dictionary. This is optional.
4949
- Any additional parameters are other fields you are tracking. They can be of any type. The names you provide for these parameters will be used as the property names in the output.
5050

5151

@@ -55,7 +55,7 @@ For example, if your function has a userId parameter at the beginning, followed
5555
yourCustomTrackFunctionName(userId, EVENT_NAME, PROPERTIES)
5656
```
5757

58-
If your function follows the format `yourCustomTrackFunctionName(EVENT_NAME, PROPERTIES)`, you can simply pass in `yourCustomTrackFunctionName` to `--customFunction` as a shorthand.
58+
If your function follows the standard format `yourCustomTrackFunctionName(EVENT_NAME, PROPERTIES)`, you can simply pass in `yourCustomTrackFunctionName` to `--customFunction` as a shorthand.
5959

6060

6161
## What's Generated?

src/analyze/go/astTraversal.js

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,34 @@ const { extractTrackingEvent } = require('./trackingExtractor');
1212
* @param {Array<Object>} events - Array to collect found tracking events (modified in place)
1313
* @param {string} filePath - Path to the file being analyzed
1414
* @param {string} functionName - Name of the current function being processed
15-
* @param {string|null} customFunction - Name of custom tracking function to detect
15+
* @param {Object|null} customConfig - Parsed custom function configuration (or null)
1616
* @param {Object} typeContext - Type information context for variable resolution
1717
* @param {string} currentFunction - Current function context for type lookups
1818
*/
19-
function extractEventsFromBody(body, events, filePath, functionName, customFunction, typeContext, currentFunction) {
19+
function extractEventsFromBody(body, events, filePath, functionName, customConfig, typeContext, currentFunction) {
2020
for (const stmt of body) {
2121
if (stmt.tag === 'exec' && stmt.expr) {
22-
processExpression(stmt.expr, events, filePath, functionName, customFunction, typeContext, currentFunction);
22+
processExpression(stmt.expr, events, filePath, functionName, customConfig, typeContext, currentFunction);
2323
} else if (stmt.tag === 'declare' && stmt.value) {
2424
// Handle variable declarations with tracking calls
25-
processExpression(stmt.value, events, filePath, functionName, customFunction, typeContext, currentFunction);
25+
processExpression(stmt.value, events, filePath, functionName, customConfig, typeContext, currentFunction);
2626
} else if (stmt.tag === 'assign' && stmt.rhs) {
2727
// Handle assignments with tracking calls
28-
processExpression(stmt.rhs, events, filePath, functionName, customFunction, typeContext, currentFunction);
28+
processExpression(stmt.rhs, events, filePath, functionName, customConfig, typeContext, currentFunction);
2929
} else if (stmt.tag === 'if' && stmt.body) {
30-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
30+
extractEventsFromBody(stmt.body, events, filePath, functionName, customConfig, typeContext, currentFunction);
3131
} else if (stmt.tag === 'elseif' && stmt.body) {
32-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
32+
extractEventsFromBody(stmt.body, events, filePath, functionName, customConfig, typeContext, currentFunction);
3333
} else if (stmt.tag === 'else' && stmt.body) {
34-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
34+
extractEventsFromBody(stmt.body, events, filePath, functionName, customConfig, typeContext, currentFunction);
3535
} else if (stmt.tag === 'for' && stmt.body) {
36-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
36+
extractEventsFromBody(stmt.body, events, filePath, functionName, customConfig, typeContext, currentFunction);
3737
} else if (stmt.tag === 'foreach' && stmt.body) {
38-
extractEventsFromBody(stmt.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
38+
extractEventsFromBody(stmt.body, events, filePath, functionName, customConfig, typeContext, currentFunction);
3939
} else if (stmt.tag === 'switch' && stmt.cases) {
4040
for (const caseNode of stmt.cases) {
4141
if (caseNode.body) {
42-
extractEventsFromBody(caseNode.body, events, filePath, functionName, customFunction, typeContext, currentFunction);
42+
extractEventsFromBody(caseNode.body, events, filePath, functionName, customConfig, typeContext, currentFunction);
4343
}
4444
}
4545
}
@@ -52,44 +52,44 @@ function extractEventsFromBody(body, events, filePath, functionName, customFunct
5252
* @param {Array<Object>} events - Array to collect found tracking events (modified in place)
5353
* @param {string} filePath - Path to the file being analyzed
5454
* @param {string} functionName - Name of the current function being processed
55-
* @param {string|null} customFunction - Name of custom tracking function to detect
55+
* @param {Object|null} customConfig - Parsed custom function configuration (or null)
5656
* @param {Object} typeContext - Type information context for variable resolution
5757
* @param {string} currentFunction - Current function context for type lookups
5858
* @param {number} [depth=0] - Current recursion depth (used to prevent infinite recursion)
5959
*/
60-
function processExpression(expr, events, filePath, functionName, customFunction, typeContext, currentFunction, depth = 0) {
60+
function processExpression(expr, events, filePath, functionName, customConfig, typeContext, currentFunction, depth = 0) {
6161
if (!expr || depth > MAX_RECURSION_DEPTH) return; // Prevent infinite recursion with depth limit
6262

6363
// Handle array of expressions
6464
if (Array.isArray(expr)) {
6565
for (const item of expr) {
66-
processExpression(item, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
66+
processExpression(item, events, filePath, functionName, customConfig, typeContext, currentFunction, depth + 1);
6767
}
6868
return;
6969
}
7070

7171
// Handle single expression with body
7272
if (expr.body) {
7373
for (const item of expr.body) {
74-
processExpression(item, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
74+
processExpression(item, events, filePath, functionName, customConfig, typeContext, currentFunction, depth + 1);
7575
}
7676
return;
7777
}
7878

7979
// Handle specific node types
8080
if (expr.tag === 'call') {
81-
const trackingCall = extractTrackingEvent(expr, filePath, functionName, customFunction, typeContext, currentFunction);
81+
const trackingCall = extractTrackingEvent(expr, filePath, functionName, customConfig, typeContext, currentFunction);
8282
if (trackingCall) {
8383
events.push(trackingCall);
8484
}
8585

8686
// Also process call arguments
8787
if (expr.args) {
88-
processExpression(expr.args, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
88+
processExpression(expr.args, events, filePath, functionName, customConfig, typeContext, currentFunction, depth + 1);
8989
}
9090
} else if (expr.tag === 'structlit') {
9191
// Check if this struct literal is a tracking event
92-
const trackingCall = extractTrackingEvent(expr, filePath, functionName, customFunction, typeContext, currentFunction);
92+
const trackingCall = extractTrackingEvent(expr, filePath, functionName, customConfig, typeContext, currentFunction);
9393
if (trackingCall) {
9494
events.push(trackingCall);
9595
}
@@ -98,21 +98,21 @@ function processExpression(expr, events, filePath, functionName, customFunction,
9898
if (!trackingCall && expr.fields) {
9999
for (const field of expr.fields) {
100100
if (field.value) {
101-
processExpression(field.value, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
101+
processExpression(field.value, events, filePath, functionName, customConfig, typeContext, currentFunction, depth + 1);
102102
}
103103
}
104104
}
105105
}
106106

107107
// Process other common properties that might contain expressions
108108
if (expr.value && expr.tag !== 'structlit') {
109-
processExpression(expr.value, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
109+
processExpression(expr.value, events, filePath, functionName, customConfig, typeContext, currentFunction, depth + 1);
110110
}
111111
if (expr.lhs) {
112-
processExpression(expr.lhs, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
112+
processExpression(expr.lhs, events, filePath, functionName, customConfig, typeContext, currentFunction, depth + 1);
113113
}
114114
if (expr.rhs) {
115-
processExpression(expr.rhs, events, filePath, functionName, customFunction, typeContext, currentFunction, depth + 1);
115+
processExpression(expr.rhs, events, filePath, functionName, customConfig, typeContext, currentFunction, depth + 1);
116116
}
117117
}
118118

src/analyze/go/eventExtractor.js

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ const { extractStringValue, findStructLiteral, findStructField, extractSnowplowV
1010
* Extract event name from a tracking call based on the source
1111
* @param {Object} callNode - AST node representing a function call or struct literal
1212
* @param {string} source - Analytics source (e.g., 'segment', 'amplitude')
13+
* @param {Object|null} customConfig - Parsed custom function configuration
1314
* @returns {string|null} Event name or null if not found
1415
*/
15-
function extractEventName(callNode, source) {
16+
function extractEventName(callNode, source, customConfig = null) {
1617
if (!callNode.args || callNode.args.length === 0) {
1718
// For struct literals, we need to check fields instead of args
1819
if (!callNode.fields || callNode.fields.length === 0) {
@@ -35,7 +36,7 @@ function extractEventName(callNode, source) {
3536
return extractSnowplowEventName(callNode);
3637

3738
case ANALYTICS_SOURCES.CUSTOM:
38-
return extractCustomEventName(callNode);
39+
return extractCustomEventName(callNode, customConfig);
3940
}
4041

4142
return null;
@@ -142,13 +143,15 @@ function extractSnowplowEventName(callNode) {
142143
* Extract custom event name
143144
* Pattern: customFunction("event_name", props)
144145
* @param {Object} callNode - AST node for custom tracking function call
146+
* @param {Object|null} customConfig - Custom configuration object
145147
* @returns {string|null} Event name or null if not found
146148
*/
147-
function extractCustomEventName(callNode) {
148-
if (callNode.args && callNode.args.length > 0) {
149-
return extractStringValue(callNode.args[0]);
150-
}
151-
return null;
149+
function extractCustomEventName(callNode, customConfig) {
150+
if (!callNode.args || callNode.args.length === 0) return null;
151+
const args = callNode.args;
152+
const eventIdx = customConfig?.eventIndex ?? 0;
153+
const argNode = args[eventIdx];
154+
return extractStringValue(argNode);
152155
}
153156

154157
module.exports = {

src/analyze/go/index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,20 @@ const { extractGoAST } = require('./goAstParser');
88
const { buildTypeContext } = require('./typeContext');
99
const { deduplicateEvents } = require('./eventDeduplicator');
1010
const { extractEventsFromBody } = require('./astTraversal');
11+
const { processGoFile } = require('./utils');
12+
const { parseCustomFunctionSignature } = require('../utils/customFunctionParser');
1113

1214
/**
1315
* Analyze a Go file and extract tracking events
1416
* @param {string} filePath - Path to the Go file to analyze
15-
* @param {string|null} customFunction - Name of custom tracking function to detect (optional)
17+
* @param {string|null} customFunctionSignature - Signature of custom tracking function to detect (optional)
1618
* @returns {Promise<Array>} Array of tracking events found in the file
1719
* @throws {Error} If the file cannot be read or parsed
1820
*/
19-
async function analyzeGoFile(filePath, customFunction) {
21+
async function analyzeGoFile(filePath, customFunctionSignature) {
2022
try {
23+
const customConfig = customFunctionSignature ? parseCustomFunctionSignature(customFunctionSignature) : null;
24+
2125
// Read the Go file
2226
const source = fs.readFileSync(filePath, 'utf8');
2327

@@ -37,7 +41,7 @@ async function analyzeGoFile(filePath, customFunction) {
3741
currentFunction = node.name;
3842
// Process the function body
3943
if (node.body) {
40-
extractEventsFromBody(node.body, events, filePath, currentFunction, customFunction, typeContext, currentFunction);
44+
extractEventsFromBody(node.body, events, filePath, currentFunction, customConfig, typeContext, currentFunction);
4145
}
4246
}
4347
}

src/analyze/go/propertyExtractor.js

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ const { extractStringValue, findStructLiteral, findStructField, extractFieldName
1212
* @param {string} source - Analytics source (e.g., 'segment', 'amplitude')
1313
* @param {Object} typeContext - Type information context for variable resolution
1414
* @param {string} currentFunction - Current function context for type lookups
15+
* @param {Object} customConfig - Custom configuration for property extraction
1516
* @returns {Object} Object containing extracted properties with their type information
1617
*/
17-
function extractProperties(callNode, source, typeContext, currentFunction) {
18+
function extractProperties(callNode, source, typeContext, currentFunction, customConfig) {
1819
const properties = {};
1920

2021
switch (source) {
@@ -36,7 +37,7 @@ function extractProperties(callNode, source, typeContext, currentFunction) {
3637
break;
3738

3839
case ANALYTICS_SOURCES.CUSTOM:
39-
extractCustomProperties(callNode, properties, typeContext, currentFunction);
40+
extractCustomProperties(callNode, properties, typeContext, currentFunction, customConfig);
4041
break;
4142
}
4243

@@ -270,10 +271,29 @@ function extractSnowplowProperties(callNode, properties, typeContext, currentFun
270271
* @param {Object} properties - Object to store extracted properties (modified in place)
271272
* @param {Object} typeContext - Type information context for variable resolution
272273
* @param {string} currentFunction - Current function context for type lookups
274+
* @param {Object} customConfig - Custom configuration for property extraction
273275
*/
274-
function extractCustomProperties(callNode, properties, typeContext, currentFunction) {
275-
if (callNode.args && callNode.args.length > 1) {
276-
extractPropertiesFromExpr(callNode.args[1], properties, typeContext, currentFunction);
276+
function extractCustomProperties(callNode, properties, typeContext, currentFunction, customConfig) {
277+
if (!callNode.args || callNode.args.length === 0) return;
278+
279+
const args = callNode.args;
280+
281+
const propsIdx = customConfig?.propertiesIndex ?? 1;
282+
283+
// Extract extra params first (those not event or properties)
284+
if (customConfig && Array.isArray(customConfig.extraParams)) {
285+
customConfig.extraParams.forEach(param => {
286+
const argNode = args[param.idx];
287+
if (argNode) {
288+
properties[param.name] = getPropertyInfo(argNode, typeContext, currentFunction);
289+
}
290+
});
291+
}
292+
293+
// Extract properties map/object (if provided)
294+
const propsArg = args[propsIdx];
295+
if (propsArg) {
296+
extractPropertiesFromExpr(propsArg, properties, typeContext, currentFunction);
277297
}
278298
}
279299

src/analyze/go/trackingExtractor.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,19 +13,19 @@ const { extractProperties } = require('./propertyExtractor');
1313
* @param {Object} callNode - AST node representing a function call or struct literal
1414
* @param {string} filePath - Path to the file being analyzed
1515
* @param {string} functionName - Name of the function containing this tracking call
16-
* @param {string|null} customFunction - Name of custom tracking function to detect
16+
* @param {Object|null} customConfig - Parsed custom function configuration (or null)
1717
* @param {Object} typeContext - Type information context for variable resolution
1818
* @param {string} currentFunction - Current function context for type lookups
1919
* @returns {Object|null} Tracking event object with eventName, source, properties, etc., or null if not a tracking call
2020
*/
21-
function extractTrackingEvent(callNode, filePath, functionName, customFunction, typeContext, currentFunction) {
22-
const source = detectSource(callNode, customFunction);
21+
function extractTrackingEvent(callNode, filePath, functionName, customConfig, typeContext, currentFunction) {
22+
const source = detectSource(callNode, customConfig ? customConfig.functionName : null);
2323
if (!source) return null;
2424

25-
const eventName = extractEventName(callNode, source);
25+
const eventName = extractEventName(callNode, source, customConfig);
2626
if (!eventName) return null;
2727

28-
const properties = extractProperties(callNode, source, typeContext, currentFunction);
28+
const properties = extractProperties(callNode, source, typeContext, currentFunction, customConfig);
2929

3030
// Get line number based on source type
3131
let line = 0;

0 commit comments

Comments
 (0)