@flisk/analyze-tracking 0.8.8 → 0.9.1

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.
@@ -18,24 +18,24 @@ function resolveIdentifierToInitializer(checker, identifier, sourceFile) {
18
18
  if (!symbol || !symbol.valueDeclaration) {
19
19
  return null;
20
20
  }
21
-
21
+
22
22
  const declaration = symbol.valueDeclaration;
23
-
23
+
24
24
  // Handle variable declarations
25
25
  if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
26
26
  return declaration.initializer;
27
27
  }
28
-
28
+
29
29
  // Handle property assignments
30
30
  if (ts.isPropertyAssignment(declaration) && declaration.initializer) {
31
31
  return declaration.initializer;
32
32
  }
33
-
33
+
34
34
  // Handle parameter with default value
35
35
  if (ts.isParameter(declaration) && declaration.initializer) {
36
36
  return declaration.initializer;
37
37
  }
38
-
38
+
39
39
  return null;
40
40
  } catch (error) {
41
41
  return null;
@@ -69,12 +69,12 @@ function resolveTypeToProperties(checker, typeString, visitedTypes = new Set())
69
69
  if (visitedTypes.has(typeString)) {
70
70
  return { type: 'object' };
71
71
  }
72
-
72
+
73
73
  // Handle primitive types
74
74
  if (['string', 'number', 'boolean', 'any', 'unknown', 'null', 'undefined', 'void', 'never'].includes(typeString)) {
75
75
  return { type: typeString };
76
76
  }
77
-
77
+
78
78
  // Handle array types: T[] or Array<T>
79
79
  const arrayMatch = typeString.match(/^(.+)\[\]$/) || typeString.match(/^Array<(.+)>$/);
80
80
  if (arrayMatch) {
@@ -86,7 +86,7 @@ function resolveTypeToProperties(checker, typeString, visitedTypes = new Set())
86
86
  items: elementProps
87
87
  };
88
88
  }
89
-
89
+
90
90
  // Handle readonly array types: readonly T[] or ReadonlyArray<T>
91
91
  const readonlyArrayMatch = typeString.match(/^readonly (.+)\[\]$/) || typeString.match(/^ReadonlyArray<(.+)>$/);
92
92
  if (readonlyArrayMatch) {
@@ -98,18 +98,37 @@ function resolveTypeToProperties(checker, typeString, visitedTypes = new Set())
98
98
  items: elementProps
99
99
  };
100
100
  }
101
-
102
- // Handle union types - preserve them as-is
101
+
102
+ // Handle union types
103
103
  if (typeString.includes('|')) {
104
+ // Try to extract the non-undefined/non-null type from the union
105
+ const resolvedUnion = resolveUnionType(checker, typeString, visitedTypes);
106
+ if (resolvedUnion) {
107
+ return resolvedUnion;
108
+ }
109
+ // Fallback: preserve as-is
104
110
  return { type: typeString };
105
111
  }
106
-
112
+
107
113
  // Handle intersection types
108
114
  if (typeString.includes('&')) {
109
115
  // For simplicity, mark intersection types as 'object'
110
116
  return { type: 'object' };
111
117
  }
112
-
118
+
119
+ // Check if it's an enum type - don't try to expand enum members
120
+ if (checker && isEnumType(checker, typeString)) {
121
+ const enumValues = getEnumValues(checker, typeString);
122
+ if (enumValues && enumValues.length > 0) {
123
+ return {
124
+ type: 'enum',
125
+ values: enumValues
126
+ };
127
+ }
128
+ // Fallback for string enums
129
+ return { type: 'string' };
130
+ }
131
+
113
132
  // Check if it looks like a custom type/interface
114
133
  if (isCustomType(typeString)) {
115
134
  return {
@@ -117,11 +136,245 @@ function resolveTypeToProperties(checker, typeString, visitedTypes = new Set())
117
136
  __unresolved: typeString
118
137
  };
119
138
  }
120
-
139
+
121
140
  // Default case - preserve the type string as-is
122
141
  return { type: typeString };
