@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
package/src/analyze/helpers.js
DELETED
|
@@ -1,656 +0,0 @@
|
|
|
1
|
-
const ts = require('typescript');
|
|
2
|
-
|
|
3
|
-
function detectSourceJs(node, customFunction) {
|
|
4
|
-
if (!node.callee) return 'unknown';
|
|
5
|
-
|
|
6
|
-
if (node.callee.type === 'Identifier' && node.callee.name === 'gtag') {
|
|
7
|
-
return 'googleanalytics';
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (node.callee.type === 'MemberExpression') {
|
|
11
|
-
const objectName = node.callee.object.name;
|
|
12
|
-
const methodName = node.callee.property.name;
|
|
13
|
-
|
|
14
|
-
if (objectName === 'analytics' && methodName === 'track') return 'segment';
|
|
15
|
-
if (objectName === 'mixpanel' && methodName === 'track') return 'mixpanel';
|
|
16
|
-
if (objectName === 'amplitude' && methodName === 'track') return 'amplitude';
|
|
17
|
-
if (objectName === 'rudderanalytics' && methodName === 'track') return 'rudderstack';
|
|
18
|
-
if ((objectName === 'mParticle' || objectName === 'mparticle') && methodName === 'logEvent') return 'mparticle';
|
|
19
|
-
if (objectName === 'posthog' && methodName === 'capture') return 'posthog';
|
|
20
|
-
if (objectName === 'pendo' && methodName === 'track') return 'pendo';
|
|
21
|
-
if (objectName === 'heap' && methodName === 'track') return 'heap';
|
|
22
|
-
|
|
23
|
-
// Check for Snowplow pattern: tracker.track(...)
|
|
24
|
-
if (objectName === 'tracker' && methodName === 'track') {
|
|
25
|
-
return 'snowplow';
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (node.callee.type === 'Identifier' && node.callee.name === customFunction) {
|
|
30
|
-
return 'custom';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return 'unknown';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function detectSourceTs(node, customFunction) {
|
|
37
|
-
if (!node.expression) return 'unknown';
|
|
38
|
-
|
|
39
|
-
if (ts.isIdentifier(node.expression) && node.expression.escapedText === 'gtag') {
|
|
40
|
-
return 'googleanalytics';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
44
|
-
const objectName = node.expression.expression.escapedText;
|
|
45
|
-
const methodName = node.expression.name.escapedText;
|
|
46
|
-
|
|
47
|
-
if (objectName === 'analytics' && methodName === 'track') return 'segment';
|
|
48
|
-
if (objectName === 'mixpanel' && methodName === 'track') return 'mixpanel';
|
|
49
|
-
if (objectName === 'amplitude' && methodName === 'track') return 'amplitude';
|
|
50
|
-
if (objectName === 'rudderanalytics' && methodName === 'track') return 'rudderstack';
|
|
51
|
-
if ((objectName === 'mParticle' || objectName === 'mparticle') && methodName === 'logEvent') return 'mparticle';
|
|
52
|
-
if (objectName === 'posthog' && methodName === 'capture') return 'posthog';
|
|
53
|
-
if (objectName === 'pendo' && methodName === 'track') return 'pendo';
|
|
54
|
-
if (objectName === 'heap' && methodName === 'track') return 'heap';
|
|
55
|
-
|
|
56
|
-
// Check for Snowplow pattern: tracker.track(...)
|
|
57
|
-
if (objectName === 'tracker' && methodName === 'track') {
|
|
58
|
-
return 'snowplow';
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (ts.isIdentifier(node.expression) && node.expression.escapedText === customFunction) {
|
|
63
|
-
return 'custom';
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return 'unknown';
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function findWrappingFunctionTs(node) {
|
|
70
|
-
let current = node;
|
|
71
|
-
while (current) {
|
|
72
|
-
if (ts.isFunctionDeclaration(current) || ts.isMethodDeclaration(current) || ts.isArrowFunction(current)) {
|
|
73
|
-
return current.name ? current.name.escapedText : 'anonymous';
|
|
74
|
-
}
|
|
75
|
-
current = current.parent;
|
|
76
|
-
}
|
|
77
|
-
return 'global';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function findWrappingFunctionJs(node, ancestors) {
|
|
81
|
-
for (let i = ancestors.length - 1; i >= 0; i--) {
|
|
82
|
-
const current = ancestors[i];
|
|
83
|
-
|
|
84
|
-
// Handle direct variable assignments (e.g., const myFunc = () => {})
|
|
85
|
-
if (current.type === 'VariableDeclarator' && current.init === node) {
|
|
86
|
-
return current.id.name;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Handle arrow functions or function expressions assigned to variables
|
|
90
|
-
if (current.type === 'VariableDeclarator' && (current.init.type === 'ArrowFunctionExpression' || current.init.type === 'FunctionExpression')) {
|
|
91
|
-
return current.id.name;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Handle named function declarations
|
|
95
|
-
if (current.type === 'FunctionDeclaration') {
|
|
96
|
-
return current.id ? current.id.name : 'anonymous';
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Handle class methods
|
|
100
|
-
if (current.type === 'MethodDefinition') {
|
|
101
|
-
return current.key.name || 'anonymous';
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Handle exported variable/function (e.g., export const myFunc = () => {})
|
|
105
|
-
if (current.type === 'ExportNamedDeclaration' && current.declaration) {
|
|
106
|
-
const declaration = current.declaration.declarations ? current.declaration.declarations[0] : null;
|
|
107
|
-
if (declaration && (declaration.init.type === 'ArrowFunctionExpression' || declaration.init.type === 'FunctionExpression')) {
|
|
108
|
-
return declaration.id.name;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Handle methods within object literals
|
|
113
|
-
if (current.type === 'Property' && current.value === node) {
|
|
114
|
-
return current.key.name || current.key.value;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
return 'global';
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function extractJsProperties(node) {
|
|
121
|
-
const properties = {};
|
|
122
|
-
|
|
123
|
-
node.properties.forEach((prop) => {
|
|
124
|
-
const key = prop.key?.name || prop.key?.value;
|
|
125
|
-
if (key) {
|
|
126
|
-
if (prop.value.type === 'ObjectExpression') {
|
|
127
|
-
properties[key] = {
|
|
128
|
-
type: 'object',
|
|
129
|
-
properties: extractJsProperties(prop.value),
|
|
130
|
-
};
|
|
131
|
-
} else if (prop.value.type === 'ArrayExpression') {
|
|
132
|
-
// Handle arrays - analyze elements to determine item type
|
|
133
|
-
let itemType = 'any';
|
|
134
|
-
if (prop.value.elements && prop.value.elements.length > 0) {
|
|
135
|
-
// Check the types of all elements
|
|
136
|
-
const elementTypes = new Set();
|
|
137
|
-
prop.value.elements.forEach(element => {
|
|
138
|
-
if (element) {
|
|
139
|
-
if (element.type === 'Literal') {
|
|
140
|
-
elementTypes.add(typeof element.value);
|
|
141
|
-
} else if (element.type === 'ObjectExpression') {
|
|
142
|
-
elementTypes.add('object');
|
|
143
|
-
} else if (element.type === 'ArrayExpression') {
|
|
144
|
-
elementTypes.add('array');
|
|
145
|
-
} else {
|
|
146
|
-
elementTypes.add('any');
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// If all elements are the same type, use that type
|
|
152
|
-
if (elementTypes.size === 1) {
|
|
153
|
-
itemType = Array.from(elementTypes)[0];
|
|
154
|
-
} else {
|
|
155
|
-
itemType = 'any';
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
properties[key] = {
|
|
160
|
-
type: 'array',
|
|
161
|
-
items: {
|
|
162
|
-
type: itemType
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
} else {
|
|
166
|
-
let valueType = typeof prop.value.value;
|
|
167
|
-
if (valueType === 'undefined') {
|
|
168
|
-
valueType = 'any';
|
|
169
|
-
} else if (valueType === 'object') {
|
|
170
|
-
valueType = 'any';
|
|
171
|
-
}
|
|
172
|
-
properties[key] = { type: valueType };
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
return properties;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function extractTsProperties(checker, node) {
|
|
181
|
-
const properties = {};
|
|
182
|
-
|
|
183
|
-
for (const prop of node.properties) {
|
|
184
|
-
const key = !!prop.name ? prop.name.text : (!!prop.key ? (prop.key.text || prop.key.value) : undefined);
|
|
185
|
-
if (!key) continue;
|
|
186
|
-
let valueType = 'any';
|
|
187
|
-
|
|
188
|
-
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
189
|
-
const symbol = checker.getSymbolAtLocation(prop.name);
|
|
190
|
-
if (symbol) {
|
|
191
|
-
// Get the type of the shorthand property
|
|
192
|
-
const propType = checker.getTypeAtLocation(prop.name);
|
|
193
|
-
const typeString = checker.typeToString(propType);
|
|
194
|
-
|
|
195
|
-
// Check if it's an array type
|
|
196
|
-
if (typeString.includes('[]') || typeString.startsWith('Array<')) {
|
|
197
|
-
// Handle array types
|
|
198
|
-
let elementType = null;
|
|
199
|
-
|
|
200
|
-
// Try to get type arguments for generic types
|
|
201
|
-
if (propType.target && propType.typeArguments && propType.typeArguments.length > 0) {
|
|
202
|
-
elementType = propType.typeArguments[0];
|
|
203
|
-
}
|
|
204
|
-
// Try indexed access for array types
|
|
205
|
-
else {
|
|
206
|
-
try {
|
|
207
|
-
const numberType = checker.getNumberType();
|
|
208
|
-
elementType = checker.getIndexedAccessType(propType, numberType);
|
|
209
|
-
} catch (e) {
|
|
210
|
-
// Indexed access failed
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (elementType) {
|
|
215
|
-
const elementInterfaceProps = extractInterfaceProperties(checker, elementType);
|
|
216
|
-
if (Object.keys(elementInterfaceProps).length > 0) {
|
|
217
|
-
properties[key] = {
|
|
218
|
-
type: 'array',
|
|
219
|
-
items: {
|
|
220
|
-
type: 'object',
|
|
221
|
-
properties: elementInterfaceProps
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
} else {
|
|
225
|
-
properties[key] = {
|
|
226
|
-
type: 'array',
|
|
227
|
-
items: {
|
|
228
|
-
type: 'object'
|
|
229
|
-
}
|
|
230
|
-
};
|
|
231
|
-
}
|
|
232
|
-
} else {
|
|
233
|
-
properties[key] = {
|
|
234
|
-
type: 'array',
|
|
235
|
-
items: {
|
|
236
|
-
type: 'any'
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
} else {
|
|
241
|
-
// Not an array, handle as before
|
|
242
|
-
const resolvedType = resolveTypeToProperties(checker, typeString);
|
|
243
|
-
if (resolvedType.__unresolved) {
|
|
244
|
-
// Try to get the actual type and extract properties
|
|
245
|
-
const interfaceProps = extractInterfaceProperties(checker, propType);
|
|
246
|
-
if (Object.keys(interfaceProps).length > 0) {
|
|
247
|
-
properties[key] = {
|
|
248
|
-
type: 'object',
|
|
249
|
-
properties: interfaceProps
|
|
250
|
-
};
|
|
251
|
-
} else {
|
|
252
|
-
properties[key] = resolvedType;
|
|
253
|
-
delete properties[key].__unresolved;
|
|
254
|
-
}
|
|
255
|
-
} else {
|
|
256
|
-
properties[key] = resolvedType;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
} else if (prop.initializer) {
|
|
261
|
-
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
262
|
-
properties[key] = {
|
|
263
|
-
type: 'object',
|
|
264
|
-
properties: extractTsProperties(checker, prop.initializer),
|
|
265
|
-
};
|
|
266
|
-
} else if (ts.isArrayLiteralExpression(prop.initializer)) {
|
|
267
|
-
// For array literals, we need to check the elements
|
|
268
|
-
const elementTypes = new Set();
|
|
269
|
-
|
|
270
|
-
if (prop.initializer.elements.length === 0) {
|
|
271
|
-
// Empty array
|
|
272
|
-
properties[key] = {
|
|
273
|
-
type: 'array',
|
|
274
|
-
items: {
|
|
275
|
-
type: 'any'
|
|
276
|
-
}
|
|
277
|
-
};
|
|
278
|
-
} else {
|
|
279
|
-
// Check types of all elements
|
|
280
|
-
for (const element of prop.initializer.elements) {
|
|
281
|
-
const elemType = getBasicTypeOfArrayElement(checker, element);
|
|
282
|
-
elementTypes.add(elemType);
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// If all elements are the same type, use that type; otherwise use 'any'
|
|
286
|
-
const itemType = elementTypes.size === 1 ? Array.from(elementTypes)[0] : 'any';
|
|
287
|
-
|
|
288
|
-
properties[key] = {
|
|
289
|
-
type: 'array',
|
|
290
|
-
items: {
|
|
291
|
-
type: itemType
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
} else if (ts.isIdentifier(prop.initializer)) {
|
|
296
|
-
// Handle identifiers (variable references)
|
|
297
|
-
const identifierType = checker.getTypeAtLocation(prop.initializer);
|
|
298
|
-
const typeString = checker.typeToString(identifierType);
|
|
299
|
-
|
|
300
|
-
// Check if it's an array type
|
|
301
|
-
if (typeString.includes('[]') || typeString.startsWith('Array<')) {
|
|
302
|
-
// Extract element type and check if it's a custom interface
|
|
303
|
-
let elementType = null;
|
|
304
|
-
|
|
305
|
-
// Try to get type arguments for generic types
|
|
306
|
-
if (identifierType.target && identifierType.typeArguments && identifierType.typeArguments.length > 0) {
|
|
307
|
-
elementType = identifierType.typeArguments[0];
|
|
308
|
-
}
|
|
309
|
-
// Try indexed access for array types
|
|
310
|
-
else {
|
|
311
|
-
try {
|
|
312
|
-
const numberType = checker.getNumberType();
|
|
313
|
-
elementType = checker.getIndexedAccessType(identifierType, numberType);
|
|
314
|
-
} catch (e) {
|
|
315
|
-
// Indexed access failed
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (elementType) {
|
|
320
|
-
const elementInterfaceProps = extractInterfaceProperties(checker, elementType);
|
|
321
|
-
if (Object.keys(elementInterfaceProps).length > 0) {
|
|
322
|
-
properties[key] = {
|
|
323
|
-
type: 'array',
|
|
324
|
-
items: {
|
|
325
|
-
type: 'object',
|
|
326
|
-
properties: elementInterfaceProps
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
} else {
|
|
330
|
-
properties[key] = {
|
|
331
|
-
type: 'array',
|
|
332
|
-
items: {
|
|
333
|
-
type: 'object'
|
|
334
|
-
}
|
|
335
|
-
};
|
|
336
|
-
}
|
|
337
|
-
} else {
|
|
338
|
-
properties[key] = {
|
|
339
|
-
type: 'array',
|
|
340
|
-
items: {
|
|
341
|
-
type: 'any'
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
} else {
|
|
346
|
-
// Not an array, resolve normally
|
|
347
|
-
const resolvedType = resolveTypeToProperties(checker, typeString);
|
|
348
|
-
if (resolvedType.__unresolved) {
|
|
349
|
-
const interfaceProps = extractInterfaceProperties(checker, identifierType);
|
|
350
|
-
if (Object.keys(interfaceProps).length > 0) {
|
|
351
|
-
properties[key] = {
|
|
352
|
-
type: 'object',
|
|
353
|
-
properties: interfaceProps
|
|
354
|
-
};
|
|
355
|
-
} else {
|
|
356
|
-
properties[key] = resolvedType;
|
|
357
|
-
delete properties[key].__unresolved;
|
|
358
|
-
}
|
|
359
|
-
} else {
|
|
360
|
-
properties[key] = resolvedType;
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
// Handle hard-coded values
|
|
365
|
-
switch (prop.initializer.kind) {
|
|
366
|
-
case ts.SyntaxKind.StringLiteral:
|
|
367
|
-
valueType = 'string';
|
|
368
|
-
break;
|
|
369
|
-
case ts.SyntaxKind.NumericLiteral:
|
|
370
|
-
valueType = 'number';
|
|
371
|
-
break;
|
|
372
|
-
case ts.SyntaxKind.TrueKeyword:
|
|
373
|
-
case ts.SyntaxKind.FalseKeyword:
|
|
374
|
-
valueType = 'boolean';
|
|
375
|
-
break;
|
|
376
|
-
case ts.SyntaxKind.ArrayLiteralExpression:
|
|
377
|
-
valueType = 'array';
|
|
378
|
-
break;
|
|
379
|
-
case ts.SyntaxKind.ObjectLiteralExpression:
|
|
380
|
-
valueType = 'object';
|
|
381
|
-
break;
|
|
382
|
-
default:
|
|
383
|
-
valueType = 'any';
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (valueType === 'any') {
|
|
387
|
-
valueType = getTypeOfNode(checker, prop.initializer) || 'any';
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Check if this is a custom type that should be expanded
|
|
391
|
-
const resolvedType = resolveTypeToProperties(checker, valueType);
|
|
392
|
-
if (resolvedType.__unresolved) {
|
|
393
|
-
// Try to get the actual type and extract properties
|
|
394
|
-
const propType = checker.getTypeAtLocation(prop.initializer);
|
|
395
|
-
const interfaceProps = extractInterfaceProperties(checker, propType);
|
|
396
|
-
if (Object.keys(interfaceProps).length > 0) {
|
|
397
|
-
properties[key] = {
|
|
398
|
-
type: 'object',
|
|
399
|
-
properties: interfaceProps
|
|
400
|
-
};
|
|
401
|
-
} else {
|
|
402
|
-
properties[key] = resolvedType;
|
|
403
|
-
delete properties[key].__unresolved;
|
|
404
|
-
}
|
|
405
|
-
} else {
|
|
406
|
-
properties[key] = resolvedType;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
} else if (prop.type) {
|
|
410
|
-
valueType = checker.typeToString(checker.getTypeFromTypeNode(prop.type)) || 'any';
|
|
411
|
-
|
|
412
|
-
// Check if this is a custom type that should be expanded
|
|
413
|
-
const resolvedType = resolveTypeToProperties(checker, valueType);
|
|
414
|
-
|
|
415
|
-
// Special handling for arrays of custom types
|
|
416
|
-
if (resolvedType.type === 'array') {
|
|
417
|
-
const propType = checker.getTypeFromTypeNode(prop.type);
|
|
418
|
-
|
|
419
|
-
// Try multiple approaches to get array element type
|
|
420
|
-
let elementType = null;
|
|
421
|
-
|
|
422
|
-
// First try: Check if it's a generic type reference (Array<T> or ReadonlyArray<T>)
|
|
423
|
-
if (propType.target && propType.typeArguments && propType.typeArguments.length > 0) {
|
|
424
|
-
elementType = propType.typeArguments[0];
|
|
425
|
-
}
|
|
426
|
-
// Second try: For T[] syntax, use indexed access
|
|
427
|
-
else {
|
|
428
|
-
try {
|
|
429
|
-
const numberType = checker.getNumberType();
|
|
430
|
-
elementType = checker.getIndexedAccessType(propType, numberType);
|
|
431
|
-
} catch (e) {
|
|
432
|
-
// Indexed access failed
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
if (elementType) {
|
|
437
|
-
const elementInterfaceProps = extractInterfaceProperties(checker, elementType);
|
|
438
|
-
if (Object.keys(elementInterfaceProps).length > 0) {
|
|
439
|
-
resolvedType.items = {
|
|
440
|
-
type: 'object',
|
|
441
|
-
properties: elementInterfaceProps
|
|
442
|
-
};
|
|
443
|
-
} else {
|
|
444
|
-
// If no properties found but it looks like a custom type, mark as object
|
|
445
|
-
const elementTypeString = checker.typeToString(elementType);
|
|
446
|
-
if (elementTypeString[0] === elementTypeString[0].toUpperCase() && !elementTypeString.includes('<')) {
|
|
447
|
-
resolvedType.items = { type: 'object' };
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
properties[key] = resolvedType;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
return properties;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
function getTypeOfNode(checker, node) {
|
|
461
|
-
const type = checker.getTypeAtLocation(node);
|
|
462
|
-
return checker.typeToString(type);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function getBasicTypeOfArrayElement(checker, element) {
|
|
466
|
-
if (!element) return 'any';
|
|
467
|
-
|
|
468
|
-
// Check for literal values first
|
|
469
|
-
if (ts.isStringLiteral(element)) {
|
|
470
|
-
return 'string';
|
|
471
|
-
} else if (ts.isNumericLiteral(element)) {
|
|
472
|
-
return 'number';
|
|
473
|
-
} else if (element.kind === ts.SyntaxKind.TrueKeyword || element.kind === ts.SyntaxKind.FalseKeyword) {
|
|
474
|
-
return 'boolean';
|
|
475
|
-
} else if (ts.isObjectLiteralExpression(element)) {
|
|
476
|
-
return 'object';
|
|
477
|
-
} else if (ts.isArrayLiteralExpression(element)) {
|
|
478
|
-
return 'array';
|
|
479
|
-
} else if (element.kind === ts.SyntaxKind.NullKeyword) {
|
|
480
|
-
return 'null';
|
|
481
|
-
} else if (element.kind === ts.SyntaxKind.UndefinedKeyword) {
|
|
482
|
-
return 'undefined';
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// For identifiers and other expressions, try to get the type
|
|
486
|
-
const typeString = getTypeOfNode(checker, element);
|
|
487
|
-
|
|
488
|
-
// Extract basic type from TypeScript type string
|
|
489
|
-
if (typeString.startsWith('"') || typeString.startsWith("'")) {
|
|
490
|
-
return 'string'; // String literal type
|
|
491
|
-
} else if (!isNaN(Number(typeString))) {
|
|
492
|
-
return 'number'; // Numeric literal type
|
|
493
|
-
} else if (typeString === 'true' || typeString === 'false') {
|
|
494
|
-
return 'boolean'; // Boolean literal type
|
|
495
|
-
} else if (typeString.includes('[]') || typeString.startsWith('Array<')) {
|
|
496
|
-
return 'array';
|
|
497
|
-
} else if (typeString === 'string' || typeString === 'number' || typeString === 'boolean' ||
|
|
498
|
-
typeString === 'object' || typeString === 'null' || typeString === 'undefined') {
|
|
499
|
-
return typeString;
|
|
500
|
-
} else if (typeString[0] === typeString[0].toUpperCase() && !typeString.includes('<')) {
|
|
501
|
-
// This looks like a custom type/interface, return 'object'
|
|
502
|
-
return 'object';
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
return 'any';
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
function resolveIdentifierToInitializer(checker, identifier, sourceFile) {
|
|
509
|
-
try {
|
|
510
|
-
const symbol = checker.getSymbolAtLocation(identifier);
|
|
511
|
-
if (!symbol || !symbol.valueDeclaration) return null;
|
|
512
|
-
|
|
513
|
-
const declaration = symbol.valueDeclaration;
|
|
514
|
-
|
|
515
|
-
// Handle variable declarations
|
|
516
|
-
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
517
|
-
return declaration.initializer;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Handle property assignments
|
|
521
|
-
if (ts.isPropertyAssignment(declaration) && declaration.initializer) {
|
|
522
|
-
return declaration.initializer;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// Handle parameter with default value
|
|
526
|
-
if (ts.isParameter(declaration) && declaration.initializer) {
|
|
527
|
-
return declaration.initializer;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
return null;
|
|
531
|
-
} catch (error) {
|
|
532
|
-
return null;
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
function resolveTypeToProperties(checker, typeString, visitedTypes = new Set()) {
|
|
537
|
-
// Prevent infinite recursion for circular references
|
|
538
|
-
if (visitedTypes.has(typeString)) {
|
|
539
|
-
return { type: typeString };
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// Handle primitive types
|
|
543
|
-
if (['string', 'number', 'boolean', 'any', 'unknown', 'null', 'undefined'].includes(typeString)) {
|
|
544
|
-
return { type: typeString };
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Handle array types
|
|
548
|
-
const arrayMatch = typeString.match(/^(.+)\[\]$/) || typeString.match(/^Array<(.+)>$/);
|
|
549
|
-
if (arrayMatch) {
|
|
550
|
-
const elementType = arrayMatch[1].trim();
|
|
551
|
-
visitedTypes.add(typeString);
|
|
552
|
-
const elementProps = resolveTypeToProperties(checker, elementType, visitedTypes);
|
|
553
|
-
return {
|
|
554
|
-
type: 'array',
|
|
555
|
-
items: elementProps
|
|
556
|
-
};
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Handle readonly array types
|
|
560
|
-
const readonlyArrayMatch = typeString.match(/^readonly (.+)\[\]$/) || typeString.match(/^ReadonlyArray<(.+)>$/);
|
|
561
|
-
if (readonlyArrayMatch) {
|
|
562
|
-
const elementType = readonlyArrayMatch[1].trim();
|
|
563
|
-
visitedTypes.add(typeString);
|
|
564
|
-
const elementProps = resolveTypeToProperties(checker, elementType, visitedTypes);
|
|
565
|
-
|
|
566
|
-
// If element type is a custom interface/type, we need to find its properties
|
|
567
|
-
if (elementProps.__unresolved && checker) {
|
|
568
|
-
// Try to find the type by name and extract its properties
|
|
569
|
-
// This would require access to the program's type checker context
|
|
570
|
-
// For now, mark it as object type but try to get properties later
|
|
571
|
-
return {
|
|
572
|
-
type: 'array',
|
|
573
|
-
items: {
|
|
574
|
-
type: 'object',
|
|
575
|
-
__needsResolution: elementType
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
return {
|
|
581
|
-
type: 'array',
|
|
582
|
-
items: elementProps
|
|
583
|
-
};
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// Try to find the type symbol and extract properties
|
|
587
|
-
try {
|
|
588
|
-
// This is a simplified approach - in a real implementation, we'd need access to the actual type node
|
|
589
|
-
// For now, we'll return the type as-is, but mark it as an object if it looks like a custom type
|
|
590
|
-
if (typeString[0] === typeString[0].toUpperCase() && !typeString.includes('<') && !typeString.includes('|') && !typeString.includes('&')) {
|
|
591
|
-
// Looks like a custom type/interface
|
|
592
|
-
return {
|
|
593
|
-
type: 'object',
|
|
594
|
-
__unresolved: typeString // Mark for later resolution
|
|
595
|
-
};
|
|
596
|
-
}
|
|
597
|
-
} catch (error) {
|
|
598
|
-
// Fall through
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
return { type: typeString };
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function extractInterfaceProperties(checker, type) {
|
|
605
|
-
const properties = {};
|
|
606
|
-
const typeSymbol = type.getSymbol();
|
|
607
|
-
|
|
608
|
-
if (!typeSymbol) return properties;
|
|
609
|
-
|
|
610
|
-
// Get all properties of the type
|
|
611
|
-
const members = checker.getPropertiesOfType(type);
|
|
612
|
-
|
|
613
|
-
for (const member of members) {
|
|
614
|
-
const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
|
|
615
|
-
const memberTypeString = checker.typeToString(memberType);
|
|
616
|
-
const isOptional = member.flags & ts.SymbolFlags.Optional;
|
|
617
|
-
|
|
618
|
-
// Recursively resolve the member type
|
|
619
|
-
const resolvedType = resolveTypeToProperties(checker, memberTypeString);
|
|
620
|
-
|
|
621
|
-
// If it's an unresolved object type, try to extract its properties
|
|
622
|
-
if (resolvedType.__unresolved) {
|
|
623
|
-
const nestedProperties = extractInterfaceProperties(checker, memberType);
|
|
624
|
-
if (Object.keys(nestedProperties).length > 0) {
|
|
625
|
-
properties[member.name] = {
|
|
626
|
-
type: 'object',
|
|
627
|
-
properties: nestedProperties
|
|
628
|
-
};
|
|
629
|
-
} else {
|
|
630
|
-
properties[member.name] = resolvedType;
|
|
631
|
-
}
|
|
632
|
-
} else {
|
|
633
|
-
properties[member.name] = resolvedType;
|
|
634
|
-
// Clean up any unresolved markers
|
|
635
|
-
if (properties[member.name].__unresolved) {
|
|
636
|
-
delete properties[member.name].__unresolved;
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
return properties;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
module.exports = {
|
|
645
|
-
detectSourceJs,
|
|
646
|
-
detectSourceTs,
|
|
647
|
-
findWrappingFunctionTs,
|
|
648
|
-
findWrappingFunctionJs,
|
|
649
|
-
extractJsProperties,
|
|
650
|
-
extractTsProperties,
|
|
651
|
-
getTypeOfNode,
|
|
652
|
-
getBasicTypeOfArrayElement,
|
|
653
|
-
resolveIdentifierToInitializer,
|
|
654
|
-
resolveTypeToProperties,
|
|
655
|
-
extractInterfaceProperties,
|
|
656
|
-
};
|