ts-class-to-openapi 1.3.1 → 1.3.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 (45) hide show
  1. package/dist/index.cjs +985 -0
  2. package/dist/index.d.cts +56 -0
  3. package/dist/index.d.mts +58 -0
  4. package/dist/index.mjs +960 -0
  5. package/package.json +26 -22
  6. package/dist/__test__/entities/additional-test-classes.d.ts +0 -12
  7. package/dist/__test__/entities/circular-reference-cases.d.ts +0 -1
  8. package/dist/__test__/entities/circular-reference-classes.d.ts +0 -110
  9. package/dist/__test__/entities/collision/arrays/number-array.d.ts +0 -3
  10. package/dist/__test__/entities/collision/arrays/string-array.d.ts +0 -3
  11. package/dist/__test__/entities/collision/number-props/same-name.d.ts +0 -5
  12. package/dist/__test__/entities/collision/string-props/same-name.d.ts +0 -5
  13. package/dist/__test__/entities/collision/throwing/class-a.d.ts +0 -4
  14. package/dist/__test__/entities/collision/throwing/class-b.d.ts +0 -4
  15. package/dist/__test__/entities/complex-circular-dependencies.d.ts +0 -71
  16. package/dist/__test__/entities/decorated-classes.d.ts +0 -54
  17. package/dist/__test__/entities/deep-nested-classes.d.ts +0 -1
  18. package/dist/__test__/entities/enum-classes.d.ts +0 -30
  19. package/dist/__test__/entities/evaluation/generics.d.ts +0 -6
  20. package/dist/__test__/entities/evaluation/modifiers.d.ts +0 -7
  21. package/dist/__test__/entities/generic-circular-classes.d.ts +0 -57
  22. package/dist/__test__/entities/nested-classes.d.ts +0 -70
  23. package/dist/__test__/entities/nested-reuse-classes.d.ts +0 -43
  24. package/dist/__test__/entities/pure-classes.d.ts +0 -37
  25. package/dist/__test__/entities/schema-validation-classes.d.ts +0 -35
  26. package/dist/__test__/index.d.ts +0 -8
  27. package/dist/__test__/test.d.ts +0 -1
  28. package/dist/__test__/testCases/collision-advanced.test.d.ts +0 -1
  29. package/dist/__test__/testCases/collision.test.d.ts +0 -1
  30. package/dist/__test__/testCases/decorated-classes.test.d.ts +0 -1
  31. package/dist/__test__/testCases/edge-cases.test.d.ts +0 -1
  32. package/dist/__test__/testCases/enum-properties.test.d.ts +0 -1
  33. package/dist/__test__/testCases/generics-and-modifiers.test.d.ts +0 -1
  34. package/dist/__test__/testCases/nested-classes.test.d.ts +0 -1
  35. package/dist/__test__/testCases/nested-reuse.test.d.ts +0 -1
  36. package/dist/__test__/testCases/pure-classes.test.d.ts +0 -1
  37. package/dist/__test__/testCases/schema-validation.test.d.ts +0 -1
  38. package/dist/index.d.ts +0 -3
  39. package/dist/index.esm.js +0 -1224
  40. package/dist/index.js +0 -1226
  41. package/dist/run.d.ts +0 -1
  42. package/dist/run.js +0 -1308
  43. package/dist/transformer.d.ts +0 -5
  44. package/dist/transformer.fixtures.d.ts +0 -170
  45. package/dist/types.d.ts +0 -97