123
142
  }
124
143
 
144
+ /**
145
+ * Resolves a union type by extracting the meaningful type (ignoring undefined/null)
146
+ * @param {Object} checker - TypeScript type checker
147
+ * @param {string} typeString - Union type string
148
+ * @param {Set} visitedTypes - Set of visited types
149
+ * @returns {Object|null} Resolved type or null
150
+ */
151
+ function resolveUnionType(checker, typeString, visitedTypes) {
152
+ // Split by | and trim each part
153
+ const parts = splitUnionType(typeString);
154
+
155
+ // Filter out undefined and null
156
+ const meaningfulParts = parts.filter(p =>
157
+ p !== 'undefined' && p !== 'null' && p.trim() !== ''
158
+ );
159
+
160
+ if (meaningfulParts.length === 0) {
161
+ return { type: 'null' };
162
+ }
163
+
164
+ if (meaningfulParts.length === 1) {
165
+ const part = meaningfulParts[0].trim();
166
+
167
+ // Check if it's an object literal type like { id: string; name: string }
168
+ if (part.startsWith('{') && part.endsWith('}')) {
169
+ const properties = parseObjectLiteralType(part);
170
+ if (Object.keys(properties).length > 0) {
171
+ return {
172
+ type: 'object',
173
+ properties
174
+ };
175
+ }
176
+ }
177
+
178
+ // Recursively resolve the meaningful part
179
+ return resolveTypeToProperties(checker, part, visitedTypes);
180
+ }
181
+
182
+ // Multiple meaningful parts - try to find the most specific one
183
+ // Prefer object types over primitives
184
+ for (const part of meaningfulParts) {
185
+ const trimmed = part.trim();
186
+ if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
187
+ const properties = parseObjectLiteralType(trimmed);
188
+ if (Object.keys(properties).length > 0) {
189
+ return {
190
+ type: 'object',
191
+ properties
192
+ };
193
+ }
194
+ }
195
+ }
196
+
197
+ // Return null to indicate we couldn't resolve it
198
+ return null;
199
+ }
200
+
201
+ /**
202
+ * Splits a union type string into its constituent parts, handling nested braces
203
+ * @param {string} typeString - Union type string
204
+ * @returns {string[]} Array of type parts
205
+ */
206
+ function splitUnionType(typeString) {
207
+ const parts = [];
208
+ let current = '';
209
+ let depth = 0;
210
+ let parenDepth = 0;
211
+ let angleDepth = 0;
212
+
213
+ for (let i = 0; i < typeString.length; i++) {
214
+ const char = typeString[i];
215
+
216
+ if (char === '{') depth++;
217
+ else if (char === '}') depth--;
218
+ else if (char === '(') parenDepth++;
219
+ else if (char === ')') parenDepth--;
220
+ else if (char === '<') angleDepth++;
221
+ else if (char === '>') angleDepth--;
222
+
223
+ if (char === '|' && depth === 0 && parenDepth === 0 && angleDepth === 0) {
224
+ parts.push(current.trim());
225
+ current = '';
226
+ } else {
227
+ current += char;
228
+ }
229
+ }
230
+
231
+ if (current.trim()) {
232
+ parts.push(current.trim());
233
+ }
234
+
235
+ return parts;
236
+ }
237
+
238
+ /**
239
+ * Parses an object literal type string like "{ id: string; name: string }"
240
+ * @param {string} typeString - Object literal type string
241
+ * @returns {Object} Parsed properties
242
+ */
243
+ function parseObjectLiteralType(typeString) {
244
+ const properties = {};
245
+
246
+ // Remove outer braces and trim
247
+ let inner = typeString.slice(1, -1).trim();
248
+ if (!inner) return properties;
249
+
250
+ // Split by semicolons (property separators), handling nested braces
251
+ const propStrings = splitBySemicolon(inner);
252
+
253
+ for (const propString of propStrings) {
254
+ const trimmed = propString.trim();
255
+ if (!trimmed) continue;
256
+
257
+ // Parse "key: type" or "key?: type"
258
+ const colonIndex = findPropertyColonIndex(trimmed);
259
+ if (colonIndex === -1) continue;
260
+
261
+ let key = trimmed.slice(0, colonIndex).trim();
262
+ const typeStr = trimmed.slice(colonIndex + 1).trim();
263
+
264
+ // Handle optional properties (key?)
265
+ if (key.endsWith('?')) {
266
+ key = key.slice(0, -1);
267
+ }
268
+
269
+ if (!key) continue;
270
+
271
+ // Resolve the property type
272
+ properties[key] = resolvePropertyType(typeStr);
273
+ }
274
+
275
+ return properties;
276
+ }
277
+
278
+ /**
279
+ * Splits a string by semicolons, respecting nested structures
280
+ * @param {string} str - String to split
281
+ * @returns {string[]} Array of parts
282
+ */
283
+ function splitBySemicolon(str) {
284
+ const parts = [];
285
+ let current = '';
286
+ let depth = 0;
287
+ let parenDepth = 0;
288
+ let angleDepth = 0;
289
+
290
+ for (let i = 0; i < str.length; i++) {
291
+ const char = str[i];
292
+
293
+ if (char === '{') depth++;
294
+ else if (char === '}') depth--;
295
+ else if (char === '(') parenDepth++;
296
+ else if (char === ')') parenDepth--;
297
+ else if (char === '<') angleDepth++;
298
+ else if (char === '>') angleDepth--;
299
+
300
+ if (char === ';' && depth === 0 && parenDepth === 0 && angleDepth === 0) {
301
+ parts.push(current.trim());
302
+ current = '';
303
+ } else {
304
+ current += char;
305
+ }
306
+ }
307
+
308
+ if (current.trim()) {
309
+ parts.push(current.trim());
310
+ }
311
+
312
+ return parts;
313
+ }
314
+
315
+ /**
316
+ * Finds the index of the colon separating property name from type
317
+ * @param {string} str - Property string
318
+ * @returns {number} Index of the colon or -1
319
+ */
320
+ function findPropertyColonIndex(str) {
321
+ // Find the first colon that's not inside nested structures
322
+ let depth = 0;
323
+ let parenDepth = 0;
324
+ let angleDepth = 0;
325
+
326
+ for (let i = 0; i < str.length; i++) {
327
+ const char = str[i];
328
+
329
+ if (char === '{') depth++;
330
+ else if (char === '}') depth--;
331
+ else if (char === '(') parenDepth++;
332
+ else if (char === ')') parenDepth--;
333
+ else if (char === '<') angleDepth++;
334
+ else if (char === '>') angleDepth--;
335
+ else if (char === ':' && depth === 0 && parenDepth === 0 && angleDepth === 0) {
336
+ return i;
337
+ }
338
+ }
339
+
340
+ return -1;
341
+ }
342
+
343
+ /**
344
+ * Resolves a property type string to a schema
345
+ * @param {string} typeStr - Type string
346
+ * @returns {Object} Property schema
347
+ */
348
+ function resolvePropertyType(typeStr) {
349
+ const trimmed = typeStr.trim();
350
+
351
+ // Handle primitive types
352
+ if (['string', 'number', 'boolean', 'any', 'unknown', 'null', 'undefined'].includes(trimmed)) {
353
+ return { type: trimmed };
354
+ }
355
+
356
+ // Handle array types
357
+ if (trimmed.endsWith('[]')) {
358
+ const elementType = trimmed.slice(0, -2).trim();
359
+ return {
360
+ type: 'array',
361
+ items: resolvePropertyType(elementType)
362
+ };
363
+ }
364
+
365
+ // Handle nested object types
366
+ if (trimmed.startsWith('{') && trimmed.endsWith('}')) {
367
+ const nestedProps = parseObjectLiteralType(trimmed);
368
+ return {
369
+ type: 'object',
370
+ properties: nestedProps
371
+ };
372
+ }
373
+
374
+ // For other types, return as-is
375
+ return { type: trimmed };
376
+ }
377
+
125
378
  /**
126
379
  * Checks if a type string represents a custom type or interface
127
380
  * @param {string} typeString - Type string to check
@@ -129,9 +382,9 @@ function resolveTypeToProperties(checker, typeString, visitedTypes = new Set())
129
382
  */
