@flisk/analyze-tracking 0.7.4 → 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/ruby/detectors.js +12 -1
- package/src/analyze/ruby/extractors.js +24 -12
- package/src/analyze/ruby/visitor.js +10 -1
- package/src/analyze/typescript/detectors/analytics-source.js +6 -3
- package/src/analyze/typescript/extractors/event-extractor.js +111 -6
- package/src/analyze/typescript/extractors/property-extractor.js +53 -0
- package/src/analyze/typescript/utils/function-finder.js +12 -1
- package/src/analyze/typescript/utils/type-resolver.js +1 -1
package/package.json
CHANGED
|
@@ -36,7 +36,18 @@ function detectSource(node, customFunction = null) {
|
|
|
36
36
|
if (node.name === 'track_struct_event') return 'snowplow';
|
|
37
37
|
|
|
38
38
|
// Custom tracking function
|
|
39
|
-
if (customFunction
|
|
39
|
+
if (customFunction) {
|
|
40
|
+
// Handle simple function names (e.g., 'customTrackFunction')
|
|
41
|
+
if (node.name === customFunction) return 'custom';
|
|
42
|
+
|
|
43
|
+
// Handle module-scoped function names (e.g., 'CustomModule.track')
|
|
44
|
+
if (customFunction.includes('.')) {
|
|
45
|
+
const [moduleName, methodName] = customFunction.split('.');
|
|
46
|
+
if (node.receiver && node.receiver.name === moduleName && node.name === methodName) {
|
|
47
|
+
return 'custom';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
40
51
|
|
|
41
52
|
return null;
|
|
42
53
|
}
|
|
@@ -14,14 +14,17 @@ const { getValueType } = require('./types');
|
|
|
14
14
|
function extractEventName(node, source) {
|
|
15
15
|
if (source === 'segment' || source === 'rudderstack') {
|
|
16
16
|
// Both Segment and Rudderstack use the same format
|
|
17
|
-
const params = node.arguments_
|
|
17
|
+
const params = node.arguments_?.arguments_?.[0]?.elements;
|
|
18
|
+
if (!params || !Array.isArray(params)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
18
21
|
const eventProperty = params.find(param => param?.key?.unescaped?.value === 'event');
|
|
19
22
|
return eventProperty?.value?.unescaped?.value || null;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
if (source === 'mixpanel') {
|
|
23
26
|
// Mixpanel Ruby SDK format: tracker.track('distinct_id', 'event_name', {...})
|
|
24
|
-
const args = node.arguments_
|
|
27
|
+
const args = node.arguments_?.arguments_;
|
|
25
28
|
if (args && args.length > 1 && args[1]?.unescaped?.value) {
|
|
26
29
|
return args[1].unescaped.value;
|
|
27
30
|
}
|
|
@@ -29,8 +32,8 @@ function extractEventName(node, source) {
|
|
|
29
32
|
|
|
30
33
|
if (source === 'posthog') {
|
|
31
34
|
// PostHog Ruby SDK format: posthog.capture({distinct_id: '...', event: '...', properties: {...}})
|
|
32
|
-
const hashArg = node.arguments_
|
|
33
|
-
if (hashArg && hashArg.elements) {
|
|
35
|
+
const hashArg = node.arguments_?.arguments_?.[0];
|
|
36
|
+
if (hashArg && hashArg.elements && Array.isArray(hashArg.elements)) {
|
|
34
37
|
const eventProperty = hashArg.elements.find(elem => elem?.key?.unescaped?.value === 'event');
|
|
35
38
|
return eventProperty?.value?.unescaped?.value || null;
|
|
36
39
|
}
|
|
@@ -38,14 +41,17 @@ function extractEventName(node, source) {
|
|
|
38
41
|
|
|
39
42
|
if (source === 'snowplow') {
|
|
40
43
|
// Snowplow Ruby SDK: tracker.track_struct_event(category: '...', action: '...', ...)
|
|
41
|
-
const params = node.arguments_
|
|
44
|
+
const params = node.arguments_?.arguments_?.[0]?.elements;
|
|
45
|
+
if (!params || !Array.isArray(params)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
42
48
|
const actionProperty = params.find(param => param?.key?.unescaped?.value === 'action');
|
|
43
49
|
return actionProperty?.value?.unescaped?.value || null;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
if (source === 'custom') {
|
|
47
53
|
// Custom function format: customFunction('event_name', {...})
|
|
48
|
-
const args = node.arguments_
|
|
54
|
+
const args = node.arguments_?.arguments_;
|
|
49
55
|
if (args && args.length > 0 && args[0]?.unescaped?.value) {
|
|
50
56
|
return args[0].unescaped.value;
|
|
51
57
|
}
|
|
@@ -65,7 +71,10 @@ async function extractProperties(node, source) {
|
|
|
65
71
|
|
|
66
72
|
if (source === 'segment' || source === 'rudderstack') {
|
|
67
73
|
// Both Segment and Rudderstack use the same format
|
|
68
|
-
const params = node.arguments_
|
|
74
|
+
const params = node.arguments_?.arguments_?.[0]?.elements;
|
|
75
|
+
if (!params || !Array.isArray(params)) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
69
78
|
const properties = {};
|
|
70
79
|
|
|
71
80
|
// Process all top-level fields except 'event'
|
|
@@ -108,7 +117,7 @@ async function extractProperties(node, source) {
|
|
|
108
117
|
|
|
109
118
|
if (source === 'mixpanel') {
|
|
110
119
|
// Mixpanel Ruby SDK: tracker.track('distinct_id', 'event_name', {properties})
|
|
111
|
-
const args = node.arguments_
|
|
120
|
+
const args = node.arguments_?.arguments_;
|
|
112
121
|
const properties = {};
|
|
113
122
|
|
|
114
123
|
// Add distinct_id as property (even if it's a variable)
|
|
@@ -129,10 +138,10 @@ async function extractProperties(node, source) {
|
|
|
129
138
|
|
|
130
139
|
if (source === 'posthog') {
|
|
131
140
|
// PostHog Ruby SDK: posthog.capture({distinct_id: '...', event: '...', properties: {...}})
|
|
132
|
-
const hashArg = node.arguments_
|
|
141
|
+
const hashArg = node.arguments_?.arguments_?.[0];
|
|
133
142
|
const properties = {};
|
|
134
143
|
|
|
135
|
-
if (hashArg && hashArg.elements) {
|
|
144
|
+
if (hashArg && hashArg.elements && Array.isArray(hashArg.elements)) {
|
|
136
145
|
// Extract distinct_id if present
|
|
137
146
|
const distinctIdProperty = hashArg.elements.find(elem => elem?.key?.unescaped?.value === 'distinct_id');
|
|
138
147
|
if (distinctIdProperty?.value) {
|
|
@@ -154,7 +163,10 @@ async function extractProperties(node, source) {
|
|
|
154
163
|
|
|
155
164
|
if (source === 'snowplow') {
|
|
156
165
|
// Snowplow Ruby SDK: tracker.track_struct_event(category: '...', action: '...', ...)
|
|
157
|
-
const params = node.arguments_
|
|
166
|
+
const params = node.arguments_?.arguments_?.[0]?.elements;
|
|
167
|
+
if (!params || !Array.isArray(params)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
158
170
|
const properties = {};
|
|
159
171
|
|
|
160
172
|
// Extract all struct event parameters except 'action' (which is used as the event name)
|
|
@@ -172,7 +184,7 @@ async function extractProperties(node, source) {
|
|
|
172
184
|
|
|
173
185
|
if (source === 'custom') {
|
|
174
186
|
// Custom function format: customFunction('event_name', {properties})
|
|
175
|
-
const args = node.arguments_
|
|
187
|
+
const args = node.arguments_?.arguments_;
|
|
176
188
|
if (args && args.length > 1 && args[1] instanceof HashNode) {
|
|
177
189
|
return await extractHashProperties(args[1]);
|
|
178
190
|
}
|
|
@@ -29,7 +29,16 @@ class TrackingVisitor {
|
|
|
29
29
|
if (!eventName) return;
|
|
30
30
|
|
|
31
31
|
const line = getLineNumber(this.code, node.location);
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
// For module-scoped custom functions, use the custom function name as the functionName
|
|
34
|
+
// For simple custom functions, use the wrapping function name
|
|
35
|
+
let functionName;
|
|
36
|
+
if (source === 'custom' && this.customFunction && this.customFunction.includes('.')) {
|
|
37
|
+
functionName = this.customFunction;
|
|
38
|
+
} else {
|
|
39
|
+
functionName = await findWrappingFunction(node, ancestors);
|
|
40
|
+
}
|
|
41
|
+
|
|
33
42
|
const properties = await extractProperties(node, source);
|
|
34
43
|
|
|
35
44
|
this.events.push({
|
|
@@ -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
|
}
|
|
@@ -84,8 +87,8 @@ function detectMemberBasedProvider(node) {
|
|
|
84
87
|
return 'unknown';
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
const objectName = node.expression.expression
|
|
88
|
-
const methodName = node.expression.name
|
|
90
|
+
const objectName = node.expression.expression?.escapedText;
|
|
91
|
+
const methodName = node.expression.name?.escapedText;
|
|
89
92
|
|
|
90
93
|
if (!objectName || !methodName) {
|
|
91
94
|
return 'unknown';
|
|
@@ -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,6 +357,7 @@ function resolveTypeSchema(checker, typeString) {
|
|
|
350
357
|
* @returns {string|null} Literal type or null
|
|
351
358
|
*/
|
|
352
359
|
function getLiteralType(node) {
|
|
360
|
+
if (!node || typeof node.kind === 'undefined') return null;
|
|
353
361
|
if (ts.isStringLiteral(node)) return 'string';
|
|
354
362
|
if (ts.isNumericLiteral(node)) return 'number';
|
|
355
363
|
if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return 'boolean';
|
|
@@ -370,6 +378,51 @@ function isArrayType(typeString) {
|
|
|
370
378
|
typeString.startsWith('readonly ');
|
|
371
379
|
}
|
|
372
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
|
+
|
|
373
426
|
/**
|
|
374
427
|
* Extracts properties from a TypeScript interface or type
|
|
375
428
|
* @param {Object} checker - TypeScript type checker
|
|
@@ -93,7 +93,7 @@ function findParentFunctionName(node) {
|
|
|
93
93
|
parent.initializer &&
|
|
94
94
|
ts.isCallExpression(parent.initializer) &&
|
|
95
95
|
ts.isIdentifier(parent.initializer.expression) &&
|
|
96
|
-
|
|
96
|
+
isReactHookCall(parent.initializer)
|
|
97
97
|
) {
|
|
98
98
|
return `${parent.initializer.expression.escapedText}(${parent.name.escapedText})`;
|
|
99
99
|
}
|
|
@@ -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)) {
|