Skip to content

Commit afdc5ae

Browse files
committed
tests for custom module/class methods
1 parent 8e0c7ad commit afdc5ae

File tree

8 files changed

+105
-2
lines changed

8 files changed

+105
-2
lines changed

src/analyze/javascript/detectors/analytics-source.js

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,61 @@ function detectAnalyticsSource(node, customFunction) {
4343
* @returns {boolean}
4444
*/
4545
function isCustomFunction(node, customFunction) {
46-
return node.callee.type === NODE_TYPES.IDENTIFIER &&
47-
node.callee.name === customFunction;
46+
if (!customFunction) return false;
47+
48+
// Support dot-separated names like "CustomModule.track"
49+
const parts = customFunction.split('.');
50+
51+
// Simple identifier (no dot)
52+
if (parts.length === 1) {
53+
return node.callee.type === NODE_TYPES.IDENTIFIER && node.callee.name === customFunction;
54+
}
55+
56+
// For dot-separated names, the callee should be a MemberExpression chain.
57+
if (node.callee.type !== NODE_TYPES.MEMBER_EXPRESSION) {
58+
return false;
59+
}
60+
61+
return matchesMemberChain(node.callee, parts);
62+
}
63+
64+
/**
65+
* Recursively verifies that a MemberExpression chain matches the expected parts.
66+
* Example: parts ["CustomModule", "track"] should match `CustomModule.track()`.
67+
* @param {Object} memberExpr - AST MemberExpression node
68+
* @param {string[]} parts - Expected name segments (left -> right)
69+
* @returns {boolean}
70+
*/
71+
function matchesMemberChain(memberExpr, parts) {
72+
let currentNode = memberExpr;
73+
let idx = parts.length - 1; // start from the rightmost property
74+
75+
while (currentNode && idx >= 0) {
76+
const expectedPart = parts[idx];
77+
78+
// property should match current expectedPart
79+
if (currentNode.type === NODE_TYPES.MEMBER_EXPRESSION) {
80+
// Ensure property is Identifier and matches
81+
if (
82+
currentNode.property.type !== NODE_TYPES.IDENTIFIER ||
83+
currentNode.property.name !== expectedPart
84+
) {
85+
return false;
86+
}
87+
88+
// Move to the object of the MemberExpression
89+
currentNode = currentNode.object;
90+
idx -= 1;
91+
} else if (currentNode.type === NODE_TYPES.IDENTIFIER) {
92+
// We reached the leftmost Identifier; it should match the first part
93+
return idx === 0 && currentNode.name === expectedPart;
94+
} else {
95+
// Unexpected node type (e.g., ThisExpression, CallExpression, etc.)
96+
return false;
97+
}
98+
}
99+
100+
return false;
48101
}
49102

50103
/**

src/analyze/python/pythonTrackingAnalyzer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,12 @@ def _detect_method_call_source(self, node: ast.Call) -> Optional[str]:
315315
if method_name == 'track' and self._is_snowplow_tracker_call(node):
316316
return 'snowplow'
317317

318+
# Handle dot-separated custom function names like CustomModule.track
319+
if self._custom_fn_name and '.' in self._custom_fn_name:
320+
full_name = f"{obj_id}.{method_name}"
321+
if full_name == self._custom_fn_name:
322+
return 'custom'
323+
318324
return None
319325

320326
def _detect_function_call_source(self, node: ast.Call) -> Optional[str]:

tests/analyzeJavaScript.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ test.describe('analyzeJsFile', () => {
325325
{ sig: 'customTrackFunction2(userId, EVENT_NAME, PROPERTIES)', event: 'custom_event2' },
326326
{ sig: 'customTrackFunction3(EVENT_NAME, PROPERTIES, userEmail)', event: 'custom_event3' },
327327
{ sig: 'customTrackFunction4(userId, EVENT_NAME, userAddress, PROPERTIES, userEmail)', event: 'custom_event4' },
328+
{ sig: 'CustomModule.track(userId, EVENT_NAME, PROPERTIES)', event: 'custom_module_event' },
328329
];
329330

330331
variants.forEach(({ sig, event }) => {

tests/analyzePython.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ def customTrackFunction(user_id: str, event_name: str, params: Dict[str, Any]) -
321321
{ sig: 'customTrackFunction2(userId, EVENT_NAME, PROPERTIES)', event: 'custom_event2' },
322322
{ sig: 'customTrackFunction3(EVENT_NAME, PROPERTIES, userEmail)', event: 'custom_event3' },
323323
{ sig: 'customTrackFunction4(userId, EVENT_NAME, userAddress, PROPERTIES, userEmail)', event: 'custom_event4' },
324+
{ sig: 'CustomModule.track(userId, EVENT_NAME, PROPERTIES)', event: 'custom_module_event' },
324325
];
325326

326327
for (const { sig, event } of variants) {

tests/analyzeTypeScript.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,7 @@ test.describe('analyzeTsFile', () => {
857857
{ sig: 'customTrackFunction2(userId, EVENT_NAME, PROPERTIES)', event: 'custom_event2' },
858858
{ sig: 'customTrackFunction3(EVENT_NAME, PROPERTIES, userEmail)', event: 'custom_event3' },
859859
{ sig: 'customTrackFunction4(userId, EVENT_NAME, userAddress, PROPERTIES, userEmail)', event: 'custom_event4' },
860+
{ sig: 'CustomModule.track(userId, EVENT_NAME, PROPERTIES)', event: 'custom_module_event' },
860861
];
861862

862863
variants.forEach(({ sig, event }) => {

tests/fixtures/javascript/main.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,18 @@ customTrackFunction1('custom_event1', { foo: 'bar' });
164164
customTrackFunction2('user101', 'custom_event2', { foo: 'bar' });
165165
customTrackFunction3('custom_event3', { foo: 'bar' }, '[email protected]');
166166
customTrackFunction4('user202', 'custom_event4', { city: 'San Francisco' }, { foo: 'bar' }, '[email protected]');
167+
168+
// -----------------------------------------------------------------------------
169+
// Dot-separated custom tracking function (module-style)
170+
// -----------------------------------------------------------------------------
171+
172+
const CustomModule = {
173+
track(userId, eventName, params) {
174+
console.log('CustomModule.track', userId, eventName, params);
175+
}
176+
};
177+
178+
CustomModule.track('user321', 'custom_module_event', {
179+
order_id: 'order123',
180+
foo: 'bar'
181+
});

tests/fixtures/python/main.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,17 @@ def main() -> None:
164164
customTrackFunction3("custom_event3", {"foo": "bar"}, "[email protected]")
165165
customTrackFunction4("user202", "custom_event4", {"city": "San Francisco"}, {"foo": "bar"}, "[email protected]")
166166

167+
# Dot-separated custom tracking function (module-style)
168+
class CustomModule:
169+
@staticmethod
170+
def track(user_id: str, event_name: str, params: Dict[str, Any]) -> None: # type: ignore[return-value]
171+
print("CustomModule.track", user_id, event_name, params)
172+
173+
CustomModule.track("user444", "custom_module_event", {
174+
"order_id": "order_xyz",
175+
"foo": "bar"
176+
})
177+
167178
# Stub variant definitions to satisfy linters (not executed)
168179

169180
def customTrackFunction0(event_name: str, params: Dict[str, Any]) -> None: ...

tests/fixtures/typescript/main.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,18 @@ customTrackFunction1('custom_event1', { foo: 'bar' });
308308
customTrackFunction2('user101', 'custom_event2', { foo: 'bar' });
309309
customTrackFunction3('custom_event3', { foo: 'bar' }, '[email protected]');
310310
customTrackFunction4('user202', 'custom_event4', { city: 'San Francisco' }, { foo: 'bar' }, '[email protected]');
311+
312+
// -----------------------------------------------------------------------------
313+
// Dot-separated custom tracking function (module-style)
314+
// -----------------------------------------------------------------------------
315+
316+
namespace CustomModule {
317+
export function track(userId: string, eventName: string, params: Record<string, any>): void {
318+
console.log('CustomModule.track', userId, eventName, params);
319+
}
320+
}
321+
322+
CustomModule.track('user333', 'custom_module_event', {
323+
order_id: 'order_xyz',
324+
foo: 'bar'
325+
});

0 commit comments

Comments
 (0)