130
383
  function isCustomType(typeString) {
131
384
  // Custom types typically start with uppercase and don't contain certain characters
132
- return typeString[0] === typeString[0].toUpperCase() &&
133
- !typeString.includes('<') &&
134
- !typeString.includes('|') &&
385
+ return typeString[0] === typeString[0].toUpperCase() &&
386
+ !typeString.includes('<') &&
387
+ !typeString.includes('|') &&
135
388
  !typeString.includes('&') &&
136
389
  !typeString.includes('(') &&
137
390
  !typeString.includes('[');
@@ -145,7 +398,7 @@ function isCustomType(typeString) {
145
398
  */
146
399
  function getBasicTypeOfArrayElement(checker, element) {
147
400
  if (!element || typeof element.kind === 'undefined') return 'any';
148
-
401
+
149
402
  // Check for literal values first
150
403
  if (ts.isStringLiteral(element)) {
151
404
  return 'string';
@@ -162,10 +415,10 @@ function getBasicTypeOfArrayElement(checker, element) {
162
415
  } else if (element.kind === ts.SyntaxKind.UndefinedKeyword) {
163
416
  return 'undefined';
164
417
  }
165
-
418
+
166
419
  // For identifiers and other expressions, try to get the type
167
420
  const typeString = getTypeOfNode(checker, element);
168
-
421
+
169
422
  // Extract basic type from TypeScript type string
170
423
  if (typeString.startsWith('"') || typeString.startsWith("'")) {
171
424
  return 'string'; // String literal type
@@ -180,7 +433,7 @@ function getBasicTypeOfArrayElement(checker, element) {
180
433
  } else if (isCustomType(typeString)) {
181
434
  return 'object';
182
435
  }
183
-
436
+
184
437
  return 'any';
185
438
  }
186
439
 
@@ -198,11 +451,337 @@ function isReactHookCall(node, hookNames = ['useCallback', 'useState', 'useEffec
198
451
  return false;
199
452
  }
200
453
 
454
+ /**
455
+ * Checks if a type is an enum type by examining its symbol
456
+ * @param {Object} checker - TypeScript type checker
457
+ * @param {string} typeString - Type string to check
458
+ * @param {Object} [type] - Optional TypeScript type object
459
+ * @returns {boolean}
460
+ */
461
+ function isEnumType(checker, typeString, type = null) {
462
+ if (!checker || !typeString) return false;
463
+
464
+ // Check if it's a simple enum type name (not a union)
465
+ if (typeString.includes('|') || typeString.includes('&')) {
466
+ return false;
467
+ }
468
+
469
+ // Skip primitive types
470
+ if (['string', 'number', 'boolean', 'any', 'unknown', 'null', 'undefined', 'void'].includes(typeString)) {
471
+ return false;
472
+ }
473
+
474
+ // If we have a type object, check its symbol flags
475
+ if (type && type.symbol) {
476
+ // Check if the symbol has the Enum flag
477
+ if (type.symbol.flags & ts.SymbolFlags.Enum) {
478
+ return true;
479
+ }
480
+ // Check if the symbol has the EnumMember flag (for individual enum values)
481
+ if (type.symbol.flags & ts.SymbolFlags.EnumMember) {
482
+ return true;
483
+ }
484
+ }
485
+
486
+ return false;
487
+ }
488
+
489
+ /**
490
+ * Gets the values of an enum type from its type object
491
+ * @param {Object} checker - TypeScript type checker
492
+ * @param {string} typeString - Enum type name
493
+ * @param {Object} [type] - Optional TypeScript type object
494
+ * @returns {string[]|null} Array of enum values or null
495
+ */
496
+ function getEnumValues(checker, typeString, type = null) {
497
+ if (!checker || !typeString) return null;
498
+
499
+ try {
500
+ let enumSymbol = null;
501
+
502
+ if (type && type.symbol) {
503
+ // Check if this is an enum member (e.g., SubscriptionType.MONTHLY)
504
+ if (type.symbol.flags & ts.SymbolFlags.EnumMember) {
505
+ // Get the parent enum
506
+ enumSymbol = type.symbol.parent;
507
+ }
508
+ // Check if this is the enum type itself
509
+ else if (type.symbol.flags & ts.SymbolFlags.Enum) {
510
+ enumSymbol = type.symbol;
511
+ }
512
+ }
513
+
514
+ if (enumSymbol && enumSymbol.exports) {
515
+ const values = [];
516
+ enumSymbol.exports.forEach((member, name) => {
517
+ // Get the value of each enum member
518
+ if (member.declarations && member.declarations.length > 0) {
519
+ const decl = member.declarations[0];
520
+ if (ts.isEnumMember(decl) && decl.initializer) {
521
+ if (ts.isStringLiteral(decl.initializer)) {
522
+ values.push(decl.initializer.text);
523
+ } else if (ts.isNumericLiteral(decl.initializer)) {
524
+ values.push(Number(decl.initializer.text));
525
+ }
526
+ }
527
+ }
528
+ });
529
+ if (values.length > 0) {
530
+ return values;
531
+ }
532
+ }
533
+ } catch (e) {
534
+ // Ignore errors
535
+ }
536
+
537
+ return null;
538
+ }
539
+
540
+ /**
541
+ * Resolves a TypeScript type object to a property schema
542
+ * This is more accurate than resolving from type strings
543
+ * @param {Object} checker - TypeScript type checker
544
+ * @param {Object} type - TypeScript Type object
545
+ * @param {Set} [visitedTypes] - Set of visited types to prevent cycles
546
+ * @returns {Object} Property schema
547
+ */
548
+ function resolveTypeObjectToSchema(checker, type, visitedTypes = new Set()) {
549
+ if (!type) return { type: 'any' };
550
+
551
+ const typeString = checker.typeToString(type);
552
+
553
+ // Prevent infinite recursion
554
+ if (visitedTypes.has(typeString)) {
555
+ return { type: 'object' };
556
+ }
557
+
558
+ // Handle union types
559
+ if (type.isUnion?.()) {
560
+ return resolveUnionTypeObject(checker, type, visitedTypes);
561
+ }
562
+
563
+ // Handle enum types (actual enum declarations)
564
+ if (isEnumType(checker, typeString, type)) {
565
+ const enumValues = getEnumValues(checker, typeString, type);
566
+ if (enumValues && enumValues.length > 0) {
567
+ return { type: 'enum', values: enumValues };
568
+ }
569
+ return { type: 'string' };
570
+ }
571
+
572
+ // Handle primitive types
573
+ const flags = type.flags;
574
+ if (flags & ts.TypeFlags.String || flags & ts.TypeFlags.StringLiteral) {
575
+ return { type: 'string' };
576
+ }
577
+ if (flags & ts.TypeFlags.Number || flags & ts.TypeFlags.NumberLiteral) {
578
+ return { type: 'number' };
579
+ }
580
+ if (flags & ts.TypeFlags.Boolean || flags & ts.TypeFlags.BooleanLiteral) {
581
+ return { type: 'boolean' };
582
+ }
583
+ if (flags & ts.TypeFlags.Undefined) {
584
+ return { type: 'undefined' };
585
+ }
586
+ if (flags & ts.TypeFlags.Null) {
587
+ return { type: 'null' };
588
+ }
589
+
590
+ // Handle array types
591
+ if (checker.isArrayType?.(type) || typeString.endsWith('[]') || typeString.startsWith('Array<')) {
592
+ let elementType = null;
593
+ if (type.typeArguments && type.typeArguments.length > 0) {
594
+ elementType = type.typeArguments[0];
595
+ }
596
+ if (elementType) {
597
+ visitedTypes.add(typeString);
598
+ return {
599
+ type: 'array',
600
+ items: resolveTypeObjectToSchema(checker, elementType, visitedTypes)
601
+ };
602
+ }
603
+ return { type: 'array', items: { type: 'any' } };
604
+ }
605
+
606
+ // Handle object types - try to extract properties
607
+ if (flags & ts.TypeFlags.Object) {
608
+ visitedTypes.add(typeString);
609
+ const properties = extractTypeProperties(checker, type, visitedTypes);
610
+ if (Object.keys(properties).length > 0) {
611
+ return { type: 'object', properties };
612
+ }
613
+ return { type: 'object' };
614
+ }
615
+
616
+ // Fallback
617
+ return resolveTypeToProperties(checker, typeString, visitedTypes);
618
+ }
619
+
620
+ /**
621
+ * Resolves a union type object to a property schema
622
+ * Handles string literal unions and optional types
623
+ * @param {Object} checker - TypeScript type checker
624
+ * @param {Object} type - Union type object
625
+ * @param {Set} visitedTypes - Set of visited types
626
+ * @returns {Object} Property schema
627
+ */
628
+ function resolveUnionTypeObject(checker, type, visitedTypes) {
629
+ const types = type.types || [];
630
+
631
+ // Filter out undefined and null
632
+ const meaningfulTypes = types.filter(t => {
633
+ const str = checker.typeToString(t);
634
+ return str !== 'undefined' && str !== 'null';
635
+ });
636
+
637
+ if (meaningfulTypes.length === 0) {
638
+ return { type: 'null' };
639
+ }
640
+
641
+ // Check if all remaining types are string literals -> treat as enum
642
+ const allStringLiterals = meaningfulTypes.every(t => t.isStringLiteral?.());
643
+ if (allStringLiterals) {
644
+ const values = meaningfulTypes.map(t => getStringLiteralValue(t, checker));
645
+ return { type: 'enum', values };
646
+ }
647
+
648
+ // Check if all remaining types are number literals -> treat as enum
649
+ const allNumberLiterals = meaningfulTypes.every(t => t.isNumberLiteral?.());
650
+ if (allNumberLiterals) {
651
+ const values = meaningfulTypes.map(t => getNumberLiteralValue(t, checker));
652
+ return { type: 'enum', values };
653
+ }
654
+
655
+ // If only one meaningful type remains, resolve it
656
+ if (meaningfulTypes.length === 1) {
657
+ return resolveTypeObjectToSchema(checker, meaningfulTypes[0], visitedTypes);
658
+ }
659
+
660
+ // Multiple complex types - try to find the most specific one
661
+ // Prefer object types
662
+ for (const t of meaningfulTypes) {
663
+ if (t.flags & ts.TypeFlags.Object) {
664
+ return resolveTypeObjectToSchema(checker, t, visitedTypes);
665
+ }
666
+ }
667
+
668
+ // Fallback to type string
669
+ const typeString = checker.typeToString(type);
670
+ return { type: typeString };
671
+ }
672
+
673
+ /**
674
+ * Gets the actual string value from a string literal type
675
+ * Handles both regular string literals and enum member string literals
676
+ * @param {Object} type - TypeScript type
677
+ * @param {Object} checker - TypeScript type checker
678
+ * @returns {string} The actual string value
679
+ */
680
+ function getStringLiteralValue(type, checker) {
681
+ // Check if this is an enum member - get the actual value from the initializer
682
+ if (type.symbol && (type.symbol.flags & ts.SymbolFlags.EnumMember)) {
683
+ const valueDecl = type.symbol.valueDeclaration;
684
+ if (valueDecl && ts.isEnumMember(valueDecl) && valueDecl.initializer) {
685
+ if (ts.isStringLiteral(valueDecl.initializer)) {
686
+ return valueDecl.initializer.text;
687
+ }
688
+ }
689
+ }
690
+
691
+ // For regular string literals, remove quotes from the type string
692
+ const str = checker.typeToString(type);
693
+ return str.replace(/^["']|["']$/g, '');
694
+ }
695
+
696
+ /**
697
+ * Gets the actual number value from a number literal type
698
+ * @param {Object} type - TypeScript type
699
+ * @param {Object} checker - TypeScript type checker
700
+ * @returns {number} The actual number value
701
+ */
702
+ function getNumberLiteralValue(type, checker) {
703
+ // Check if this is an enum member - get the actual value from the initializer
704
+ if (type.symbol && (type.symbol.flags & ts.SymbolFlags.EnumMember)) {
705
+ const valueDecl = type.symbol.valueDeclaration;
706
+ if (valueDecl && ts.isEnumMember(valueDecl) && valueDecl.initializer) {
707
+ if (ts.isNumericLiteral(valueDecl.initializer)) {
708
+ return Number(valueDecl.initializer.text);
709
+ }
710
+ }
711
+ }
712
+
713
+ return Number(checker.typeToString(type));
714
+ }
715
+
716
+ /**
717
+ * Extracts properties from a TypeScript type object
718
+ * @param {Object} checker - TypeScript type checker
719
+ * @param {Object} type - TypeScript Type object
720
+ * @param {Set} visitedTypes - Set of visited types
721
+ * @returns {Object} Properties map
722
+ */
723
+ function extractTypeProperties(checker, type, visitedTypes) {
724
+ const properties = {};
725
+
726
+ try {
727
+ const members = checker.getPropertiesOfType(type);
728
+
729
+ for (const member of members) {
730
+ const name = member.name;
731
+
732
+ // Skip functions and common built-in methods
733
+ if (STRING_PROTOTYPE_METHODS.has(name) || name.startsWith('__@')) {
734
+ continue;
735
+ }
736
+
737
+ try {
738
+ const memberType = checker.getTypeOfSymbolAtLocation(member, member.valueDeclaration);
739
+ const memberTypeString = checker.typeToString(memberType);
740
+
741
+ // Skip function types
742
+ if (memberTypeString.includes('=>') || memberTypeString.startsWith('(')) {
743
+ continue;
744
+ }
745
+
746
+ // Recursively resolve the member type
747
+ const resolved = resolveTypeObjectToSchema(checker, memberType, visitedTypes);
748
+ properties[name] = resolved;
749
+ } catch (e) {
750
+ properties[name] = { type: 'any' };
751
+ }
752
+ }
753
+ } catch (e) {
754
+ // Ignore errors
755
+ }
756
+
757
+ return properties;
758
+ }
759
+
760
+ /**
761
+ * Set of string prototype methods to filter out
762
+ */
763
+ const STRING_PROTOTYPE_METHODS = new Set([
764
+ 'toString', 'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf',
765
+ 'localeCompare', 'match', 'replace', 'search', 'slice', 'split',
766
+ 'substring', 'toLowerCase', 'toLocaleLowerCase', 'toUpperCase',
767
+ 'toLocaleUpperCase', 'trim', 'length', 'substr', 'valueOf',
768
+ 'codePointAt', 'includes', 'endsWith', 'normalize', 'repeat',
769
+ 'startsWith', 'anchor', 'big', 'blink', 'bold', 'fixed',
770
+ 'fontcolor', 'fontsize', 'italics', 'link', 'small', 'strike',
771
+ 'sub', 'sup', 'padStart', 'padEnd', 'trimEnd', 'trimStart',
772
+ 'trimLeft', 'trimRight', 'matchAll', 'replaceAll', 'at',
773
+ 'isWellFormed', 'toWellFormed'
774
+ ]);
775
+
201
776
  module.exports = {
202
777
  resolveIdentifierToInitializer,
203
778
  getTypeOfNode,
204
779
  resolveTypeToProperties,
205
780
  isCustomType,
206
781
  getBasicTypeOfArrayElement,
207
- isReactHookCall
782
+ isReactHookCall,
783
+ isEnumType,
784
+ getEnumValues,
785
+ resolveTypeObjectToSchema,
786
+ extractTypeProperties
208
787
  };