package/dist/index.esm.js DELETED
@@ -1,1224 +0,0 @@
1
- import ts from 'typescript';
2
- import path from 'path';
3
-
4
- const TS_CONFIG_DEFAULT_PATH = path.resolve(process.cwd(), 'tsconfig.json');
5
- const jsPrimitives = {
6
- String: { type: 'String', value: 'string' },
7
- Any: { type: 'Any'},
8
- Unknown: { type: 'Unknown'},
9
- Number: { type: 'Number', value: 'number', format: 'double' },
10
- Boolean: { type: 'Boolean', value: 'boolean' },
11
- Symbol: { type: 'Symbol', value: 'string' },
12
- BigInt: { type: 'BigInt', value: 'integer', format: 'int64' },
13
- null: { type: 'null'},
14
- Object: { type: 'Object', value: 'object' },
15
- Array: { type: 'Array', value: 'array' },
16
- Date: { type: 'Date', value: 'string', format: 'date-time' },
17
- Buffer: { type: 'Buffer'},
18
- Uint8Array: { type: 'Uint8Array'},
19
- UploadFile: { type: 'UploadFile', value: 'string', format: 'binary' },
20
- File: { type: 'File'},
21
- };
22
- const validatorDecorators = {
23
- Length: { name: 'Length'},
24
- MinLength: { name: 'MinLength'},
25
- MaxLength: { name: 'MaxLength'},
26
- IsInt: { name: 'IsInt', type: 'integer', format: 'int32' },
27
- IsNumber: { name: 'IsNumber', type: 'number'},
28
- IsString: { name: 'IsString', type: 'string'},
29
- IsPositive: { name: 'IsPositive'},
30
- IsDate: { name: 'IsDate', type: 'string', format: 'date-time' },
31
- IsEmail: { name: 'IsEmail', format: 'email' },
32
- IsNotEmpty: { name: 'IsNotEmpty' },
33
- IsOptional: { name: 'IsOptional' },
34
- IsBoolean: { name: 'IsBoolean', type: 'boolean' },
35
- IsArray: { name: 'IsArray'},
36
- Min: { name: 'Min' },
37
- Max: { name: 'Max' },
38
- ArrayNotEmpty: { name: 'ArrayNotEmpty' },
39
- ArrayMaxSize: { name: 'ArrayMaxSize' },
40
- ArrayMinSize: { name: 'ArrayMinSize' },
41
- IsEnum: { name: 'IsEnum'},
42
- };
43
- const constants = {
44
- TS_CONFIG_DEFAULT_PATH,
45
- jsPrimitives,
46
- validatorDecorators};
47
-
48
- class SchemaTransformer {
49
- static instance = null;
50
- program;
51
- checker;
52
- classCache = new Map();
53
- maxCacheSize;
54
- autoCleanup;
55
- loadedFiles = new Set();
56
- processingClasses = new Set();
57
- sourceFiles;
58
- classFileIndex = new Map();
59
- constructor(tsConfigPath = constants.TS_CONFIG_DEFAULT_PATH, options = {}) {
60
- this.maxCacheSize = options.maxCacheSize ?? 100;
61
- this.autoCleanup = options.autoCleanup ?? true;
62
- const { config, error } = ts.readConfigFile(tsConfigPath || 'tsconfig.json', ts.sys.readFile);
63
- if (error) {
64
- console.log(new Error(`Error reading tsconfig file: ${error.messageText}`).message);
65
- throw new Error(`Error reading tsconfig file: ${error.messageText}`);
66
- }
67
- const { options: tsOptions, fileNames } = ts.parseJsonConfigFileContent(config, ts.sys, './');
68
- this.program = ts.createProgram(fileNames, tsOptions);
69
- this.checker = this.program.getTypeChecker();
70
- this.sourceFiles = this.program.getSourceFiles().filter(sf => {
71
- if (sf.isDeclarationFile)
72
- return false;
73
- if (sf.fileName.includes('.d.ts'))
74
- return false;
75
- if (sf.fileName.includes('node_modules'))
76
- return false;
77
- return true;
78
- });
79
- this.sourceFiles.forEach(sf => {
80
- sf.statements.forEach(stmt => {
81
- if (ts.isClassDeclaration(stmt) && stmt.name) {
82
- const name = stmt.name.text;
83
- const entry = this.classFileIndex.get(name) || [];
84
- entry.push({ sourceFile: sf, node: stmt });
85
- this.classFileIndex.set(name, entry);
86
- }
87
- });
88
- });
89
- }
90
- getPropertiesByClassDeclaration(classNode, visitedDeclarations = new Set(), genericTypeMap = new Map()) {
91
- if (visitedDeclarations.has(classNode)) {
92
- return [];
93
- }
94
- visitedDeclarations.add(classNode);
95
- // if no heritage clauses, get properties directly from class
96
- if (!classNode.heritageClauses) {
97
- return this.getPropertiesByClassMembers(classNode.members, classNode, genericTypeMap);
98
- } // use heritage clauses to get properties from base classes
99
- else {
100
- const heritageClause = classNode.heritageClauses[0];
101
- if (heritageClause &&
102
- heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {
103
- const type = heritageClause.types[0];
104
- let properties = [];
105
- let baseProperties = [];
106
- if (!type)
107
- return [];
108
- const symbol = this.checker.getSymbolAtLocation(type.expression);
109
- if (!symbol)
110
- return [];
111
- const declaration = symbol.declarations?.[0];
112
- if (declaration && ts.isClassDeclaration(declaration)) {
113
- const newGenericTypeMap = new Map();
114
- if (declaration.typeParameters && type.typeArguments) {
115
- declaration.typeParameters.forEach((param, index) => {
116
- const arg = type.typeArguments[index];
117
- if (arg) {
118
- const resolvedArg = this.getTypeNodeToString(arg, genericTypeMap);
119
- newGenericTypeMap.set(param.name.text, resolvedArg);
120
- }
121
- });
122
- }
123
- baseProperties = this.getPropertiesByClassDeclaration(declaration, visitedDeclarations, newGenericTypeMap);
124
- }
125
- properties = this.getPropertiesByClassMembers(classNode.members, classNode, genericTypeMap);
126
- return baseProperties.concat(properties);
127
- }
128
- else {
129
- return this.getPropertiesByClassMembers(classNode.members, classNode, genericTypeMap);
130
- }
131
- }
132
- }
133
- getPropertiesByClassMembers(members, parentClassNode, genericTypeMap = new Map()) {
134
- const properties = [];
135
- for (const member of members) {
136
- if (ts.isPropertyDeclaration(member) &&
137
- member.name &&
138
- ts.isIdentifier(member.name)) {
139
- // Skip static, private, and protected properties
140
- if (member.modifiers) {
141
- const hasExcludedModifier = member.modifiers.some(m => m.kind === ts.SyntaxKind.StaticKeyword ||
142
- m.kind === ts.SyntaxKind.PrivateKeyword ||
143
- m.kind === ts.SyntaxKind.ProtectedKeyword);
144
- if (hasExcludedModifier)
145
- continue;
146
- }
147
- const propertyName = member.name.text;
148
- const type = this.getPropertyType(member, genericTypeMap);
149
- const decorators = this.extractDecorators(member);
150
- const isOptional = !!member.questionToken;
151
- const isGeneric = this.isPropertyTypeGeneric(member);
152
- const isEnum = this.isEnum(member);
153
- const isPrimitive = this.isPrimitiveType(type) || isEnum;
154
- const isClassType = this.isClassType(member);
155
- const isArray = this.isArrayProperty(member);
156
- const isTypeLiteral = this.isTypeLiteral(member);
157
- const property = {
158
- name: propertyName,
159
- type,
160
- decorators,
161
- isOptional,
162
- isGeneric,
163
- originalProperty: member,
164
- isPrimitive,
165
- isClassType,
166
- isArray,
167
- isEnum,
168
- isRef: false,
169
- isTypeLiteral: isTypeLiteral,
170
- };
171
- // Check for self-referencing properties to mark as $ref
172
- if (property.isClassType) {
173
- const declaration = this.getDeclarationProperty(property);
174
- if (parentClassNode) {
175
- if (declaration &&
176
- declaration.name &&
177
- this.checker.getSymbolAtLocation(declaration.name) ===
178
- this.checker.getSymbolAtLocation(parentClassNode.name)) {
179
- property.isRef = true;
180
- }
181
- }
182
- }
183
- if (property.isTypeLiteral &&
184
- property.originalProperty.type &&
185
- property.originalProperty.type
186
- .typeArguments?.length === 1) {
187
- const typeArguments = property.originalProperty.type.typeArguments;
188
- if (typeArguments && typeArguments[0]) {
189
- const firstTypeArg = typeArguments[0];
190
- if (ts.isTypeReferenceNode(firstTypeArg)) {
191
- const type = this.checker.getTypeAtLocation(firstTypeArg);
192
- const symbol = type.getSymbol();
193
- if (symbol && symbol.declarations) {
194
- const classDeclaration = symbol.declarations.find(decl => ts.isClassDeclaration(decl));
195
- if (classDeclaration &&
196
- ts.isClassDeclaration(classDeclaration)) {
197
- property.typeLiteralClassReference = classDeclaration;
198
- }
199
- }
200
- }
201
- }
202
- }
203
- properties.push(property);
204
- }
205
- }
206
- return properties;
207
- }
208
- getPropertyType(property, genericTypeMap = new Map()) {
209
- if (property.type) {
210
- return this.getTypeNodeToString(property.type, genericTypeMap);
211
- }
212
- const type = this.checker.getTypeAtLocation(property);
213
- return this.getStringFromType(type);
214
- }
215
- getTypeNodeToString(typeNode, genericTypeMap = new Map()) {
216
- if (ts.isTypeReferenceNode(typeNode) &&
217
- ts.isIdentifier(typeNode.typeName)) {
218
- const typeName = typeNode.typeName.text;
219
- if (genericTypeMap.has(typeName)) {
220
- return genericTypeMap.get(typeName);
221
- }
222
- if (typeName.toLowerCase() === 'uploadfile') {
223
- return 'UploadFile';
224
- }
225
- if (typeNode.typeArguments && typeNode.typeArguments.length > 0) {
226
- const firstTypeArg = typeNode.typeArguments[0];
227
- if (firstTypeArg &&
228
- ts.isTypeReferenceNode(firstTypeArg) &&
229
- ts.isIdentifier(firstTypeArg.typeName)) {
230
- if (firstTypeArg.typeName.text.toLowerCase() === 'uploadfile') {
231
- return 'UploadFile';
232
- }
233
- }
234
- return this.resolveGenericType(typeNode);
235
- }
236
- return typeNode.typeName.text;
237
- }
238
- switch (typeNode.kind) {
239
- case ts.SyntaxKind.StringKeyword:
240
- return constants.jsPrimitives.String.type;
241
- case ts.SyntaxKind.NumberKeyword:
242
- return constants.jsPrimitives.Number.type;
243
- case ts.SyntaxKind.BooleanKeyword:
244
- return constants.jsPrimitives.Boolean.type;
245
- case ts.SyntaxKind.ArrayType:
246
- const arrayType = typeNode;
247
- return `${this.getTypeNodeToString(arrayType.elementType, genericTypeMap)}[]`;
248
- case ts.SyntaxKind.UnionType:
249
- // Handle union types like string | null
250
- const unionType = typeNode;
251
- const types = unionType.types.map(t => this.getTypeNodeToString(t, genericTypeMap));
252
- // Filter out null and undefined, return the first meaningful type
253
- const meaningfulTypes = types.filter(t => t !== 'null' && t !== 'undefined');
254
- if (meaningfulTypes.length > 0 && meaningfulTypes[0]) {
255
- return meaningfulTypes[0];
256
- }
257
- if (types.length > 0 && types[0]) {
258
- return types[0];
259
- }
260
- return 'object';
261
- default:
262
- const typeText = typeNode.getText();
263
- // Check if this is a generic type parameter we can resolve
264
- if (genericTypeMap && genericTypeMap.has(typeText)) {
265
- return genericTypeMap.get(typeText);
266
- }
267
- // Handle some common TypeScript utility types
268
- if (typeText.startsWith('Date'))
269
- return constants.jsPrimitives.Date.type;
270
- if (typeText.includes('Buffer') || typeText.includes('Uint8Array'))
271
- return constants.jsPrimitives.Buffer.type;
272
- return typeText;
273
- }
274
- }
275
- resolveGenericType(typeNode) {
276
- const typeName = typeNode.typeName.text;
277
- const typeArguments = typeNode.typeArguments;
278
- if (!typeArguments || typeArguments.length === 0) {
279
- return typeName;
280
- }
281
- const type = this.checker.getTypeAtLocation(typeNode);
282
- const resolvedType = this.getStringFromType(type);
283
- if (resolvedType &&
284
- resolvedType !== typeName &&
285
- !resolvedType.includes('any')) {
286
- return resolvedType;
287
- }
288
- return typeName;
289
- }
290
- getStringFromType(type) {
291
- return this.checker.typeToString(type);
292
- }
293
- extractDecorators(member) {
294
- const decorators = [];
295
- if (member.modifiers) {
296
- for (const modifier of member.modifiers) {
297
- if (ts.isDecorator(modifier) &&
298
- ts.isCallExpression(modifier.expression)) {
299
- const decoratorName = this.getDecoratorName(modifier.expression);
300
- const args = this.getDecoratorArguments(modifier.expression);
301
- decorators.push({ name: decoratorName, arguments: args });
302
- }
303
- else if (ts.isDecorator(modifier) &&
304
- ts.isIdentifier(modifier.expression)) {
305
- decorators.push({ name: modifier.expression.text, arguments: [] });
306
- }
307
- }
308
- }
309
- return decorators;
310
- }
311
- getDecoratorName(callExpression) {
312
- if (ts.isIdentifier(callExpression.expression)) {
313
- return callExpression.expression.text;
314
- }
315
- return 'unknown';
316
- }
317
- getDecoratorArguments(callExpression) {
318
- return callExpression.arguments.map(arg => {
319
- if (ts.isNumericLiteral(arg))
320
- return Number(arg.text);
321
- if (ts.isStringLiteral(arg))
322
- return arg.text;
323
- if (arg.kind === ts.SyntaxKind.TrueKeyword)
324
- return true;
325
- if (arg.kind === ts.SyntaxKind.FalseKeyword)
326
- return false;
327
- return arg;
328
- });
329
- }
330
- getSafeDecoratorArgument(arg) {
331
- if (arg && typeof arg === 'object' && 'kind' in arg) {
332
- return arg.getText();
333
- }
334
- return arg;
335
- }
336
- isPropertyTypeGeneric(property) {
337
- if (property.type && this.isGenericTypeFromNode(property.type)) {
338
- return true;
339
- }
340
- try {
341
- const type = this.checker.getTypeAtLocation(property);
342
- return this.isGenericTypeFromSymbol(type);
343
- }
344
- catch (error) {
345
- console.warn('Error analyzing property type for generics:', error);
346
- return false;
347
- }
348
- }
349
- isGenericTypeFromNode(typeNode) {
350
- if (ts.isTypeReferenceNode(typeNode) && typeNode.typeArguments) {
351
- return typeNode.typeArguments.length > 0;
352
- }
353
- // Check for mapped types (e.g., { [K in keyof T]: T[K] })
354
- if (ts.isMappedTypeNode(typeNode)) {
355
- return true;
356
- }
357
- // Check for conditional types (e.g., T extends U ? X : Y)
358
- if (ts.isConditionalTypeNode(typeNode)) {
359
- return true;
360
- }
361
- // Check for indexed access types (e.g., T[K])
362
- if (ts.isIndexedAccessTypeNode(typeNode)) {
363
- return true;
364
- }
365
- // Check for type operators like keyof, typeof
366
- if (ts.isTypeOperatorNode(typeNode)) {
367
- return true;
368
- }
369
- return false;
370
- }
371
- isGenericTypeFromSymbol(type) {
372
- // First check if it's a simple array type - these should NOT be considered generic
373
- if (this.isSimpleArrayType(type)) {
374
- return false;
375
- }
376
- // Check if the type has type parameters
377
- if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) {
378
- return true;
379
- }
380
- // Check if it's a type reference with type arguments
381
- // But exclude simple arrays which internally use Array<T> representation
382
- if (type.typeArguments && type.typeArguments.length > 0) {
383
- const symbol = type.getSymbol();
384
- if (symbol && symbol.getName() === 'Array') {
385
- // This is Array<T> - only consider it generic if T itself is a utility type
386
- const elementType = type.typeArguments[0];
387
- if (elementType) {
388
- return this.isUtilityTypeFromType(elementType);
389
- }
390
- return false;
391
- }
392
- const elementType = type.typeArguments[0];
393
- return this.isUtilityTypeFromType(elementType);
394
- }
395
- // Check type flags for generic indicators
396
- if (type.flags & ts.TypeFlags.TypeParameter) {
397
- return true;
398
- }
399
- if (type.flags & ts.TypeFlags.Conditional) {
400
- return true;
401
- }
402
- if (type.flags & ts.TypeFlags.Index) {
403
- return true;
404
- }
405
- if (type.flags & ts.TypeFlags.IndexedAccess) {
406
- return true;
407
- }
408
- // Check if the type symbol indicates a generic type
409
- const symbol = type.getSymbol();
410
- if (symbol && symbol.declarations) {
411
- for (const declaration of symbol.declarations) {
412
- // Check for type alias declarations with type parameters
413
- if (ts.isTypeAliasDeclaration(declaration) &&
414
- declaration.typeParameters) {
415
- return true;
416
- }
417
- // Check for interface declarations with type parameters
418
- if (ts.isInterfaceDeclaration(declaration) &&
419
- declaration.typeParameters) {
420
- return true;
421
- }
422
- // Check for class declarations with type parameters
423
- if (ts.isClassDeclaration(declaration) && declaration.typeParameters) {
424
- return true;
425
- }
426
- }
427
- }
428
- return false;
429
- }
430
- isUtilityTypeFromType(type) {
431
- if (!type.aliasSymbol)
432
- return false;
433
- const aliasName = type.aliasSymbol.getName();
434
- const utilityTypes = [
435
- 'Partial',
436
- 'Required',
437
- 'Readonly',
438
- 'Pick',
439
- 'Omit',
440
- 'Record',
441
- 'Exclude',
442
- 'Extract',
443
- 'NonNullable',
444
- ];
445
- return utilityTypes.includes(aliasName);
446
- }
447
- isSimpleArrayType(type) {
448
- const symbol = type.getSymbol();
449
- if (!symbol || symbol.getName() !== 'Array') {
450
- return false;
451
- }
452
- // Check if this is Array<T> where T is a simple, non-generic type
453
- if (type.typeArguments &&
454
- type.typeArguments.length === 1) {
455
- const elementType = type.typeArguments[0];
456
- if (!elementType)
457
- return false;
458
- // If the element type is a utility type, then this array should be considered generic
459
- if (this.isUtilityTypeFromType(elementType)) {
460
- return false;
461
- }
462
- // If the element type itself has generic parameters, this array is generic
463
- if (elementType.typeArguments &&
464
- elementType.typeArguments.length > 0) {
465
- return false;
466
- }
467
- return true;
468
- }
469
- return false;
470
- }
471
- isPrimitiveType(typeName) {
472
- const lowerTypeName = typeName.toLowerCase();
473
- // Check against all primitive types from constants
474
- const primitiveTypes = [
475
- constants.jsPrimitives.String.type.toLowerCase(),
476
- constants.jsPrimitives.Number.type.toLowerCase(),
477
- constants.jsPrimitives.Boolean.type.toLowerCase(),
478
- constants.jsPrimitives.Date.type.toLowerCase(),
479
- constants.jsPrimitives.Buffer.type.toLowerCase(),
480
- constants.jsPrimitives.Uint8Array.type.toLowerCase(),
481
- constants.jsPrimitives.File.type.toLowerCase(),
482
- constants.jsPrimitives.UploadFile.type.toLowerCase(),
483
- constants.jsPrimitives.BigInt.type.toLowerCase(),
484
- constants.jsPrimitives.Symbol.type.toLowerCase(),
485
- constants.jsPrimitives.null.type.toLowerCase(),
486
- constants.jsPrimitives.Object.type.toLowerCase(),
487
- constants.jsPrimitives.Array.type.toLowerCase(),
488
- constants.jsPrimitives.Any.type.toLowerCase(),
489
- constants.jsPrimitives.Unknown.type.toLowerCase(),
490
- ];
491
- const primitivesArray = primitiveTypes.map(t => t.concat('[]'));
492
- return (primitiveTypes.includes(lowerTypeName) ||
493
- primitivesArray.includes(lowerTypeName));
494
- }
495
- static getInstance(tsConfigPath, options) {
496
- if (!SchemaTransformer.instance) {
497
- SchemaTransformer.instance = new SchemaTransformer(tsConfigPath, options);
498
- }
499
- return SchemaTransformer.instance;
500
- }
501
- getSourceFileByClass(cls, sourceOptions) {
502
- const className = cls.name;
503
- let matches = [];
504
- if (sourceOptions?.isExternal) {
505
- const sourceFiles = this.getFilteredSourceFiles(sourceOptions);
506
- for (const sourceFile of sourceFiles) {
507
- const node = sourceFile.statements.find(stmt => ts.isClassDeclaration(stmt) &&
508
- stmt.name &&
509
- stmt.name.text === className);
510
- if (node) {
511
- matches.push({ sourceFile, node });
512
- }
513
- }
514
- }
515
- else {
516
- matches = this.classFileIndex.get(className) || [];
517
- if (sourceOptions?.filePath) {
518
- matches = matches.filter(m => m.sourceFile.fileName.includes(sourceOptions.filePath));
519
- }
520
- }
521
- if (matches.length === 0) {
522
- return undefined;
523
- }
524
- if (matches.length === 1) {
525
- return matches[0];
526
- }
527
- if (matches.length > 1 && !sourceOptions?.filePath) {
528
- const bestMatch = this.findBestMatch(cls, matches);
529
- if (bestMatch) {
530
- return bestMatch;
531
- }
532
- const firstMatch = matches[0];
533
- if (firstMatch) {
534
- console.warn(`[ts-class-to-openapi] Warning: Found multiple classes with name '${className}'. Using the first one found in '${firstMatch.sourceFile.fileName}'. To resolve this collision, provide 'sourceOptions.filePath'.`);
535
- }
536
- }
537
- return matches[0];
538
- }
539
- checkTypeMatch(value, typeNode) {
540
- const runtimeType = typeof value;
541
- if (runtimeType === 'string' &&
542
- typeNode.kind === ts.SyntaxKind.StringKeyword)
543
- return true;
544
- if (runtimeType === 'number' &&
545
- typeNode.kind === ts.SyntaxKind.NumberKeyword)
546
- return true;
547
- if (runtimeType === 'boolean' &&
548
- typeNode.kind === ts.SyntaxKind.BooleanKeyword)
549
- return true;
550
- if (Array.isArray(value) && ts.isArrayTypeNode(typeNode)) {
551
- if (value.length === 0)
552
- return true;
553
- const firstItem = value[0];
554
- const elementType = typeNode.elementType;
555
- return this.checkTypeMatch(firstItem, elementType);
556
- }
557
- if (runtimeType === 'object' && value !== null && !Array.isArray(value)) {
558
- if (ts.isTypeReferenceNode(typeNode) ||
559
- typeNode.kind === ts.SyntaxKind.ObjectKeyword) {
560
- return true;
561
- }
562
- }
563
- return false;
564
- }
565
- findBestMatch(cls, matches) {
566
- const runtimeSource = cls.toString();
567
- const runtimeProperties = new Map();
568
- const regexProperties = new Set();
569
- // Try to extract properties from runtime source (assignments in constructor)
570
- try {
571
- const regex = /this\.([a-zA-Z0-9_$]+)\s*=/g;
572
- let match;
573
- while ((match = regex.exec(runtimeSource)) !== null) {
574
- regexProperties.add(match[1]);
575
- }
576
- }
577
- catch (e) {
578
- // Ignore regex errors
579
- }
580
- // Try to instantiate the class to find properties
581
- try {
582
- const instance = new cls();
583
- Object.keys(instance).forEach(key => {
584
- runtimeProperties.set(key, instance[key]);
585
- });
586
- }
587
- catch (e) {
588
- // Ignore instantiation errors (e.g. required constructor arguments)
589
- }
590
- // Try to get properties from class-validator metadata
591
- try {
592
- // eslint-disable-next-line @typescript-eslint/no-var-requires
593
- const { getMetadataStorage } = require('class-validator');
594
- const metadata = getMetadataStorage();
595
- const targetMetadata = metadata.getTargetValidationMetadatas(cls, null, false, false);
596
- targetMetadata.forEach((m) => {
597
- if (m.propertyName && !runtimeProperties.has(m.propertyName)) {
598
- runtimeProperties.set(m.propertyName, undefined);
599
- }
600
- });
601
- }
602
- catch (e) {
603
- // Ignore if class-validator is not available or fails
604
- }
605
- // console.log(`[findBestMatch] Class: ${cls.name}, Runtime Props: ${Array.from(runtimeProperties.keys()).join(', ')}`)
606
- const scores = matches.map(match => {
607
- let score = 0;
608
- for (const member of match.node.members) {
609
- if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
610
- if (runtimeSource.includes(member.name.text)) {
611
- score += 2;
612
- }
613
- }
614
- else if (ts.isPropertyDeclaration(member) &&
615
- ts.isIdentifier(member.name)) {
616
- const propName = member.name.text;
617
- if (runtimeProperties.has(propName)) {
618
- score += 1;
619
- const value = runtimeProperties.get(propName);
620
- if (member.type && this.checkTypeMatch(value, member.type)) {
621
- score += 5;
622
- }
623
- }
624
- else if (regexProperties.has(propName)) {
625
- score += 1;
626
- }
627
- }
628
- }
629
- return { match, score };
630
- });
631
- scores.sort((a, b) => b.score - a.score);
632
- const firstScore = scores[0];
633
- const secondScore = scores[1];
634
- if (firstScore && firstScore.score > 0) {
635
- if (scores.length === 1 ||
636
- (secondScore && firstScore.score > secondScore.score)) {
637
- return firstScore.match;
638
- }
639
- }
640
- return undefined;
641
- }
642
- getFilteredSourceFiles(sourceOptions) {
643
- if (sourceOptions?.isExternal) {
644
- return this.program.getSourceFiles().filter(sf => {
645
- return (sf.fileName.includes(sourceOptions.packageName) &&
646
- (!sourceOptions.filePath || sf.fileName === sourceOptions.filePath));
647
- });
648
- }
649
- return this.sourceFiles.filter(sf => {
650
- if (sourceOptions?.filePath &&
651
- !sf.fileName.includes(sourceOptions.filePath)) {
652
- return false;
653
- }
654
- return true;
655
- });
656
- }
657
- isEnum(propertyDeclaration) {
658
- if (!propertyDeclaration.type) {
659
- return false;
660
- }
661
- let typeNode = propertyDeclaration.type;
662
- if (ts.isArrayTypeNode(typeNode)) {
663
- typeNode = typeNode.elementType;
664
- }
665
- if (ts.isTypeReferenceNode(typeNode)) {
666
- const type = this.checker.getTypeAtLocation(typeNode);
667
- // console.log('isEnum check:', typeNode.getText(), type.flags)
668
- return (!!(type.flags & ts.TypeFlags.Enum) ||
669
- !!(type.flags & ts.TypeFlags.EnumLiteral));
670
- }
671
- return false;
672
- }
673
- isClassType(propertyDeclaration) {
674
- // If there's no explicit type annotation, we can't determine reliably
675
- if (!propertyDeclaration.type) {
676
- return false;
677
- }
678
- // Check if the original property type is an array type
679
- if (this.isArrayProperty(propertyDeclaration)) {
680
- const arrayType = propertyDeclaration.type;
681
- const elementType = arrayType.elementType;
682
- // Special handling for utility types with type arguments (e.g., PayloadEntity<Person>)
683
- if (ts.isTypeReferenceNode(elementType) &&
684
- elementType.typeArguments &&
685
- elementType.typeArguments.length > 0) {
686
- // Check the first type argument - it might be the actual class
687
- const firstTypeArg = elementType.typeArguments[0];
688
- if (firstTypeArg) {
689
- const argType = this.checker.getTypeAtLocation(firstTypeArg);
690
- const argSymbol = argType.getSymbol();
691
- if (argSymbol && argSymbol.declarations) {
692
- const hasClass = argSymbol.declarations.some(decl => ts.isClassDeclaration(decl));
693
- if (hasClass)
694
- return true;
695
- }
696
- }
697
- }
698
- // Get the type from the element, regardless of its syntaxkind
699
- const type = this.checker.getTypeAtLocation(elementType);
700
- const symbol = type.getSymbol();
701
- if (symbol && symbol.declarations) {
702
- return symbol.declarations.some(decl => ts.isClassDeclaration(decl));
703
- }
704
- return false;
705
- }
706
- // Check non-array types
707
- else {
708
- // Special handling for utility types with type arguments (e.g., PayloadEntity<Branch>)
709
- if (ts.isTypeReferenceNode(propertyDeclaration.type) &&
710
- propertyDeclaration.type.typeArguments &&
711
- propertyDeclaration.type.typeArguments.length > 0) {
712
- // Check the first type argument - it might be the actual class
713
- const firstTypeArg = propertyDeclaration.type.typeArguments[0];
714
- if (firstTypeArg) {
715
- const argType = this.checker.getTypeAtLocation(firstTypeArg);
716
- const argSymbol = argType.getSymbol();
717
- if (argSymbol && argSymbol.declarations) {
718
- const hasClass = argSymbol.declarations.some(decl => ts.isClassDeclaration(decl));
719
- if (hasClass)
720
- return true;
721
- }
722
- }
723
- }
724
- const type = this.checker.getTypeAtLocation(propertyDeclaration.type);
725
- const symbol = type.getSymbol();
726
- if (symbol && symbol.declarations) {
727
- return symbol.declarations.some(decl => ts.isClassDeclaration(decl));
728
- }
729
- return false;
730
- }
731
- }
732
- getDeclarationProperty(property) {
733
- if (!property.originalProperty.type) {
734
- return undefined;
735
- }
736
- // Handle array types - get the element type
737
- if (ts.isArrayTypeNode(property.originalProperty.type)) {
738
- const elementType = property.originalProperty.type.elementType;
739
- // Check if it's a utility type with type arguments (e.g., PayloadEntity<Branch>[])
740
- if (ts.isTypeReferenceNode(elementType) &&
741
- elementType.typeArguments &&
742
- elementType.typeArguments.length > 0) {
743
- const firstTypeArg = elementType.typeArguments[0];
744
- if (firstTypeArg) {
745
- const argType = this.checker.getTypeAtLocation(firstTypeArg);
746
- const argSymbol = argType.getSymbol();
747
- if (argSymbol && argSymbol.declarations) {
748
- const classDecl = argSymbol.declarations.find(decl => ts.isClassDeclaration(decl));
749
- if (classDecl)
750
- return classDecl;
751
- }
752
- }
753
- }
754
- const type = this.checker.getTypeAtLocation(elementType);
755
- const symbol = type.getSymbol();
756
- if (symbol && symbol.declarations) {
757
- // Return the first class declaration found
758
- const classDecl = symbol.declarations.find(decl => ts.isClassDeclaration(decl));
759
- return classDecl || symbol.declarations[0];
760
- }
761
- return undefined;
762
- }
763
- // Handle non-array types
764
- // Check if it's a utility type with type arguments (e.g., PayloadEntity<Branch>)
765
- if (ts.isTypeReferenceNode(property.originalProperty.type) &&
766
- property.originalProperty.type.typeArguments &&
767
- property.originalProperty.type.typeArguments.length > 0) {
768
- const firstTypeArg = property.originalProperty.type.typeArguments[0];
769
- if (firstTypeArg) {
770
- const argType = this.checker.getTypeAtLocation(firstTypeArg);
771
- const argSymbol = argType.getSymbol();
772
- if (argSymbol && argSymbol.declarations) {
773
- const classDecl = argSymbol.declarations.find(decl => ts.isClassDeclaration(decl));
774
- if (classDecl)
775
- return classDecl;
776
- }
777
- }
778
- }
779
- const type = this.checker.getTypeAtLocation(property.originalProperty.type);
780
- const symbol = type.getSymbol();
781
- if (symbol && symbol.declarations) {
782
- // Return the first class declaration found
783
- const classDecl = symbol.declarations.find(decl => ts.isClassDeclaration(decl));
784
- return classDecl || symbol.declarations[0];
785
- }
786
- return undefined;
787
- }
788
- isArrayProperty(propertyDeclaration) {
789
- if (!propertyDeclaration.type) {
790
- return false;
791
- }
792
- return ts.isArrayTypeNode(propertyDeclaration.type);
793
- }
794
- getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration, }) {
795
- let schema = {};
796
- const required = [];
797
- for (const property of properties) {
798
- schema[property.name] = this.getSchemaFromProperty({
799
- property,
800
- visitedClass,
801
- transformedSchema,
802
- classDeclaration,
803
- });
804
- // this.applyDecorators(property, schema as SchemaType)
805
- if (!property.isOptional) {
806
- required.push(property.name);
807
- }
808
- }
809
- return {
810
- type: 'object',
811
- properties: schema,
812
- required: required.length ? required : undefined,
813
- };
814
- }
815
- getSchemaFromProperty({ property, visitedClass, transformedSchema, classDeclaration, }) {
816
- let schema = {};
817
- if (property.isPrimitive) {
818
- schema = this.getSchemaFromPrimitive(property);
819
- }
820
- else if (property.isClassType) {
821
- schema = this.buildSchemaFromClass({
822
- property,
823
- classDeclaration,
824
- visitedClass,
825
- transformedSchema,
826
- });
827
- }
828
- else if (property.isTypeLiteral && property.typeLiteralClassReference) {
829
- schema = this.buildSchemaFromClass({
830
- property,
831
- classDeclaration: property.typeLiteralClassReference,
832
- visitedClass,
833
- transformedSchema,
834
- });
835
- }
836
- else {
837
- schema = { type: 'object', properties: {}, additionalProperties: true };
838
- }
839
- this.applyDecorators(property, schema);
840
- return schema;
841
- }
842
- buildSchemaFromClass({ property, classDeclaration, visitedClass, transformedSchema, }) {
843
- const declaration = this.getDeclarationProperty(property);
844
- let schema = {};
845
- if (property.isRef && classDeclaration.name) {
846
- // Self-referencing property, handle as a reference to avoid infinite recursion
847
- if (property.isArray) {
848
- schema.type = 'array';
849
- schema.items = {
850
- $ref: `#/components/schemas/${classDeclaration.name.text}`,
851
- };
852
- }
853
- else {
854
- schema = {
855
- $ref: `#/components/schemas/${classDeclaration.name.text}`,
856
- };
857
- }
858
- }
859
- else if (property.isTypeLiteral && property.typeLiteralClassReference) {
860
- schema = this.getSchemaFromClass({
861
- isArray: property.isArray,
862
- visitedClass,
863
- transformedSchema,
864
- declaration: property.typeLiteralClassReference,
865
- });
866
- }
867
- else {
868
- schema = this.getSchemaFromClass({
869
- isArray: property.isArray,
870
- visitedClass,
871
- transformedSchema,
872
- declaration,
873
- });
874
- }
875
- return schema;
876
- }
877
- getSchemaFromClass({ transformedSchema = new Map(), visitedClass = new Set(), declaration, isArray, }) {
878
- let schema = { type: 'object' };
879
- if (!declaration ||
880
- !ts.isClassDeclaration(declaration) ||
881
- !declaration.name) {
882
- return { type: 'object' };
883
- }
884
- if (visitedClass.has(declaration)) {
885
- if (isArray) {
886
- schema.type = 'array';
887
- schema.items = {
888
- $ref: `#/components/schemas/${declaration.name.text}`,
889
- };
890
- }
891
- else {
892
- schema = {
893
- $ref: `#/components/schemas/${declaration.name.text}`,
894
- };
895
- }
896
- return schema;
897
- }
898
- visitedClass.add(declaration);
899
- const properties = this.getPropertiesByClassDeclaration(declaration);
900
- let transformerProps = this.getSchemaFromProperties({
901
- properties,
902
- visitedClass,
903
- transformedSchema: transformedSchema,
904
- classDeclaration: declaration,
905
- });
906
- if (isArray) {
907
- schema.type = 'array';
908
- schema.items = {
909
- type: transformerProps.type,
910
- properties: transformerProps.properties,
911
- required: transformerProps.required,
912
- };
913
- }
914
- else {
915
- schema.type = transformerProps.type;
916
- schema.properties = transformerProps.properties;
917
- schema.required = transformerProps.required;
918
- }
919
- transformedSchema.set(declaration.name.text, schema);
920
- visitedClass.delete(declaration);
921
- return schema;
922
- }
923
- getSchemaFromEnum(property) {
924
- let typeNode = property.originalProperty.type;
925
- if (ts.isArrayTypeNode(typeNode)) {
926
- typeNode = typeNode.elementType;
927
- }
928
- const type = this.checker.getTypeAtLocation(typeNode);
929
- if (type.symbol && type.symbol.exports) {
930
- const values = [];
931
- type.symbol.exports.forEach(member => {
932
- const declaration = member.valueDeclaration;
933
- if (declaration && ts.isEnumMember(declaration)) {
934
- const value = this.checker.getConstantValue(declaration);
935
- if (value !== undefined) {
936
- values.push(value);
937
- }
938
- }
939
- });
940
- if (values.length > 0) {
941
- const propertySchema = { type: 'object' };
942
- propertySchema.enum = values;
943
- const isString = values.every(v => typeof v === 'string');
944
- const isNumber = values.every(v => typeof v === 'number');
945
- if (isString) {
946
- propertySchema.type = 'string';
947
- }
948
- else if (isNumber) {
949
- propertySchema.type = 'number';
950
- }
951
- else {
952
- propertySchema.type = 'string';
953
- }
954
- if (property.isArray) {
955
- const itemsSchema = { ...propertySchema };
956
- propertySchema.type = 'array';
957
- propertySchema.items = itemsSchema;
958
- delete propertySchema.enum;
959
- return propertySchema;
960
- }
961
- else {
962
- return propertySchema;
963
- }
964
- }
965
- }
966
- return undefined;
967
- }
968
- getSchemaFromPrimitive(property) {
969
- if (property.isEnum) {
970
- const enumSchema = this.getSchemaFromEnum(property);
971
- if (enumSchema) {
972
- return enumSchema;
973
- }
974
- }
975
- const propertySchema = { type: 'object' };
976
- const propertyType = property.type.toLowerCase().replace('[]', '').trim();
977
- let isFile = false;
978
- switch (propertyType) {
979
- case constants.jsPrimitives.String.value:
980
- propertySchema.type = constants.jsPrimitives.String.value;
981
- break;
982
- case constants.jsPrimitives.Number.value:
983
- propertySchema.type = constants.jsPrimitives.Number.value;
984
- propertySchema.format = constants.jsPrimitives.Number.format;
985
- break;
986
- case constants.jsPrimitives.BigInt.type.toLocaleLowerCase():
987
- propertySchema.type = constants.jsPrimitives.BigInt.value;
988
- propertySchema.format = constants.jsPrimitives.BigInt.format;
989
- break;
990
- case constants.jsPrimitives.Date.type.toLocaleLowerCase():
991
- propertySchema.type = constants.jsPrimitives.Date.value;
992
- propertySchema.format = constants.jsPrimitives.Date.format;
993
- break;
994
- case constants.jsPrimitives.Buffer.type.toLocaleLowerCase():
995
- case constants.jsPrimitives.Uint8Array.type.toLocaleLowerCase():
996
- case constants.jsPrimitives.File.type.toLocaleLowerCase():
997
- case constants.jsPrimitives.UploadFile.type.toLocaleLowerCase():
998
- propertySchema.type = constants.jsPrimitives.UploadFile.value;
999
- propertySchema.format = constants.jsPrimitives.UploadFile.format;
1000
- isFile = true;
1001
- break;
1002
- case constants.jsPrimitives.Array.value:
1003
- propertySchema.type = constants.jsPrimitives.Array.value;
1004
- break;
1005
- case constants.jsPrimitives.Boolean.value:
1006
- propertySchema.type = constants.jsPrimitives.Boolean.value;
1007
- break;
1008
- case constants.jsPrimitives.Symbol.type.toLocaleLowerCase():
1009
- propertySchema.type = constants.jsPrimitives.Symbol.value;
1010
- break;
1011
- case constants.jsPrimitives.Object.value:
1012
- propertySchema.type = constants.jsPrimitives.Object.value;
1013
- break;
1014
- default:
1015
- propertySchema.type = constants.jsPrimitives.String.value;
1016
- }
1017
- if (property.isArray) {
1018
- delete propertySchema.format;
1019
- propertySchema.type = `array`;
1020
- propertySchema.items = {
1021
- type: isFile ? constants.jsPrimitives.UploadFile.value : propertyType,
1022
- format: isFile
1023
- ? constants.jsPrimitives.UploadFile.format
1024
- : propertySchema.format,
1025
- };
1026
- }
1027
- return propertySchema;
1028
- }
1029
- isTypeLiteral(property) {
1030
- if (!property.type)
1031
- return false;
1032
- if (ts.isTypeReferenceNode(property.type)) {
1033
- const symbol = this.checker.getSymbolAtLocation(property.type.typeName);
1034
- if (symbol) {
1035
- const declarations = symbol.getDeclarations();
1036
- if (declarations && declarations.length > 0) {
1037
- const typeAliasDecl = declarations.find(decl => ts.isTypeAliasDeclaration(decl));
1038
- if (typeAliasDecl && typeAliasDecl.type) {
1039
- return this.isLiteralTypeNode(typeAliasDecl.type);
1040
- }
1041
- }
1042
- }
1043
- }
1044
- return false;
1045
- }
1046
- /**
1047
- *
1048
- * @param typeNode
1049
- * @returns boolean - true si el typeNode representa un tipo literal complejo
1050
- */
1051
- isLiteralTypeNode(typeNode) {
1052
- return (ts.isIntersectionTypeNode(typeNode) || // {} & Omit<T, ...>
1053
- ts.isUnionTypeNode(typeNode) || // string | number
1054
- ts.isMappedTypeNode(typeNode) || // { [K in keyof T]: ... }
1055
- ts.isTypeLiteralNode(typeNode) || // { foo: string }
1056
- ts.isConditionalTypeNode(typeNode) || // T extends U ? X : Y
1057
- ts.isIndexedAccessTypeNode(typeNode) || // T['key']
1058
- ts.isTypeOperatorNode(typeNode) || // keyof T, readonly T
1059
- ts.isTypeReferenceNode(typeNode) // Omit, Pick, Partial, etc.
1060
- );
1061
- }
1062
- applyEnumDecorator(decorator, schema) {
1063
- if (decorator.arguments.length === 0)
1064
- return;
1065
- const arg = decorator.arguments[0];
1066
- if (arg && typeof arg === 'object' && 'kind' in arg) {
1067
- const type = this.checker.getTypeAtLocation(arg);
1068
- if (type.symbol && type.symbol.exports) {
1069
- const values = [];
1070
- type.symbol.exports.forEach(member => {
1071
- const declaration = member.valueDeclaration;
1072
- if (declaration && ts.isEnumMember(declaration)) {
1073
- const value = this.checker.getConstantValue(declaration);
1074
- if (value !== undefined) {
1075
- values.push(value);
1076
- }
1077
- }
1078
- });
1079
- if (values.length > 0) {
1080
- schema.enum = values;
1081
- const isString = values.every(v => typeof v === 'string');
1082
- const isNumber = values.every(v => typeof v === 'number');
1083
- if (isString) {
1084
- schema.type = 'string';
1085
- }
1086
- else if (isNumber) {
1087
- schema.type = 'number';
1088
- }
1089
- else {
1090
- schema.type = 'string';
1091
- }
1092
- }
1093
- }
1094
- }
1095
- }
1096
- applyDecorators(property, schema) {
1097
- for (const decorator of property.decorators) {
1098
- const decoratorName = decorator.name;
1099
- switch (decoratorName) {
1100
- case constants.validatorDecorators.IsString.name:
1101
- if (!property.isArray) {
1102
- schema.type = constants.validatorDecorators.IsString.type;
1103
- }
1104
- else if (schema.items) {
1105
- schema.items.type = constants.validatorDecorators.IsString.type;
1106
- }
1107
- break;
1108
- case constants.validatorDecorators.IsInt.name:
1109
- if (!property.isArray) {
1110
- schema.type = constants.validatorDecorators.IsInt.type;
1111
- schema.format = constants.validatorDecorators.IsInt.format;
1112
- }
1113
- else if (schema.items) {
1114
- schema.items.type = constants.validatorDecorators.IsInt.type;
1115
- schema.items.format = constants.validatorDecorators.IsInt.format;
1116
- }
1117
- break;
1118
- case constants.validatorDecorators.IsNumber.name:
1119
- if (!property.isArray) {
1120
- schema.type = constants.validatorDecorators.IsNumber.type;
1121
- }
1122
- else if (schema.items) {
1123
- schema.items.type = constants.validatorDecorators.IsNumber.type;
1124
- }
1125
- break;
1126
- case constants.validatorDecorators.IsBoolean.name:
1127
- if (!property.isArray) {
1128
- schema.type = constants.validatorDecorators.IsBoolean.type;
1129
- }
1130
- else if (schema.items) {
1131
- schema.items.type = constants.validatorDecorators.IsBoolean.type;
1132
- }
1133
- break;
1134
- case constants.validatorDecorators.IsEmail.name:
1135
- if (!property.isArray) {
1136
- schema.format = constants.validatorDecorators.IsEmail.format;
1137
- }
1138
- else if (schema.items) {
1139
- schema.items.format = constants.validatorDecorators.IsEmail.format;
1140
- }
1141
- break;
1142
- case constants.validatorDecorators.IsDate.name:
1143
- if (!property.isArray) {
1144
- schema.type = constants.validatorDecorators.IsDate.type;
1145
- schema.format = constants.validatorDecorators.IsDate.format;
1146
- }
1147
- else if (schema.items) {
1148
- schema.items.type = constants.validatorDecorators.IsDate.type;
1149
- schema.items.format = constants.validatorDecorators.IsDate.format;
1150
- }
1151
- break;
1152
- case constants.validatorDecorators.IsNotEmpty.name:
1153
- property.isOptional = false;
1154
- break;
1155
- case constants.validatorDecorators.IsOptional.name:
1156
- property.isOptional = true;
1157
- break;
1158
- case constants.validatorDecorators.MinLength.name:
1159
- schema.minLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
1160
- break;
1161
- case constants.validatorDecorators.MaxLength.name:
1162
- schema.maxLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
1163
- break;
1164
- case constants.validatorDecorators.Length.name:
1165
- schema.minLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
1166
- if (decorator.arguments[1]) {
1167
- schema.maxLength = this.getSafeDecoratorArgument(decorator.arguments[1]);
1168
- }
1169
- break;
1170
- case constants.validatorDecorators.Min.name:
1171
- schema.minimum = this.getSafeDecoratorArgument(decorator.arguments[0]);
1172
- break;
1173
- case constants.validatorDecorators.Max.name:
1174
- schema.maximum = this.getSafeDecoratorArgument(decorator.arguments[0]);
1175
- break;
1176
- case constants.validatorDecorators.IsPositive.name:
1177
- schema.minimum = 0;
1178
- break;
1179
- case constants.validatorDecorators.IsArray.name:
1180
- schema.type = constants.jsPrimitives.Array.value;
1181
- break;
1182
- case constants.validatorDecorators.ArrayNotEmpty.name:
1183
- schema.minItems = 1;
1184
- property.isOptional = false;
1185
- break;
1186
- case constants.validatorDecorators.ArrayMinSize.name:
1187
- schema.minItems = this.getSafeDecoratorArgument(decorator.arguments[0]);
1188
- break;
1189
- case constants.validatorDecorators.ArrayMaxSize.name:
1190
- schema.maxItems = this.getSafeDecoratorArgument(decorator.arguments[0]);
1191
- break;
1192
- case constants.validatorDecorators.IsEnum.name:
1193
- if (!property.isArray) {
1194
- this.applyEnumDecorator(decorator, schema);
1195
- }
1196
- else if (schema.items) {
1197
- this.applyEnumDecorator(decorator, schema.items);
1198
- }
1199
- break;
1200
- }
1201
- }
1202
- }
1203
- transform(cls, sourceOptions) {
1204
- let schema = { type: 'object', properties: {} };
1205
- const result = this.getSourceFileByClass(cls, sourceOptions);
1206
- if (!result?.sourceFile) {
1207
- console.warn(`Class ${cls.name} not found in any source file.`);
1208
- return { name: cls.name, schema: {} };
1209
- }
1210
- const properties = this.getPropertiesByClassDeclaration(result.node);
1211
- schema = this.getSchemaFromProperties({
1212
- properties,
1213
- classDeclaration: result.node,
1214
- });
1215
- return { name: cls.name, schema };
1216
- }
1217
- }
1218
- function transform(cls, options) {
1219
- // Use the singleton instance instead of creating a temporary one
1220
- const transformer = SchemaTransformer.getInstance(undefined, options);
1221
- return transformer.transform(cls, options?.sourceOptions);
1222
- }
1223
-
1224
- export { transform };