@flisk/analyze-tracking 0.7.5 → 0.7.6
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/src/analyze/typescript/detectors/analytics-source.js +4 -1
- package/src/analyze/typescript/extractors/event-extractor.js +111 -6
- package/src/analyze/typescript/extractors/property-extractor.js +53 -1
- package/src/analyze/typescript/utils/function-finder.js +11 -0
- package/src/analyze/typescript/utils/type-resolver.js +1 -1
package/package.json
CHANGED
|
@@ -48,7 +48,10 @@ function isCustomFunction(node, customFunction) {
|
|
|
48
48
|
ts.isPropertyAccessExpression(node.expression) ||
|
|
49
49
|
ts.isCallExpression(node.expression) || // For chained calls like getTracker().track()
|
|
50
50
|
ts.isElementAccessExpression(node.expression) || // For array/object access like trackers['analytics'].track()
|
|
51
|
-
(
|
|
51
|
+
(node.expression?.expression &&
|
|
52
|
+
ts.isPropertyAccessExpression(node.expression.expression) &&
|
|
53
|
+
node.expression.expression.expression &&
|
|
54
|
+
ts.isThisExpression(node.expression.expression.expression)); // For class methods like this.analytics.track()
|
|
52
55
|
|
|
53
56
|
return canBeCustomFunction && node.expression.getText() === customFunction;
|
|
54
57
|
}
|
|
@@ -50,7 +50,7 @@ function extractGoogleAnalyticsEvent(node, checker, sourceFile) {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
// gtag('event', 'event_name', { properties })
|
|
53
|
-
const eventName = getStringValue(node.arguments[1]);
|
|
53
|
+
const eventName = getStringValue(node.arguments[1], checker, sourceFile);
|
|
54
54
|
const propertiesNode = node.arguments[2];
|
|
55
55
|
|
|
56
56
|
return { eventName, propertiesNode };
|
|
@@ -79,7 +79,7 @@ function extractSnowplowEvent(node, checker, sourceFile) {
|
|
|
79
79
|
const structEventArg = firstArg.arguments[0];
|
|
80
80
|
if (ts.isObjectLiteralExpression(structEventArg)) {
|
|
81
81
|
const actionProperty = findPropertyByKey(structEventArg, 'action');
|
|
82
|
-
const eventName = actionProperty ? getStringValue(actionProperty.initializer) : null;
|
|
82
|
+
const eventName = actionProperty ? getStringValue(actionProperty.initializer, checker, sourceFile) : null;
|
|
83
83
|
return { eventName, propertiesNode: structEventArg };
|
|
84
84
|
}
|
|
85
85
|
}
|
|
@@ -93,7 +93,7 @@ function extractSnowplowEvent(node, checker, sourceFile) {
|
|
|
93
93
|
const structEventArg = resolvedNode.arguments[0];
|
|
94
94
|
if (ts.isObjectLiteralExpression(structEventArg)) {
|
|
95
95
|
const actionProperty = findPropertyByKey(structEventArg, 'action');
|
|
96
|
-
const eventName = actionProperty ? getStringValue(actionProperty.initializer) : null;
|
|
96
|
+
const eventName = actionProperty ? getStringValue(actionProperty.initializer, checker, sourceFile) : null;
|
|
97
97
|
return { eventName, propertiesNode: structEventArg };
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -115,7 +115,7 @@ function extractMparticleEvent(node, checker, sourceFile) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
// mParticle.logEvent('event_name', mParticle.EventType.Navigation, { properties })
|
|
118
|
-
const eventName = getStringValue(node.arguments[0]);
|
|
118
|
+
const eventName = getStringValue(node.arguments[0], checker, sourceFile);
|
|
119
119
|
const propertiesNode = node.arguments[2];
|
|
120
120
|
|
|
121
121
|
return { eventName, propertiesNode };
|
|
@@ -134,7 +134,7 @@ function extractDefaultEvent(node, checker, sourceFile) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// provider.track('event_name', { properties })
|
|
137
|
-
const eventName = getStringValue(node.arguments[0]);
|
|
137
|
+
const eventName = getStringValue(node.arguments[0], checker, sourceFile);
|
|
138
138
|
const propertiesNode = node.arguments[1];
|
|
139
139
|
|
|
140
140
|
return { eventName, propertiesNode };
|
|
@@ -197,16 +197,121 @@ function processEventData(eventData, source, filePath, line, functionName, check
|
|
|
197
197
|
/**
|
|
198
198
|
* Gets string value from a TypeScript AST node
|
|
199
199
|
* @param {Object} node - TypeScript AST node
|
|
200
|
+
* @param {Object} checker - TypeScript type checker
|
|
201
|
+
* @param {Object} sourceFile - TypeScript source file
|
|
200
202
|
* @returns {string|null} String value or null
|
|
201
203
|
*/
|
|
202
|
-
function getStringValue(node) {
|
|
204
|
+
function getStringValue(node, checker, sourceFile) {
|
|
203
205
|
if (!node) return null;
|
|
206
|
+
|
|
207
|
+
// Handle string literals (existing behavior)
|
|
204
208
|
if (ts.isStringLiteral(node)) {
|
|
205
209
|
return node.text;
|
|
206
210
|
}
|
|
211
|
+
|
|
212
|
+
// Handle property access expressions like TRACKING_EVENTS.ECOMMERCE_PURCHASE
|
|
213
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
214
|
+
return resolvePropertyAccessToString(node, checker, sourceFile);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Handle identifiers that might reference constants
|
|
218
|
+
if (ts.isIdentifier(node)) {
|
|
219
|
+
return resolveIdentifierToString(node, checker, sourceFile);
|
|
220
|
+
}
|
|
221
|
+
|
|
207
222
|
return null;
|
|
208
223
|
}
|
|
209
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Resolves a property access expression to its string value
|
|
227
|
+
* @param {Object} node - PropertyAccessExpression node
|
|
228
|
+
* @param {Object} checker - TypeScript type checker
|
|
229
|
+
* @param {Object} sourceFile - TypeScript source file
|
|
230
|
+
* @returns {string|null} String value or null
|
|
231
|
+
*/
|
|
232
|
+
function resolvePropertyAccessToString(node, checker, sourceFile) {
|
|
233
|
+
try {
|
|
234
|
+
// Get the symbol for the property access
|
|
235
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
236
|
+
if (!symbol || !symbol.valueDeclaration) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check if it's a property assignment with a string initializer
|
|
241
|
+
if (ts.isPropertyAssignment(symbol.valueDeclaration) &&
|
|
242
|
+
symbol.valueDeclaration.initializer &&
|
|
243
|
+
ts.isStringLiteral(symbol.valueDeclaration.initializer)) {
|
|
244
|
+
return symbol.valueDeclaration.initializer.text;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Check if it's a variable declaration property
|
|
248
|
+
if (ts.isPropertySignature(symbol.valueDeclaration) ||
|
|
249
|
+
ts.isMethodSignature(symbol.valueDeclaration)) {
|
|
250
|
+
// Try to get the type and see if it's a string literal type
|
|
251
|
+
const type = checker.getTypeAtLocation(node);
|
|
252
|
+
if (type.isStringLiteral && type.isStringLiteral()) {
|
|
253
|
+
return type.value;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return null;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Resolves an identifier to its string value
|
|
265
|
+
* @param {Object} node - Identifier node
|
|
266
|
+
* @param {Object} checker - TypeScript type checker
|
|
267
|
+
* @param {Object} sourceFile - TypeScript source file
|
|
268
|
+
* @returns {string|null} String value or null
|
|
269
|
+
*/
|
|
270
|
+
function resolveIdentifierToString(node, checker, sourceFile) {
|
|
271
|
+
try {
|
|
272
|
+
const symbol = checker.getSymbolAtLocation(node);
|
|
273
|
+
if (!symbol) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// First try to resolve through value declaration
|
|
278
|
+
if (symbol.valueDeclaration) {
|
|
279
|
+
const declaration = symbol.valueDeclaration;
|
|
280
|
+
|
|
281
|
+
// Handle variable declarations with string literal initializers
|
|
282
|
+
if (ts.isVariableDeclaration(declaration) &&
|
|
283
|
+
declaration.initializer &&
|
|
284
|
+
ts.isStringLiteral(declaration.initializer)) {
|
|
285
|
+
return declaration.initializer.text;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Handle const declarations with object literals containing string properties
|
|
289
|
+
if (ts.isVariableDeclaration(declaration) &&
|
|
290
|
+
declaration.initializer &&
|
|
291
|
+
ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
292
|
+
// This case is handled by property access resolution
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// If value declaration doesn't exist or doesn't help, try type resolution
|
|
298
|
+
// This handles imported constants that are resolved through TypeScript's type system
|
|
299
|
+
const type = checker.getTypeOfSymbolAtLocation(symbol, node);
|
|
300
|
+
if (type && type.isStringLiteral && typeof type.isStringLiteral === 'function' && type.isStringLiteral()) {
|
|
301
|
+
return type.value;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Alternative approach for string literal types (different TypeScript versions)
|
|
305
|
+
if (type && type.flags && (type.flags & ts.TypeFlags.StringLiteral)) {
|
|
306
|
+
return type.value;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return null;
|
|
310
|
+
} catch (error) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
210
315
|
/**
|
|
211
316
|
* Finds a property by key in an ObjectLiteralExpression
|
|
212
317
|
* @param {Object} objectNode - ObjectLiteralExpression node
|
|
@@ -34,6 +34,13 @@ function extractProperties(checker, node) {
|
|
|
34
34
|
const properties = {};
|
|
35
35
|
|
|
36
36
|
for (const prop of node.properties) {
|
|
37
|
+
// Handle spread assignments like {...object}
|
|
38
|
+
if (ts.isSpreadAssignment(prop)) {
|
|
39
|
+
const spreadProperties = extractSpreadProperties(checker, prop);
|
|
40
|
+
Object.assign(properties, spreadProperties);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
const key = getPropertyKey(prop);
|
|
38
45
|
if (!key) continue;
|
|
39
46
|
|
|
@@ -350,7 +357,7 @@ function resolveTypeSchema(checker, typeString) {
|
|
|
350
357
|
* @returns {string|null} Literal type or null
|
|
351
358
|
*/
|
|
352
359
|
function getLiteralType(node) {
|
|
353
|
-
if (!node) return null;
|
|
360
|
+
if (!node || typeof node.kind === 'undefined') return null;
|
|
354
361
|
if (ts.isStringLiteral(node)) return 'string';
|
|
355
362
|
if (ts.isNumericLiteral(node)) return 'number';
|
|
356
363
|
if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return 'boolean';
|
|
@@ -371,6 +378,51 @@ function isArrayType(typeString) {
|
|
|
371
378
|
typeString.startsWith('readonly ');
|
|
372
379
|
}
|
|
373
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Extracts properties from a spread assignment
|
|
383
|
+
* @param {Object} checker - TypeScript type checker
|
|
384
|
+
* @param {Object} spreadNode - SpreadAssignment node
|
|
385
|
+
* @returns {Object.<string, PropertySchema>}
|
|
386
|
+
*/
|
|
387
|
+
function extractSpreadProperties(checker, spreadNode) {
|
|
388
|
+
if (!spreadNode.expression) {
|
|
389
|
+
return {};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// If the spread is an identifier, resolve it to its declaration
|
|
393
|
+
if (ts.isIdentifier(spreadNode.expression)) {
|
|
394
|
+
const symbol = checker.getSymbolAtLocation(spreadNode.expression);
|
|
395
|
+
if (symbol && symbol.declarations && symbol.declarations.length > 0) {
|
|
396
|
+
const declaration = symbol.declarations[0];
|
|
397
|
+
|
|
398
|
+
// If it's a variable declaration with an object literal initializer
|
|
399
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
400
|
+
if (ts.isObjectLiteralExpression(declaration.initializer)) {
|
|
401
|
+
// Extract properties directly from the object literal
|
|
402
|
+
return extractProperties(checker, declaration.initializer);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Fallback to the original identifier schema extraction
|
|
408
|
+
const identifierSchema = extractIdentifierSchema(checker, spreadNode.expression);
|
|
409
|
+
return identifierSchema.properties || {};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// If the spread is an object literal, extract its properties
|
|
413
|
+
if (ts.isObjectLiteralExpression(spreadNode.expression)) {
|
|
414
|
+
return extractProperties(checker, spreadNode.expression);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// For other expressions, try to get the type and extract properties from it
|
|
418
|
+
try {
|
|
419
|
+
const spreadType = checker.getTypeAtLocation(spreadNode.expression);
|
|
420
|
+
return extractInterfaceProperties(checker, spreadType);
|
|
421
|
+
} catch (error) {
|
|
422
|
+
return {};
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
374
426
|
/**
|
|
375
427
|
* Extracts properties from a TypeScript interface or type
|
|
376
428
|
* @param {Object} checker - TypeScript type checker
|
|
@@ -110,6 +110,16 @@ function findParentFunctionName(node) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// Property declaration in class: myFunc = () => {}
|
|
114
|
+
if (ts.isPropertyDeclaration(parent) && parent.name) {
|
|
115
|
+
if (ts.isIdentifier(parent.name)) {
|
|
116
|
+
return parent.name.escapedText;
|
|
117
|
+
}
|
|
118
|
+
if (ts.isStringLiteral(parent.name)) {
|
|
119
|
+
return parent.name.text;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
113
123
|
// Method property in object literal: { myFunc() {} }
|
|
114
124
|
if (ts.isMethodDeclaration(parent) && parent.name) {
|
|
115
125
|
return parent.name.escapedText;
|
|
@@ -117,6 +127,7 @@ function findParentFunctionName(node) {
|
|
|
117
127
|
|
|
118
128
|
// Binary expression assignment: obj.myFunc = () => {}
|
|
119
129
|
if (ts.isBinaryExpression(parent) &&
|
|
130
|
+
parent.operatorToken &&
|
|
120
131
|
parent.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
121
132
|
if (ts.isPropertyAccessExpression(parent.left)) {
|
|
122
133
|
return parent.left.name.escapedText;
|
|
@@ -144,7 +144,7 @@ function isCustomType(typeString) {
|
|
|
144
144
|
* @returns {string} Basic type string
|
|
145
145
|
*/
|
|
146
146
|
function getBasicTypeOfArrayElement(checker, element) {
|
|
147
|
-
if (!element) return 'any';
|
|
147
|
+
if (!element || typeof element.kind === 'undefined') return 'any';
|
|
148
148
|
|
|
149
149
|
// Check for literal values first
|
|
150
150
|
if (ts.isStringLiteral(element)) {
|