Skip to content

Commit 7f84b15

Browse files
committed
handle ts custom functions with no properties
1 parent 1ab9cb4 commit 7f84b15

File tree

8 files changed

+92
-18
lines changed

8 files changed

+92
-18
lines changed

src/analyze/javascript/extractors/event-extractor.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,18 @@ function extractCustomEvent(node, constantMap, customConfig) {
161161
function processEventData(eventData, source, filePath, line, functionName, customConfig) {
162162
const { eventName, propertiesNode } = eventData;
163163

164-
if (!eventName || !propertiesNode || propertiesNode.type !== NODE_TYPES.OBJECT_EXPRESSION) {
164+
// Must at least have an event name – properties are optional.
165+
if (!eventName) {
165166
return null;
166167
}
167168

168-
let properties = extractProperties(propertiesNode);
169+
// Default to empty properties when none are supplied.
170+
let properties = {};
171+
172+
// Only attempt extraction when we have a literal object expression.
173+
if (propertiesNode && propertiesNode.type === NODE_TYPES.OBJECT_EXPRESSION) {
174+
properties = extractProperties(propertiesNode);
175+
}
169176

170177
// Handle custom extra params
171178
if (source === 'custom' && customConfig && eventData.extraArgs) {

src/analyze/typescript/extractors/event-extractor.js

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -186,26 +186,25 @@ function extractDefaultEvent(node, checker, sourceFile) {
186186
function processEventData(eventData, source, filePath, line, functionName, checker, sourceFile, customConfig) {
187187
const { eventName, propertiesNode } = eventData;
188188

189-
if (!eventName || !propertiesNode) {
189+
// Require an event name – properties are optional.
190+
if (!eventName) {
190191
return null;
191192
}
192193

193-
let properties = null;
194+
let properties = {};
194195

195-
// Check if properties is an object literal
196-
if (ts.isObjectLiteralExpression(propertiesNode)) {
197-
properties = extractProperties(checker, propertiesNode);
198-
}
199-
// Check if properties is an identifier (variable reference)
200-
else if (ts.isIdentifier(propertiesNode)) {
201-
const resolvedNode = resolveIdentifierToInitializer(checker, propertiesNode, sourceFile);
202-
if (resolvedNode && ts.isObjectLiteralExpression(resolvedNode)) {
203-
properties = extractProperties(checker, resolvedNode);
196+
if (propertiesNode) {
197+
// Check if properties is an object literal
198+
if (ts.isObjectLiteralExpression(propertiesNode)) {
199+
properties = extractProperties(checker, propertiesNode);
200+
}
201+
// Check if properties is an identifier (variable reference)
202+
else if (ts.isIdentifier(propertiesNode)) {
203+
const resolvedNode = resolveIdentifierToInitializer(checker, propertiesNode, sourceFile);
204+
if (resolvedNode && ts.isObjectLiteralExpression(resolvedNode)) {
205+
properties = extractProperties(checker, resolvedNode);
206+
}
204207
}
205-
}
206-
207-
if (!properties) {
208-
return null;
209208
}
210209

211210
// Special handling for Snowplow: remove 'action' from properties

tests/analyzeJavaScript.test.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,20 @@ test.describe('analyzeJsFile', () => {
393393
const builtInProvidersCount = events.filter(e => e.source !== 'custom').length;
394394
assert.ok(builtInProvidersCount >= 10, 'Should still include built-in events');
395395
});
396+
397+
test('should detect events with no properties for custom function', () => {
398+
const eventOnlyFile = path.join(fixturesDir, 'javascript', 'event-only.js');
399+
const customFunctionSignatures = [parseCustomFunctionSignature('trackUserEvent(EVENT_NAME)')];
400+
const events = analyzeJsFile(eventOnlyFile, customFunctionSignatures);
401+
402+
assert.strictEqual(events.length, 2);
403+
404+
const literalEvent = events.find(e => e.eventName === 'ViewedEligibilityResults');
405+
assert.ok(literalEvent);
406+
assert.deepStrictEqual(literalEvent.properties, {});
407+
408+
const constantEvent = events.find(e => e.eventName === 'ViewedPostShipDashboard');
409+
assert.ok(constantEvent);
410+
assert.deepStrictEqual(constantEvent.properties, {});
411+
});
396412
});

tests/analyzeTypeScript.test.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ test.describe('analyzeTsFile', () => {
3737
// Sort events by line number for consistent ordering
3838
events.sort((a, b) => a.line - b.line);
3939

40-
assert.strictEqual(events.length, 19);
40+
assert.strictEqual(events.length, 20);
4141

4242
// Test Google Analytics event
4343
const gaEvent = events.find(e => e.eventName === 'order_completed' && e.source === 'googleanalytics');
@@ -986,4 +986,21 @@ test.describe('analyzeTsFile', () => {
986986
foo: { type: 'string' }
987987
});
988988
});
989+
990+
test('should detect events with no properties for custom function', () => {
991+
const eventOnlyFile = path.join(fixturesDir, 'typescript', 'event-only.ts');
992+
const program = createProgram(eventOnlyFile);
993+
const customFunctionSignatures = [parseCustomFunctionSignature('trackUserEvent(EVENT_NAME)')];
994+
const events = analyzeTsFile(eventOnlyFile, program, customFunctionSignatures);
995+
996+
assert.strictEqual(events.length, 2);
997+
998+
const literalEvent = events.find(e => e.eventName === 'ViewedEligibilityResults');
999+
assert.ok(literalEvent);
1000+
assert.deepStrictEqual(literalEvent.properties, {});
1001+
1002+
const constantEvent = events.find(e => e.eventName === 'ViewedPostShipDashboard');
1003+
assert.ok(constantEvent);
1004+
assert.deepStrictEqual(constantEvent.properties, {});
1005+
});
9891006
});
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
function trackUserEvent() {
2+
// stub implementation for tests
3+
}
4+
5+
// Direct string literal event name
6+
trackUserEvent('ViewedEligibilityResults');
7+
8+
// Event name referenced via constant map
9+
const TELEMETRY_EVENTS = {
10+
VIEWED_POST_SHIP_DASHBOARD: 'ViewedPostShipDashboard',
11+
};
12+
13+
trackUserEvent(TELEMETRY_EVENTS.VIEWED_POST_SHIP_DASHBOARD);

tests/fixtures/tracking-schema-all.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,10 @@ events:
11211121
line: 361
11221122
function: global
11231123
destination: custom
1124+
- path: typescript/main.ts
1125+
line: 417
1126+
function: trackFailedPayment
1127+
destination: custom
11241128
properties:
11251129
containerSection:
11261130
type: string
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Stub implementation for the test – matches the custom signature under test
2+
const trackUserEvent = (..._args: any[]): void => {
3+
/* no-op */
4+
};
5+
6+
// Direct string literal event name
7+
trackUserEvent('ViewedEligibilityResults');
8+
9+
// Event name referenced via constant map
10+
const TELEMETRY_EVENTS = {
11+
VIEWED_POST_SHIP_DASHBOARD: 'ViewedPostShipDashboard' as const,
12+
};
13+
14+
trackUserEvent(TELEMETRY_EVENTS.VIEWED_POST_SHIP_DASHBOARD);

tests/fixtures/typescript/tracking-schema-typescript.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ events:
441441
line: 361
442442
function: global
443443
destination: custom
444+
- path: main.ts
445+
line: 417
446+
function: trackFailedPayment
447+
destination: custom
444448
properties:
445449
containerSection:
446450
type: string

0 commit comments

Comments
 (0)