@flisk/analyze-tracking 0.7.2 → 0.7.4
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 +4 -0
- package/bin/cli.js +30 -2
- package/package.json +12 -8
- package/src/analyze/go/astTraversal.js +121 -0
- package/src/analyze/go/constants.js +20 -0
- package/src/analyze/go/eventDeduplicator.js +47 -0
- package/src/analyze/go/eventExtractor.js +156 -0
- package/src/analyze/go/goAstParser/constants.js +39 -0
- package/src/analyze/go/goAstParser/expressionParser.js +281 -0
- package/src/analyze/go/goAstParser/index.js +52 -0
- package/src/analyze/go/goAstParser/statementParser.js +387 -0
- package/src/analyze/go/goAstParser/tokenizer.js +196 -0
- package/src/analyze/go/goAstParser/typeParser.js +202 -0
- package/src/analyze/go/goAstParser/utils.js +99 -0
- package/src/analyze/go/index.js +55 -0
- package/src/analyze/go/propertyExtractor.js +670 -0
- package/src/analyze/go/trackingDetector.js +71 -0
- package/src/analyze/go/trackingExtractor.js +54 -0
- package/src/analyze/go/typeContext.js +88 -0
- package/src/analyze/go/utils.js +215 -0
- package/src/analyze/index.js +11 -6
- package/src/analyze/javascript/constants.js +115 -0
- package/src/analyze/javascript/detectors/analytics-source.js +119 -0
- package/src/analyze/javascript/detectors/index.js +10 -0
- package/src/analyze/javascript/extractors/event-extractor.js +179 -0
- package/src/analyze/javascript/extractors/index.js +13 -0
- package/src/analyze/javascript/extractors/property-extractor.js +172 -0
- package/src/analyze/javascript/index.js +38 -0
- package/src/analyze/javascript/parser.js +126 -0
- package/src/analyze/javascript/utils/function-finder.js +123 -0
- package/src/analyze/python/index.js +111 -0
- package/src/analyze/python/pythonTrackingAnalyzer.py +814 -0
- package/src/analyze/ruby/detectors.js +46 -0
- package/src/analyze/ruby/extractors.js +258 -0
- package/src/analyze/ruby/index.js +51 -0
- package/src/analyze/ruby/traversal.js +123 -0
- package/src/analyze/ruby/types.js +30 -0
- package/src/analyze/ruby/visitor.js +66 -0
- package/src/analyze/typescript/constants.js +109 -0
- package/src/analyze/typescript/detectors/analytics-source.js +125 -0
- package/src/analyze/typescript/detectors/index.js +10 -0
- package/src/analyze/typescript/extractors/event-extractor.js +269 -0
- package/src/analyze/typescript/extractors/index.js +14 -0
- package/src/analyze/typescript/extractors/property-extractor.js +427 -0
- package/src/analyze/typescript/index.js +48 -0
- package/src/analyze/typescript/parser.js +131 -0
- package/src/analyze/typescript/utils/function-finder.js +139 -0
- package/src/analyze/typescript/utils/type-resolver.js +208 -0
- package/src/generateDescriptions/index.js +81 -0
- package/src/generateDescriptions/llmUtils.js +33 -0
- package/src/generateDescriptions/promptUtils.js +62 -0
- package/src/generateDescriptions/schemaUtils.js +61 -0
- package/src/index.js +13 -4
- package/src/{fileProcessor.js → utils/fileProcessor.js} +5 -0
- package/src/{repoDetails.js → utils/repoDetails.js} +5 -0
- package/src/utils/yamlGenerator.js +47 -0
- package/src/analyze/analyzeGoFile.js +0 -1164
- package/src/analyze/analyzeJsFile.js +0 -87
- package/src/analyze/analyzePythonFile.js +0 -42
- package/src/analyze/analyzeRubyFile.js +0 -419
- package/src/analyze/analyzeTsFile.js +0 -192
- package/src/analyze/go2json.js +0 -1069
- package/src/analyze/helpers.js +0 -656
- package/src/analyze/pythonTrackingAnalyzer.py +0 -541
- package/src/generateDescriptions.js +0 -196
- package/src/yamlGenerator.js +0 -23
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Property extraction from TypeScript AST nodes
|
|
3
|
+
* @module analyze/typescript/extractors/property-extractor
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ts = require('typescript');
|
|
7
|
+
const {
|
|
8
|
+
getTypeOfNode,
|
|
9
|
+
resolveTypeToProperties,
|
|
10
|
+
getBasicTypeOfArrayElement,
|
|
11
|
+
isCustomType
|
|
12
|
+
} = require('../utils/type-resolver');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Property structure representation
|
|
16
|
+
* @typedef {Object} PropertySchema
|
|
17
|
+
* @property {string} type - The property type (string, number, boolean, object, array, any)
|
|
18
|
+
* @property {PropertySchema} [properties] - Nested properties for objects
|
|
19
|
+
* @property {Object} [items] - Item type information for arrays
|
|
20
|
+
* @property {string} [__unresolved] - Unresolved type name marker
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Extracts properties from a TypeScript ObjectLiteralExpression node
|
|
25
|
+
* @param {Object} checker - TypeScript type checker
|
|
26
|
+
* @param {Object} node - ObjectLiteralExpression node
|
|
27
|
+
* @returns {Object.<string, PropertySchema>} Extracted properties with their schemas
|
|
28
|
+
*/
|
|
29
|
+
function extractProperties(checker, node) {
|
|
30
|
+
if (!node || !ts.isObjectLiteralExpression(node)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const properties = {};
|
|
35
|
+
|
|
36
|
+
for (const prop of node.properties) {
|
|
37
|
+
const key = getPropertyKey(prop);
|
|
38
|
+
if (!key) continue;
|
|
39
|
+
|
|
40
|
+
const schema = extractPropertySchema(checker, prop);
|
|
41
|
+
if (schema) {
|
|
42
|
+
properties[key] = schema;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return properties;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Gets the key name from a property node
|
|
51
|
+
* @param {Object} prop - Property node
|
|
52
|
+
* @returns {string|null} Property key or null
|
|
53
|
+
*/
|
|
54
|
+
function getPropertyKey(prop) {
|
|
55
|
+
if (!prop.name) {
|
|
56
|
+
// Shorthand property assignment
|
|
57
|
+
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
58
|
+
return prop.name.escapedText;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Regular property with name
|
|
64
|
+
if (ts.isIdentifier(prop.name)) {
|
|
65
|
+
return prop.name.escapedText;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (ts.isStringLiteral(prop.name)) {
|
|
69
|
+
return prop.name.text;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extracts schema information from a property
|
|
77
|
+
* @param {Object} checker - TypeScript type checker
|
|
78
|
+
* @param {Object} prop - Property node
|
|
79
|
+
* @returns {PropertySchema|null} Property schema or null
|
|
80
|
+
*/
|
|
81
|
+
function extractPropertySchema(checker, prop) {
|
|
82
|
+
// Handle shorthand property assignments
|
|
83
|
+
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
84
|
+
return extractShorthandPropertySchema(checker, prop);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle property assignments with initializers
|
|
88
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
89
|
+
if (prop.initializer) {
|
|
90
|
+
return extractValueSchema(checker, prop.initializer);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Property with type annotation but no initializer
|
|
94
|
+
if (prop.type) {
|
|
95
|
+
const typeString = checker.typeToString(checker.getTypeFromTypeNode(prop.type));
|
|
96
|
+
return resolveTypeSchema(checker, typeString);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Handle method declarations
|
|
101
|
+
if (ts.isMethodDeclaration(prop)) {
|
|
102
|
+
return { type: 'function' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Extracts schema for shorthand property assignments
|
|
110
|
+
* @param {Object} checker - TypeScript type checker
|
|
111
|
+
* @param {Object} prop - ShorthandPropertyAssignment node
|
|
112
|
+
* @returns {PropertySchema}
|
|
113
|
+
*/
|
|
114
|
+
function extractShorthandPropertySchema(checker, prop) {
|
|
115
|
+
const symbol = checker.getSymbolAtLocation(prop.name);
|
|
116
|
+
if (!symbol) {
|
|
117
|
+
return { type: 'any' };
|
|
118
|
+
}
|
|
119
|
+
const declarations = symbol.declarations || [];
|
|
120
|
+
for (const decl of declarations) {
|
|
121
|
+
// Detect destructuring from useState: const [state, setState] = useState<Type>(...)
|
|
122
|
+
if (
|
|
123
|
+
ts.isBindingElement(decl) &&
|
|
124
|
+
decl.parent &&
|
|
125
|
+
ts.isArrayBindingPattern(decl.parent) &&
|
|
126
|
+
decl.parent.parent &&
|
|
127
|
+
ts.isVariableDeclaration(decl.parent.parent) &&
|
|
128
|
+
decl.parent.parent.initializer &&
|
|
129
|
+
ts.isCallExpression(decl.parent.parent.initializer) &&
|
|
130
|
+
ts.isIdentifier(decl.parent.parent.initializer.expression) &&
|
|
131
|
+
decl.parent.parent.initializer.expression.escapedText === 'useState'
|
|
132
|
+
) {
|
|
133
|
+
// Try to get type from generic argument
|
|
134
|
+
const callExpr = decl.parent.parent.initializer;
|
|
135
|
+
if (callExpr.typeArguments && callExpr.typeArguments.length > 0) {
|
|
136
|
+
const typeNode = callExpr.typeArguments[0];
|
|
137
|
+
const type = checker.getTypeFromTypeNode(typeNode);
|
|
138
|
+
const typeString = checker.typeToString(type);
|
|
139
|
+
return resolveTypeToProperties(checker, typeString);
|
|
140
|
+
}
|
|
141
|
+
// Fallback: get type from initial value
|
|
142
|
+
if (callExpr.arguments && callExpr.arguments.length > 0) {
|
|
143
|
+
const initType = checker.getTypeAtLocation(callExpr.arguments[0]);
|
|
144
|
+
const typeString = checker.typeToString(initType);
|
|
145
|
+
return resolveTypeToProperties(checker, typeString);
|
|
146
|
+
}
|
|
147
|
+
// Default to any
|
|
148
|
+
return { type: 'any' };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const propType = checker.getTypeAtLocation(prop.name);
|
|
153
|
+
const typeString = checker.typeToString(propType);
|
|
154
|
+
|
|
155
|
+
// Handle array types
|
|
156
|
+
if (isArrayType(typeString)) {
|
|
157
|
+
return extractArrayTypeSchema(checker, propType, typeString);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Handle other types
|
|
161
|
+
const resolvedType = resolveTypeToProperties(checker, typeString);
|
|
162
|
+
|
|
163
|
+
// If it's an unresolved custom type, try to extract interface properties
|
|
164
|
+
if (resolvedType.__unresolved) {
|
|
165
|
+
const interfaceProps = extractInterfaceProperties(checker, propType);
|
|
166
|
+
if (Object.keys(interfaceProps).length > 0) {
|
|
167
|
+
return {
|
|
168
|
+
type: 'object',
|
|
169
|
+
properties: interfaceProps
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return resolvedType;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Extracts schema from a value node
|
|
179
|
+
* @param {Object} checker - TypeScript type checker
|
|
180
|
+
* @param {Object} valueNode - Value node to extract schema from
|
|
181
|
+
* @returns {PropertySchema}
|
|
182
|
+
*/
|
|
183
|
+
function extractValueSchema(checker, valueNode) {
|
|
184
|
+
// Object literal
|
|
185
|
+
if (ts.isObjectLiteralExpression(valueNode)) {
|
|
186
|
+
return {
|
|
187
|
+
type: 'object',
|
|
188
|
+
properties: extractProperties(checker, valueNode)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Array literal
|
|
193
|
+
if (ts.isArrayLiteralExpression(valueNode)) {
|
|
194
|
+
return extractArrayLiteralSchema(checker, valueNode);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Identifier (variable reference)
|
|
198
|
+
if (ts.isIdentifier(valueNode)) {
|
|
199
|
+
return extractIdentifierSchema(checker, valueNode);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Literal values
|
|
203
|
+
const literalType = getLiteralType(valueNode);
|
|
204
|
+
if (literalType) {
|
|
205
|
+
return { type: literalType };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// For other expressions, get the type from TypeChecker
|
|
209
|
+
const typeString = getTypeOfNode(checker, valueNode);
|
|
210
|
+
return resolveTypeSchema(checker, typeString);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Extracts schema for array literals
|
|
215
|
+
* @param {Object} checker - TypeScript type checker
|
|
216
|
+
* @param {Object} node - ArrayLiteralExpression node
|
|
217
|
+
* @returns {PropertySchema}
|
|
218
|
+
*/
|
|
219
|
+
function extractArrayLiteralSchema(checker, node) {
|
|
220
|
+
if (node.elements.length === 0) {
|
|
221
|
+
return {
|
|
222
|
+
type: 'array',
|
|
223
|
+
items: { type: 'any' }
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Check types of all elements
|
|
228
|
+
const elementTypes = new Set();
|
|
229
|
+
for (const element of node.elements) {
|
|
230
|
+
const elemType = getBasicTypeOfArrayElement(checker, element);
|
|
231
|
+
elementTypes.add(elemType);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// If all elements are the same type, use that type
|
|
235
|
+
const itemType = elementTypes.size === 1 ? Array.from(elementTypes)[0] : 'any';
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
type: 'array',
|
|
239
|
+
items: { type: itemType }
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Extracts schema for identifier references
|
|
245
|
+
* @param {Object} checker - TypeScript type checker
|
|
246
|
+
* @param {Object} identifier - Identifier node
|
|
247
|
+
* @returns {PropertySchema}
|
|
248
|
+
*/
|
|
249
|
+
function extractIdentifierSchema(checker, identifier) {
|
|
250
|
+
const identifierType = checker.getTypeAtLocation(identifier);
|
|
251
|
+
const typeString = checker.typeToString(identifierType);
|
|
252
|
+
|
|
253
|
+
// Handle array types
|
|
254
|
+
if (isArrayType(typeString)) {
|
|
255
|
+
return extractArrayTypeSchema(checker, identifierType, typeString);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Handle other types
|
|
259
|
+
const resolvedType = resolveTypeToProperties(checker, typeString);
|
|
260
|
+
|
|
261
|
+
// If it's an unresolved custom type, try to extract interface properties
|
|
262
|
+
if (resolvedType.__unresolved) {
|
|
263
|
+
const interfaceProps = extractInterfaceProperties(checker, identifierType);
|
|
264
|
+
if (Object.keys(interfaceProps).length > 0) {
|
|
265
|
+
return {
|
|
266
|
+
type: 'object',
|
|
267
|
+
properties: interfaceProps
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return resolvedType;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Extracts schema for array types
|
|
277
|
+
* @param {Object} checker - TypeScript type checker
|
|
278
|
+
* @param {Object} type - TypeScript Type object
|
|
279
|
+
* @param {string} typeString - String representation of the type
|
|
280
|
+
* @returns {PropertySchema}
|
|
281
|
+
*/
|
|
282
|
+
function extractArrayTypeSchema(checker, type, typeString) {
|
|
283
|
+
let elementType = null;
|
|
284
|
+
|
|
285
|
+
// Try to get type arguments for generic types
|
|
286
|
+
if (type.target && type.typeArguments && type.typeArguments.length > 0) {
|
|
287
|
+
elementType = type.typeArguments[0];
|
|
288
|
+
}
|
|
289
|
+
// Try indexed access for array types
|
|
290
|
+
else {
|
|
291
|
+
try {
|
|
292
|
+
const numberType = checker.getNumberType();
|
|
293
|
+
elementType = checker.getIndexedAccessType(type, numberType);
|
|
294
|
+
} catch (e) {
|
|
295
|
+
// Indexed access failed
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (elementType) {
|
|
300
|
+
const elementInterfaceProps = extractInterfaceProperties(checker, elementType);
|
|
301
|
+
if (Object.keys(elementInterfaceProps).length > 0) {
|
|
302
|
+
return {
|
|
303
|
+
type: 'array',
|
|
304
|
+
items: {
|
|
305
|
+
type: 'object',
|
|
306
|
+
properties: elementInterfaceProps
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
} else {
|
|
310
|
+
const elementTypeString = checker.typeToString(elementType);
|
|
311
|
+
if (isCustomType(elementTypeString)) {
|
|
312
|
+
return {
|
|
313
|
+
type: 'array',
|
|
314
|
+
items: { type: 'object' }
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return {
|
|
318
|
+
type: 'array',
|
|
319
|
+
items: resolveTypeToProperties(checker, elementTypeString)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
type: 'array',
|
|
326
|
+
items: { type: 'any' }
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Resolves a type string to a schema
|
|
332
|
+
* @param {Object} checker - TypeScript type checker
|
|
333
|
+
* @param {string} typeString - Type string
|
|
334
|
+
* @returns {PropertySchema}
|
|
335
|
+
*/
|
|
336
|
+
function resolveTypeSchema(checker, typeString) {
|
|
337
|
+
const resolvedType = resolveTypeToProperties(checker, typeString);
|
|
338
|
+
|
|
339
|
+
// Clean up any unresolved markers for simple types
|
|
340
|
+
if (resolvedType.__unresolved) {
|
|
341
|
+
delete resolvedType.__unresolved;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return resolvedType;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Gets the literal type of a node
|
|
349
|
+
* @param {Object} node - AST node
|
|
350
|
+
* @returns {string|null} Literal type or null
|
|
351
|
+
*/
|
|
352
|
+
function getLiteralType(node) {
|
|
353
|
+
if (ts.isStringLiteral(node)) return 'string';
|
|
354
|
+
if (ts.isNumericLiteral(node)) return 'number';
|
|
355
|
+
if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return 'boolean';
|
|
356
|
+
if (node.kind === ts.SyntaxKind.NullKeyword) return 'null';
|
|
357
|
+
if (node.kind === ts.SyntaxKind.UndefinedKeyword) return 'undefined';
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Checks if a type string represents an array type
|
|
363
|
+
* @param {string} typeString - Type string to check
|
|
364
|
+
* @returns {boolean}
|
|
365
|
+
*/
|
|
366
|
+
function isArrayType(typeString) {
|
|
367
|
+
return typeString.includes('[]') ||
|
|
368
|
+
typeString.startsWith('Array<') ||
|
|
369
|
+
typeString.startsWith('ReadonlyArray<') ||
|
|
370
|
+
typeString.startsWith('readonly ');
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Extracts properties from a TypeScript interface or type
|
|
375
|
+
* @param {Object} checker - TypeScript type checker
|
|
376
|
+
* @param {Object} type - TypeScript Type object
|
|
377
|
+
* @returns {Object.<string, PropertySchema>}
|
|
378
|
+
*/
|
|
379
|
+
function extractInterfaceProperties(checker, type) {
|
|
380
|
+
const properties = {};
|
|
381
|
+
const typeSymbol = type.getSymbol();
|
|
382
|
+
|
|
383
|
+
if (!typeSymbol) return properties;
|
|
384
|
+
|
|
385
|
+
// Get all properties of the type
|
|
386
|
+
const members = checker.getPropertiesOfType(type);
|
|
387
|
+
|
|
388
|
+
for (const member of members) {
|
|
389
|
+
try {
|
|
390
|
+
const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
|
|
391
|
+
const memberTypeString = checker.typeToString(memberType);
|
|
392
|
+
|
|
393
|
+
// Recursively resolve the member type
|
|
394
|
+
const resolvedType = resolveTypeToProperties(checker, memberTypeString);
|
|
395
|
+
|
|
396
|
+
// If it's an unresolved object type, try to extract its properties
|
|
397
|
+
if (resolvedType.__unresolved) {
|
|
398
|
+
const nestedProperties = extractInterfaceProperties(checker, memberType);
|
|
399
|
+
if (Object.keys(nestedProperties).length > 0) {
|
|
400
|
+
properties[member.name] = {
|
|
401
|
+
type: 'object',
|
|
402
|
+
properties: nestedProperties
|
|
403
|
+
};
|
|
404
|
+
} else {
|
|
405
|
+
properties[member.name] = resolvedType;
|
|
406
|
+
delete properties[member.name].__unresolved;
|
|
407
|
+
}
|
|
408
|
+
} else if (resolvedType.type === 'array' && memberType.target) {
|
|
409
|
+
// Handle array types in interfaces
|
|
410
|
+
const arraySchema = extractArrayTypeSchema(checker, memberType, memberTypeString);
|
|
411
|
+
properties[member.name] = arraySchema;
|
|
412
|
+
} else {
|
|
413
|
+
properties[member.name] = resolvedType;
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
// Skip properties that cause errors
|
|
417
|
+
properties[member.name] = { type: 'any' };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return properties;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
module.exports = {
|
|
425
|
+
extractProperties,
|
|
426
|
+
extractInterfaceProperties
|
|
427
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TypeScript analytics tracking analyzer - main entry point
|
|
3
|
+
* @module analyze/typescript
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { getProgram, findTrackingEvents, ProgramError, SourceFileError } = require('./parser');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Analyzes a TypeScript file for analytics tracking calls
|
|
10
|
+
* @param {string} filePath - Path to the TypeScript file to analyze
|
|
11
|
+
* @param {Object} [program] - Optional existing TypeScript program to reuse
|
|
12
|
+
* @param {string} [customFunction] - Optional custom function name to detect
|
|
13
|
+
* @returns {Array<Object>} Array of tracking events found in the file
|
|
14
|
+
*/
|
|
15
|
+
function analyzeTsFile(filePath, program, customFunction) {
|
|
16
|
+
const events = [];
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Get or create TypeScript program
|
|
20
|
+
const tsProgram = getProgram(filePath, program);
|
|
21
|
+
|
|
22
|
+
// Get source file from program
|
|
23
|
+
const sourceFile = tsProgram.getSourceFile(filePath);
|
|
24
|
+
if (!sourceFile) {
|
|
25
|
+
throw new SourceFileError(filePath);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Get type checker
|
|
29
|
+
const checker = tsProgram.getTypeChecker();
|
|
30
|
+
|
|
31
|
+
// Find and extract tracking events
|
|
32
|
+
const foundEvents = findTrackingEvents(sourceFile, checker, filePath, customFunction);
|
|
33
|
+
events.push(...foundEvents);
|
|
34
|
+
|
|
35
|
+
} catch (error) {
|
|
36
|
+
if (error instanceof ProgramError) {
|
|
37
|
+
console.error(`Error creating TypeScript program for ${filePath}: ${error.originalError?.message || error.message}`);
|
|
38
|
+
} else if (error instanceof SourceFileError) {
|
|
39
|
+
console.error(`Error: Unable to get source file for ${filePath}`);
|
|
40
|
+
} else {
|
|
41
|
+
console.error(`Error analyzing TypeScript file ${filePath}: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return events;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { analyzeTsFile };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview TypeScript AST parsing and walking module
|
|
3
|
+
* @module analyze/typescript/parser
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const ts = require('typescript');
|
|
7
|
+
const { detectAnalyticsSource } = require('./detectors');
|
|
8
|
+
const { extractEventData, processEventData } = require('./extractors');
|
|
9
|
+
const { findWrappingFunction } = require('./utils/function-finder');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown when TypeScript program cannot be created
|
|
13
|
+
*/
|
|
14
|
+
class ProgramError extends Error {
|
|
15
|
+
constructor(filePath, originalError) {
|
|
16
|
+
super(`Failed to create TypeScript program for: ${filePath}`);
|
|
17
|
+
this.name = 'ProgramError';
|
|
18
|
+
this.filePath = filePath;
|
|
19
|
+
this.originalError = originalError;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Error thrown when source file cannot be retrieved
|
|
25
|
+
*/
|
|
26
|
+
class SourceFileError extends Error {
|
|
27
|
+
constructor(filePath) {
|
|
28
|
+
super(`Failed to get source file: ${filePath}`);
|
|
29
|
+
this.name = 'SourceFileError';
|
|
30
|
+
this.filePath = filePath;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gets or creates a TypeScript program for analysis
|
|
36
|
+
* @param {string} filePath - Path to the TypeScript file
|
|
37
|
+
* @param {Object} [existingProgram] - Existing TypeScript program to reuse
|
|
38
|
+
* @returns {Object} TypeScript program
|
|
39
|
+
* @throws {ProgramError} If program cannot be created
|
|
40
|
+
*/
|
|
41
|
+
function getProgram(filePath, existingProgram) {
|
|
42
|
+
if (existingProgram) {
|
|
43
|
+
return existingProgram;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
// Create a minimal program for single file analysis
|
|
48
|
+
const options = {
|
|
49
|
+
target: ts.ScriptTarget.Latest,
|
|
50
|
+
module: ts.ModuleKind.CommonJS,
|
|
51
|
+
allowJs: true,
|
|
52
|
+
checkJs: false,
|
|
53
|
+
noEmit: true
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const program = ts.createProgram([filePath], options);
|
|
57
|
+
return program;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new ProgramError(filePath, error);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Walks the TypeScript AST and finds analytics tracking calls
|
|
65
|
+
* @param {Object} sourceFile - TypeScript source file
|
|
66
|
+
* @param {Object} checker - TypeScript type checker
|
|
67
|
+
* @param {string} filePath - Path to the file being analyzed
|
|
68
|
+
* @param {string} [customFunction] - Custom function name to detect
|
|
69
|
+
* @returns {Array<Object>} Array of found events
|
|
70
|
+
*/
|
|
71
|
+
function findTrackingEvents(sourceFile, checker, filePath, customFunction) {
|
|
72
|
+
const events = [];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Visitor function for AST traversal
|
|
76
|
+
* @param {Object} node - Current AST node
|
|
77
|
+
*/
|
|
78
|
+
function visit(node) {
|
|
79
|
+
try {
|
|
80
|
+
if (ts.isCallExpression(node)) {
|
|
81
|
+
const event = extractTrackingEvent(node, sourceFile, checker, filePath, customFunction);
|
|
82
|
+
if (event) {
|
|
83
|
+
events.push(event);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Continue traversing the AST
|
|
87
|
+
ts.forEachChild(node, visit);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`Error processing node in ${filePath}:`, error.message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Start traversal from the root
|
|
94
|
+
ts.forEachChild(sourceFile, visit);
|
|
95
|
+
|
|
96
|
+
return events;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Extracts tracking event from a CallExpression node
|
|
101
|
+
* @param {Object} node - CallExpression node
|
|
102
|
+
* @param {Object} sourceFile - TypeScript source file
|
|
103
|
+
* @param {Object} checker - TypeScript type checker
|
|
104
|
+
* @param {string} filePath - File path
|
|
105
|
+
* @param {string} [customFunction] - Custom function name
|
|
106
|
+
* @returns {Object|null} Extracted event or null
|
|
107
|
+
*/
|
|
108
|
+
function extractTrackingEvent(node, sourceFile, checker, filePath, customFunction) {
|
|
109
|
+
// Detect the analytics source
|
|
110
|
+
const source = detectAnalyticsSource(node, customFunction);
|
|
111
|
+
if (source === 'unknown') {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Extract event data based on the source
|
|
116
|
+
const eventData = extractEventData(node, source, checker, sourceFile);
|
|
117
|
+
|
|
118
|
+
// Get location and context information
|
|
119
|
+
const line = sourceFile.getLineAndCharacterOfPosition(node.getStart()).line + 1;
|
|
120
|
+
const functionName = findWrappingFunction(node);
|
|
121
|
+
|
|
122
|
+
// Process the event data into final format
|
|
123
|
+
return processEventData(eventData, source, filePath, line, functionName, checker, sourceFile);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
getProgram,
|
|
128
|
+
findTrackingEvents,
|
|
129
|
+
ProgramError,
|
|
130
|
+
SourceFileError
|
|
131
|
+
};
|