@flisk/analyze-tracking 0.7.0 → 0.7.2
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/README.md +35 -61
- package/package.json +16 -3
- package/schema.json +4 -0
- package/src/analyze/analyzeGoFile.js +285 -89
- package/src/analyze/analyzeJsFile.js +20 -5
- package/src/analyze/analyzePythonFile.js +1 -0
- package/src/analyze/analyzeRubyFile.js +20 -10
- package/src/analyze/analyzeTsFile.js +138 -15
- package/src/analyze/helpers.js +462 -23
- package/src/analyze/index.js +0 -1
- package/src/analyze/pythonTrackingAnalyzer.py +149 -47
- package/.github/workflows/npm-publish.yml +0 -33
- package/.github/workflows/pr-check.yml +0 -17
- package/jest.config.js +0 -7
- package/tests/detectSource.test.js +0 -20
- package/tests/extractProperties.test.js +0 -109
- package/tests/findWrappingFunction.test.js +0 -30
|
@@ -130,26 +130,36 @@ class TrackingVisitor(ast.NodeVisitor):
|
|
|
130
130
|
if obj_id == 'analytics' and method_name == 'track':
|
|
131
131
|
return 'segment'
|
|
132
132
|
# Mixpanel
|
|
133
|
-
if obj_id == '
|
|
133
|
+
if obj_id == 'mp' and method_name == 'track':
|
|
134
134
|
return 'mixpanel'
|
|
135
|
-
# Amplitude
|
|
136
|
-
if obj_id == 'amplitude' and method_name == 'track':
|
|
137
|
-
return 'amplitude'
|
|
138
135
|
# Rudderstack
|
|
139
136
|
if obj_id == 'rudder_analytics' and method_name == 'track':
|
|
140
137
|
return 'rudderstack'
|
|
141
|
-
# mParticle
|
|
142
|
-
if obj_id == 'mParticle' and method_name == 'logEvent':
|
|
143
|
-
return 'mparticle'
|
|
144
138
|
# PostHog
|
|
145
139
|
if obj_id == 'posthog' and method_name == 'capture':
|
|
146
140
|
return 'posthog'
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
|
|
142
|
+
# Amplitude - tracker with BaseEvent
|
|
143
|
+
if method_name == 'track' and len(node.args) >= 1:
|
|
144
|
+
first_arg = node.args[0]
|
|
145
|
+
# Check if the first argument is a BaseEvent call
|
|
146
|
+
if isinstance(first_arg, ast.Call) and isinstance(first_arg.func, ast.Name):
|
|
147
|
+
if first_arg.func.id == 'BaseEvent':
|
|
148
|
+
return 'amplitude'
|
|
149
|
+
|
|
150
|
+
# Snowplow - tracker with StructuredEvent
|
|
151
|
+
if method_name == 'track' and len(node.args) >= 1:
|
|
152
|
+
first_arg = node.args[0]
|
|
153
|
+
# Check if the first argument is a StructuredEvent call
|
|
154
|
+
if isinstance(first_arg, ast.Call) and isinstance(first_arg.func, ast.Name):
|
|
155
|
+
if first_arg.func.id == 'StructuredEvent':
|
|
156
|
+
return 'snowplow'
|
|
157
|
+
# Also check if it's a variable that might be a StructuredEvent
|
|
158
|
+
elif isinstance(first_arg, ast.Name):
|
|
159
|
+
# Check if we can find the assignment of this variable
|
|
160
|
+
# For now, we'll assume any tracker.track() with a single argument is Snowplow
|
|
161
|
+
if obj_id == 'tracker':
|
|
162
|
+
return 'snowplow'
|
|
153
163
|
|
|
154
164
|
# Check for Snowplow struct event patterns
|
|
155
165
|
if isinstance(node.func, ast.Name) and node.func.id in ['trackStructEvent', 'buildStructEvent']:
|
|
@@ -169,14 +179,20 @@ class TrackingVisitor(ast.NodeVisitor):
|
|
|
169
179
|
|
|
170
180
|
def extract_event_name(self, node, source):
|
|
171
181
|
try:
|
|
172
|
-
if source in ['segment', '
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
182
|
+
if source in ['segment', 'rudderstack', 'mixpanel']:
|
|
183
|
+
# Segment/Rudderstack/Mixpanel format: library.track(user_id/distinct_id, 'event_name', {...})
|
|
184
|
+
if len(node.args) >= 2 and isinstance(node.args[1], ast.Constant):
|
|
185
|
+
return node.args[1].value
|
|
186
|
+
elif source == 'amplitude':
|
|
187
|
+
# Amplitude format: client.track(BaseEvent(event_type='event_name', ...))
|
|
188
|
+
if len(node.args) >= 1 and isinstance(node.args[0], ast.Call):
|
|
189
|
+
base_event_call = node.args[0]
|
|
190
|
+
# Look for event_type in keyword arguments
|
|
191
|
+
for keyword in base_event_call.keywords:
|
|
192
|
+
if keyword.arg == 'event_type' and isinstance(keyword.value, ast.Constant):
|
|
193
|
+
return keyword.value.value
|
|
194
|
+
elif source in ['custom']:
|
|
195
|
+
# Standard format: customFunction('event_name', {...})
|
|
180
196
|
if len(node.args) >= 1 and isinstance(node.args[0], ast.Constant):
|
|
181
197
|
return node.args[0].value
|
|
182
198
|
|
|
@@ -195,28 +211,28 @@ class TrackingVisitor(ast.NodeVisitor):
|
|
|
195
211
|
return node.args[1].value
|
|
196
212
|
|
|
197
213
|
elif source == 'snowplow':
|
|
198
|
-
# Snowplow
|
|
214
|
+
# Snowplow has multiple patterns
|
|
199
215
|
if len(node.args) >= 1:
|
|
200
|
-
|
|
201
|
-
props_node = None
|
|
202
|
-
|
|
203
|
-
# Direct trackStructEvent/buildStructEvent call
|
|
204
|
-
if isinstance(node.func, ast.Name) and node.func.id in ['trackStructEvent', 'buildStructEvent']:
|
|
205
|
-
if len(node.args) >= 1:
|
|
206
|
-
props_node = node.args[0]
|
|
216
|
+
first_arg = node.args[0]
|
|
207
217
|
|
|
208
|
-
#
|
|
209
|
-
|
|
210
|
-
if
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
# Pattern 1: tracker.track(StructuredEvent(...))
|
|
219
|
+
if isinstance(first_arg, ast.Call) and isinstance(first_arg.func, ast.Name):
|
|
220
|
+
if first_arg.func.id == 'StructuredEvent':
|
|
221
|
+
# Look for action in keyword arguments
|
|
222
|
+
for keyword in first_arg.keywords:
|
|
223
|
+
if keyword.arg == 'action' and isinstance(keyword.value, ast.Constant):
|
|
224
|
+
return keyword.value.value
|
|
225
|
+
|
|
226
|
+
# Pattern 2 & 3: For other Snowplow patterns
|
|
227
|
+
# For Snowplow struct events
|
|
228
|
+
if isinstance(node.func, ast.Name) and node.func.id in ['trackStructEvent', 'buildStructEvent']:
|
|
229
|
+
if len(node.args) >= 1:
|
|
230
|
+
props_node = node.args[0]
|
|
231
|
+
|
|
232
|
+
# snowplow('trackStructEvent', {...}) pattern
|
|
233
|
+
elif isinstance(node.func, ast.Name) and node.func.id == 'snowplow':
|
|
234
|
+
if len(node.args) >= 2:
|
|
235
|
+
props_node = node.args[1]
|
|
220
236
|
except:
|
|
221
237
|
pass
|
|
222
238
|
return None
|
|
@@ -227,9 +243,53 @@ class TrackingVisitor(ast.NodeVisitor):
|
|
|
227
243
|
props_node = None
|
|
228
244
|
|
|
229
245
|
# Get the properties object based on source
|
|
230
|
-
if source in ['segment', '
|
|
231
|
-
#
|
|
232
|
-
#
|
|
246
|
+
if source in ['segment', 'rudderstack']:
|
|
247
|
+
# Segment/Rudderstack format: analytics.track(user_id, 'event_name', {properties})
|
|
248
|
+
# Add user_id as a property if it's not null
|
|
249
|
+
if len(node.args) > 0:
|
|
250
|
+
user_id_node = node.args[0]
|
|
251
|
+
if isinstance(user_id_node, ast.Constant) and user_id_node.value is not None:
|
|
252
|
+
properties["user_id"] = {"type": "string"}
|
|
253
|
+
elif isinstance(user_id_node, ast.Name):
|
|
254
|
+
# It's a variable reference, include it as a property
|
|
255
|
+
properties["user_id"] = {"type": "string"}
|
|
256
|
+
|
|
257
|
+
if len(node.args) > 2:
|
|
258
|
+
props_node = node.args[2]
|
|
259
|
+
elif source == 'mixpanel':
|
|
260
|
+
# Mixpanel format: mp.track(distinct_id, 'event_name', {properties})
|
|
261
|
+
# Add distinct_id as a property if it's not null
|
|
262
|
+
if len(node.args) > 0:
|
|
263
|
+
distinct_id_node = node.args[0]
|
|
264
|
+
if isinstance(distinct_id_node, ast.Constant) and distinct_id_node.value is not None:
|
|
265
|
+
properties["distinct_id"] = {"type": "string"}
|
|
266
|
+
elif isinstance(distinct_id_node, ast.Name):
|
|
267
|
+
# It's a variable reference, include it as a property
|
|
268
|
+
properties["distinct_id"] = {"type": "string"}
|
|
269
|
+
|
|
270
|
+
if len(node.args) > 2:
|
|
271
|
+
props_node = node.args[2]
|
|
272
|
+
elif source == 'amplitude':
|
|
273
|
+
# Amplitude format: client.track(BaseEvent(event_type='...', user_id='...', event_properties={...}))
|
|
274
|
+
if len(node.args) >= 1 and isinstance(node.args[0], ast.Call):
|
|
275
|
+
base_event_call = node.args[0]
|
|
276
|
+
|
|
277
|
+
# First, check for user_id parameter
|
|
278
|
+
for keyword in base_event_call.keywords:
|
|
279
|
+
if keyword.arg == 'user_id':
|
|
280
|
+
if isinstance(keyword.value, ast.Constant) and keyword.value.value is not None:
|
|
281
|
+
properties["user_id"] = {"type": "string"}
|
|
282
|
+
elif isinstance(keyword.value, ast.Name):
|
|
283
|
+
# It's a variable reference, include it as a property
|
|
284
|
+
properties["user_id"] = {"type": "string"}
|
|
285
|
+
|
|
286
|
+
# Then look for event_properties
|
|
287
|
+
for keyword in base_event_call.keywords:
|
|
288
|
+
if keyword.arg == 'event_properties' and isinstance(keyword.value, ast.Dict):
|
|
289
|
+
props_node = keyword.value
|
|
290
|
+
break
|
|
291
|
+
elif source in ['custom']:
|
|
292
|
+
# Standard format: customFunction('event_name', {properties})
|
|
233
293
|
if len(node.args) > 1:
|
|
234
294
|
props_node = node.args[1]
|
|
235
295
|
|
|
@@ -272,6 +332,48 @@ class TrackingVisitor(ast.NodeVisitor):
|
|
|
272
332
|
elif isinstance(node.func, ast.Name) and node.func.id == 'snowplow':
|
|
273
333
|
if len(node.args) >= 2:
|
|
274
334
|
props_node = node.args[1]
|
|
335
|
+
|
|
336
|
+
# Pattern: tracker.track(StructuredEvent(...))
|
|
337
|
+
elif len(node.args) >= 1:
|
|
338
|
+
first_arg = node.args[0]
|
|
339
|
+
if isinstance(first_arg, ast.Call) and isinstance(first_arg.func, ast.Name):
|
|
340
|
+
if first_arg.func.id == 'StructuredEvent':
|
|
341
|
+
# Extract all keyword arguments from StructuredEvent except 'action'
|
|
342
|
+
for keyword in first_arg.keywords:
|
|
343
|
+
if keyword.arg and keyword.arg != 'action':
|
|
344
|
+
# Map property_ to property for consistency
|
|
345
|
+
prop_name = 'property' if keyword.arg == 'property_' else keyword.arg
|
|
346
|
+
|
|
347
|
+
if isinstance(keyword.value, ast.Constant):
|
|
348
|
+
value_type = self.get_value_type(keyword.value.value)
|
|
349
|
+
properties[prop_name] = {"type": value_type}
|
|
350
|
+
elif isinstance(keyword.value, ast.Name):
|
|
351
|
+
# Check if we know the type of this variable
|
|
352
|
+
var_name = keyword.value.id
|
|
353
|
+
if var_name in self.var_types:
|
|
354
|
+
var_type = self.var_types[var_name]
|
|
355
|
+
if isinstance(var_type, dict):
|
|
356
|
+
properties[prop_name] = var_type
|
|
357
|
+
else:
|
|
358
|
+
properties[prop_name] = {"type": var_type}
|
|
359
|
+
else:
|
|
360
|
+
properties[prop_name] = {"type": "any"}
|
|
361
|
+
elif isinstance(keyword.value, ast.Dict):
|
|
362
|
+
# Nested dictionary
|
|
363
|
+
nested_props = self.extract_nested_dict(keyword.value)
|
|
364
|
+
properties[prop_name] = {
|
|
365
|
+
"type": "object",
|
|
366
|
+
"properties": nested_props
|
|
367
|
+
}
|
|
368
|
+
elif isinstance(keyword.value, ast.List) or isinstance(keyword.value, ast.Tuple):
|
|
369
|
+
# Array/list/tuple
|
|
370
|
+
item_type = self.infer_sequence_item_type(keyword.value)
|
|
371
|
+
properties[prop_name] = {
|
|
372
|
+
"type": "array",
|
|
373
|
+
"items": item_type
|
|
374
|
+
}
|
|
375
|
+
# Don't process props_node if we've already extracted properties
|
|
376
|
+
props_node = None
|
|
275
377
|
|
|
276
378
|
# Extract properties from the dictionary
|
|
277
379
|
if props_node and isinstance(props_node, ast.Dict):
|
|
@@ -398,12 +500,12 @@ class TrackingVisitor(ast.NodeVisitor):
|
|
|
398
500
|
return nested_props
|
|
399
501
|
|
|
400
502
|
def get_value_type(self, value):
|
|
401
|
-
if isinstance(value,
|
|
503
|
+
if isinstance(value, bool):
|
|
504
|
+
return "boolean"
|
|
505
|
+
elif isinstance(value, str):
|
|
402
506
|
return "string"
|
|
403
507
|
elif isinstance(value, (int, float)):
|
|
404
508
|
return "number"
|
|
405
|
-
elif isinstance(value, bool):
|
|
406
|
-
return "boolean"
|
|
407
509
|
elif value is None:
|
|
408
510
|
return "null"
|
|
409
511
|
return "any"
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
name: Publish Package to NPM
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [published]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
test:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
steps:
|
|
11
|
-
- uses: actions/checkout@v4
|
|
12
|
-
- uses: actions/setup-node@v4
|
|
13
|
-
with:
|
|
14
|
-
node-version: 20
|
|
15
|
-
- run: npm ci
|
|
16
|
-
- run: npm test
|
|
17
|
-
|
|
18
|
-
publish:
|
|
19
|
-
needs: test
|
|
20
|
-
runs-on: ubuntu-latest
|
|
21
|
-
permissions:
|
|
22
|
-
contents: read
|
|
23
|
-
id-token: write
|
|
24
|
-
steps:
|
|
25
|
-
- uses: actions/checkout@v4
|
|
26
|
-
- uses: actions/setup-node@v4
|
|
27
|
-
with:
|
|
28
|
-
node-version: 20
|
|
29
|
-
registry-url: https://registry.npmjs.org/
|
|
30
|
-
- run: npm ci
|
|
31
|
-
- run: npm publish --provenance --access public
|
|
32
|
-
env:
|
|
33
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
name: PR Tests
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
pull_request_target:
|
|
5
|
-
branches:
|
|
6
|
-
- main
|
|
7
|
-
|
|
8
|
-
jobs:
|
|
9
|
-
test:
|
|
10
|
-
runs-on: ubuntu-latest
|
|
11
|
-
steps:
|
|
12
|
-
- uses: actions/checkout@v4
|
|
13
|
-
- uses: actions/setup-node@v4
|
|
14
|
-
with:
|
|
15
|
-
node-version: 20
|
|
16
|
-
- run: npm ci
|
|
17
|
-
- run: npm test
|
package/jest.config.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
detectSourceJs,
|
|
3
|
-
} = require('../src/analyze/helpers');
|
|
4
|
-
|
|
5
|
-
describe('detectSourceJs', () => {
|
|
6
|
-
it('should detect Google Analytics', () => {
|
|
7
|
-
const node = { callee: { type: 'Identifier', name: 'gtag' } };
|
|
8
|
-
expect(detectSourceJs(node)).toBe('googleanalytics');
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
it('should detect Segment', () => {
|
|
12
|
-
const node = { callee: { type: 'MemberExpression', object: { name: 'analytics' }, property: { name: 'track' } } };
|
|
13
|
-
expect(detectSourceJs(node)).toBe('segment');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should return unknown for unrecognized source', () => {
|
|
17
|
-
const node = { callee: { type: 'Identifier', name: 'unknownLib' } };
|
|
18
|
-
expect(detectSourceJs(node)).toBe('unknown');
|
|
19
|
-
});
|
|
20
|
-
});
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
const ts = require('typescript');
|
|
2
|
-
const {
|
|
3
|
-
extractJsProperties,
|
|
4
|
-
extractTsProperties,
|
|
5
|
-
} = require('../src/analyze/helpers');
|
|
6
|
-
|
|
7
|
-
describe('extractJsProperties', () => {
|
|
8
|
-
it('should extract simple properties', () => {
|
|
9
|
-
const node = {
|
|
10
|
-
properties: [
|
|
11
|
-
{ key: { name: 'userId' }, value: { value: '12345', type: 'Literal' } },
|
|
12
|
-
{ key: { name: 'plan' }, value: { value: 'Free', type: 'Literal' } },
|
|
13
|
-
],
|
|
14
|
-
};
|
|
15
|
-
const properties = extractJsProperties(node);
|
|
16
|
-
expect(properties).toEqual({
|
|
17
|
-
userId: { type: 'string' },
|
|
18
|
-
plan: { type: 'string' },
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it('should handle nested object properties', () => {
|
|
23
|
-
const node = {
|
|
24
|
-
properties: [
|
|
25
|
-
{
|
|
26
|
-
key: { name: 'address' },
|
|
27
|
-
value: {
|
|
28
|
-
type: 'ObjectExpression',
|
|
29
|
-
properties: [
|
|
30
|
-
{ key: { name: 'city' }, value: { value: 'San Francisco', type: 'Literal' } },
|
|
31
|
-
{ key: { name: 'state' }, value: { value: 'CA', type: 'Literal' } },
|
|
32
|
-
],
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
};
|
|
37
|
-
const properties = extractJsProperties(node);
|
|
38
|
-
expect(properties).toEqual({
|
|
39
|
-
address: {
|
|
40
|
-
type: 'object',
|
|
41
|
-
properties: {
|
|
42
|
-
city: { type: 'string' },
|
|
43
|
-
state: { type: 'string' },
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should handle properties with undefined type', () => {
|
|
50
|
-
const node = {
|
|
51
|
-
properties: [{ key: { name: 'undefinedProp' }, value: { value: undefined, type: 'Literal' } }],
|
|
52
|
-
};
|
|
53
|
-
const properties = extractJsProperties(node);
|
|
54
|
-
expect(properties).toEqual({
|
|
55
|
-
undefinedProp: { type: 'any' },
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
describe('extractTsProperties', () => {
|
|
61
|
-
it('should extract properties from TypeScript object', () => {
|
|
62
|
-
const node = {
|
|
63
|
-
properties: [
|
|
64
|
-
{ name: { text: 'userId' }, initializer: { text: '12345', type: 'Literal' } },
|
|
65
|
-
{ name: { text: 'plan' }, initializer: { text: 'Free', type: 'Literal' } },
|
|
66
|
-
],
|
|
67
|
-
};
|
|
68
|
-
const checker = {
|
|
69
|
-
getTypeAtLocation: jest.fn().mockReturnValue({}),
|
|
70
|
-
typeToString: jest.fn().mockReturnValue('string'),
|
|
71
|
-
};
|
|
72
|
-
const properties = extractTsProperties(checker, node);
|
|
73
|
-
expect(properties).toEqual({
|
|
74
|
-
userId: { type: 'string' },
|
|
75
|
-
plan: { type: 'string' },
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('should handle nested object properties in TypeScript', () => {
|
|
80
|
-
const node = {
|
|
81
|
-
properties: [
|
|
82
|
-
{
|
|
83
|
-
name: { text: 'address' },
|
|
84
|
-
initializer: {
|
|
85
|
-
kind: ts.SyntaxKind.ObjectLiteralExpression,
|
|
86
|
-
properties: [
|
|
87
|
-
{ name: { text: 'city' }, initializer: { text: 'San Francisco', type: 'Literal' } },
|
|
88
|
-
{ name: { text: 'state' }, initializer: { text: 'CA', type: 'Literal' } },
|
|
89
|
-
],
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
],
|
|
93
|
-
};
|
|
94
|
-
const checker = {
|
|
95
|
-
getTypeAtLocation: jest.fn().mockReturnValue({}),
|
|
96
|
-
typeToString: jest.fn().mockReturnValue('string'),
|
|
97
|
-
};
|
|
98
|
-
const properties = extractTsProperties(checker, node);
|
|
99
|
-
expect(properties).toEqual({
|
|
100
|
-
address: {
|
|
101
|
-
type: 'object',
|
|
102
|
-
properties: {
|
|
103
|
-
city: { type: 'string' },
|
|
104
|
-
state: { type: 'string' },
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
});
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
const ts = require('typescript');
|
|
2
|
-
const {
|
|
3
|
-
findWrappingFunctionJs,
|
|
4
|
-
} = require('../src/analyze/helpers');
|
|
5
|
-
|
|
6
|
-
describe('findWrappingFunctionJs', () => {
|
|
7
|
-
it('should return function name for arrow function assigned to variable', () => {
|
|
8
|
-
const node = { type: 'ArrowFunctionExpression' };
|
|
9
|
-
const ancestors = [
|
|
10
|
-
{ type: 'Program' },
|
|
11
|
-
{ type: 'VariableDeclarator', init: node, id: { name: 'checkout' } },
|
|
12
|
-
];
|
|
13
|
-
expect(findWrappingFunctionJs(node, ancestors)).toBe('checkout');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('should return function name for function expression assigned to variable', () => {
|
|
17
|
-
const node = { type: 'FunctionExpression' };
|
|
18
|
-
const ancestors = [
|
|
19
|
-
{ type: 'Program' },
|
|
20
|
-
{ type: 'VariableDeclarator', init: node, id: { name: 'myFunc' } },
|
|
21
|
-
];
|
|
22
|
-
expect(findWrappingFunctionJs(node, ancestors)).toBe('myFunc');
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should return "global" if no wrapping function is found', () => {
|
|
26
|
-
const node = {};
|
|
27
|
-
const ancestors = [{ type: 'Program' }];
|
|
28
|
-
expect(findWrappingFunctionJs(node, ancestors)).toBe('global');
|
|
29
|
-
});
|
|
30
|
-
});
|