@flisk/analyze-tracking 0.7.2 → 0.7.3

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.
Files changed (64) hide show
  1. package/bin/cli.js +1 -1
  2. package/package.json +9 -7
  3. package/src/analyze/go/astTraversal.js +121 -0
  4. package/src/analyze/go/constants.js +20 -0
  5. package/src/analyze/go/eventDeduplicator.js +47 -0
  6. package/src/analyze/go/eventExtractor.js +156 -0
  7. package/src/analyze/go/goAstParser/constants.js +39 -0
  8. package/src/analyze/go/goAstParser/expressionParser.js +281 -0
  9. package/src/analyze/go/goAstParser/index.js +52 -0
  10. package/src/analyze/go/goAstParser/statementParser.js +387 -0
  11. package/src/analyze/go/goAstParser/tokenizer.js +196 -0
  12. package/src/analyze/go/goAstParser/typeParser.js +202 -0
  13. package/src/analyze/go/goAstParser/utils.js +99 -0
  14. package/src/analyze/go/index.js +55 -0
  15. package/src/analyze/go/propertyExtractor.js +670 -0
  16. package/src/analyze/go/trackingDetector.js +71 -0
  17. package/src/analyze/go/trackingExtractor.js +54 -0
  18. package/src/analyze/go/typeContext.js +88 -0
  19. package/src/analyze/go/utils.js +215 -0
  20. package/src/analyze/index.js +11 -6
  21. package/src/analyze/javascript/constants.js +115 -0
  22. package/src/analyze/javascript/detectors/analytics-source.js +119 -0
  23. package/src/analyze/javascript/detectors/index.js +10 -0
  24. package/src/analyze/javascript/extractors/event-extractor.js +179 -0
  25. package/src/analyze/javascript/extractors/index.js +13 -0
  26. package/src/analyze/javascript/extractors/property-extractor.js +172 -0
  27. package/src/analyze/javascript/index.js +38 -0
  28. package/src/analyze/javascript/parser.js +126 -0
  29. package/src/analyze/javascript/utils/function-finder.js +123 -0
  30. package/src/analyze/python/index.js +111 -0
  31. package/src/analyze/python/pythonTrackingAnalyzer.py +814 -0
  32. package/src/analyze/ruby/detectors.js +46 -0
  33. package/src/analyze/ruby/extractors.js +258 -0
  34. package/src/analyze/ruby/index.js +51 -0
  35. package/src/analyze/ruby/traversal.js +123 -0
  36. package/src/analyze/ruby/types.js +30 -0
  37. package/src/analyze/ruby/visitor.js +66 -0
  38. package/src/analyze/typescript/constants.js +109 -0
  39. package/src/analyze/typescript/detectors/analytics-source.js +120 -0
  40. package/src/analyze/typescript/detectors/index.js +10 -0
  41. package/src/analyze/typescript/extractors/event-extractor.js +269 -0
  42. package/src/analyze/typescript/extractors/index.js +14 -0
  43. package/src/analyze/typescript/extractors/property-extractor.js +395 -0
  44. package/src/analyze/typescript/index.js +48 -0
  45. package/src/analyze/typescript/parser.js +131 -0
  46. package/src/analyze/typescript/utils/function-finder.js +114 -0
  47. package/src/analyze/typescript/utils/type-resolver.js +193 -0
  48. package/src/generateDescriptions/index.js +81 -0
  49. package/src/generateDescriptions/llmUtils.js +33 -0
  50. package/src/generateDescriptions/promptUtils.js +62 -0
  51. package/src/generateDescriptions/schemaUtils.js +61 -0
  52. package/src/index.js +7 -2
  53. package/src/{fileProcessor.js → utils/fileProcessor.js} +5 -0
  54. package/src/{repoDetails.js → utils/repoDetails.js} +5 -0
  55. package/src/{yamlGenerator.js → utils/yamlGenerator.js} +5 -0
  56. package/src/analyze/analyzeGoFile.js +0 -1164
  57. package/src/analyze/analyzeJsFile.js +0 -87
  58. package/src/analyze/analyzePythonFile.js +0 -42
  59. package/src/analyze/analyzeRubyFile.js +0 -419
  60. package/src/analyze/analyzeTsFile.js +0 -192
  61. package/src/analyze/go2json.js +0 -1069
  62. package/src/analyze/helpers.js +0 -656
  63. package/src/analyze/pythonTrackingAnalyzer.py +0 -541
  64. package/src/generateDescriptions.js +0 -196
@@ -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
- };