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