ts-class-to-openapi 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/__test__/entities/enum-classes.d.ts +30 -0
- package/dist/__test__/index.d.ts +1 -0
- package/dist/index.esm.js +210 -28
- package/dist/index.js +210 -28
- package/dist/run.d.ts +1 -1
- package/dist/run.js +279 -649
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
- /package/dist/__test__/testCases/{debug.test.d.ts → enum-properties.test.d.ts} +0 -0
package/dist/index.js
CHANGED
|
@@ -112,7 +112,8 @@ class SchemaTransformer {
|
|
|
112
112
|
const decorators = this.extractDecorators(member);
|
|
113
113
|
const isOptional = !!member.questionToken;
|
|
114
114
|
const isGeneric = this.isPropertyTypeGeneric(member);
|
|
115
|
-
const
|
|
115
|
+
const isEnum = this.isEnum(member);
|
|
116
|
+
const isPrimitive = this.isPrimitiveType(type) || isEnum;
|
|
116
117
|
const isClassType = this.isClassType(member);
|
|
117
118
|
const isArray = this.isArrayProperty(member);
|
|
118
119
|
const isTypeLiteral = this.isTypeLiteral(member);
|
|
@@ -126,6 +127,7 @@ class SchemaTransformer {
|
|
|
126
127
|
isPrimitive,
|
|
127
128
|
isClassType,
|
|
128
129
|
isArray,
|
|
130
|
+
isEnum,
|
|
129
131
|
isRef: false,
|
|
130
132
|
isTypeLiteral: isTypeLiteral,
|
|
131
133
|
};
|
|
@@ -277,9 +279,15 @@ class SchemaTransformer {
|
|
|
277
279
|
return true;
|
|
278
280
|
if (arg.kind === ts.SyntaxKind.FalseKeyword)
|
|
279
281
|
return false;
|
|
280
|
-
return arg
|
|
282
|
+
return arg;
|
|
281
283
|
});
|
|
282
284
|
}
|
|
285
|
+
getSafeDecoratorArgument(arg) {
|
|
286
|
+
if (arg && typeof arg === 'object' && 'kind' in arg) {
|
|
287
|
+
return arg.getText();
|
|
288
|
+
}
|
|
289
|
+
return arg;
|
|
290
|
+
}
|
|
283
291
|
isPropertyTypeGeneric(property) {
|
|
284
292
|
if (property.type && this.isGenericTypeFromNode(property.type)) {
|
|
285
293
|
return true;
|
|
@@ -477,49 +485,135 @@ class SchemaTransformer {
|
|
|
477
485
|
}
|
|
478
486
|
}
|
|
479
487
|
}
|
|
488
|
+
isEnum(propertyDeclaration) {
|
|
489
|
+
if (!propertyDeclaration.type) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
let typeNode = propertyDeclaration.type;
|
|
493
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
494
|
+
typeNode = typeNode.elementType;
|
|
495
|
+
}
|
|
496
|
+
if (ts.isTypeReferenceNode(typeNode)) {
|
|
497
|
+
const type = this.checker.getTypeAtLocation(typeNode);
|
|
498
|
+
// console.log('isEnum check:', typeNode.getText(), type.flags)
|
|
499
|
+
return (!!(type.flags & ts.TypeFlags.Enum) ||
|
|
500
|
+
!!(type.flags & ts.TypeFlags.EnumLiteral));
|
|
501
|
+
}
|
|
502
|
+
return false;
|
|
503
|
+
}
|
|
480
504
|
isClassType(propertyDeclaration) {
|
|
481
505
|
// If there's no explicit type annotation, we can't determine reliably
|
|
482
506
|
if (!propertyDeclaration.type) {
|
|
483
507
|
return false;
|
|
484
508
|
}
|
|
485
509
|
// Check if the original property type is an array type
|
|
486
|
-
if (this.isArrayProperty(propertyDeclaration)
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
510
|
+
if (this.isArrayProperty(propertyDeclaration)) {
|
|
511
|
+
const arrayType = propertyDeclaration.type;
|
|
512
|
+
const elementType = arrayType.elementType;
|
|
513
|
+
// Special handling for utility types with type arguments (e.g., PayloadEntity<Person>)
|
|
514
|
+
if (ts.isTypeReferenceNode(elementType) &&
|
|
515
|
+
elementType.typeArguments &&
|
|
516
|
+
elementType.typeArguments.length > 0) {
|
|
517
|
+
// Check the first type argument - it might be the actual class
|
|
518
|
+
const firstTypeArg = elementType.typeArguments[0];
|
|
519
|
+
if (firstTypeArg) {
|
|
520
|
+
const argType = this.checker.getTypeAtLocation(firstTypeArg);
|
|
521
|
+
const argSymbol = argType.getSymbol();
|
|
522
|
+
if (argSymbol && argSymbol.declarations) {
|
|
523
|
+
const hasClass = argSymbol.declarations.some(decl => ts.isClassDeclaration(decl));
|
|
524
|
+
if (hasClass)
|
|
525
|
+
return true;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
// Get the type from the element, regardless of its syntaxkind
|
|
530
|
+
const type = this.checker.getTypeAtLocation(elementType);
|
|
490
531
|
const symbol = type.getSymbol();
|
|
491
532
|
if (symbol && symbol.declarations) {
|
|
492
533
|
return symbol.declarations.some(decl => ts.isClassDeclaration(decl));
|
|
493
534
|
}
|
|
535
|
+
return false;
|
|
494
536
|
}
|
|
495
|
-
|
|
537
|
+
// Check non-array types
|
|
538
|
+
else {
|
|
539
|
+
// Special handling for utility types with type arguments (e.g., PayloadEntity<Branch>)
|
|
540
|
+
if (ts.isTypeReferenceNode(propertyDeclaration.type) &&
|
|
541
|
+
propertyDeclaration.type.typeArguments &&
|
|
542
|
+
propertyDeclaration.type.typeArguments.length > 0) {
|
|
543
|
+
// Check the first type argument - it might be the actual class
|
|
544
|
+
const firstTypeArg = propertyDeclaration.type.typeArguments[0];
|
|
545
|
+
if (firstTypeArg) {
|
|
546
|
+
const argType = this.checker.getTypeAtLocation(firstTypeArg);
|
|
547
|
+
const argSymbol = argType.getSymbol();
|
|
548
|
+
if (argSymbol && argSymbol.declarations) {
|
|
549
|
+
const hasClass = argSymbol.declarations.some(decl => ts.isClassDeclaration(decl));
|
|
550
|
+
if (hasClass)
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
496
555
|
const type = this.checker.getTypeAtLocation(propertyDeclaration.type);
|
|
497
556
|
const symbol = type.getSymbol();
|
|
498
557
|
if (symbol && symbol.declarations) {
|
|
499
558
|
return symbol.declarations.some(decl => ts.isClassDeclaration(decl));
|
|
500
559
|
}
|
|
560
|
+
return false;
|
|
501
561
|
}
|
|
502
|
-
return false;
|
|
503
562
|
}
|
|
504
563
|
getDeclarationProperty(property) {
|
|
505
564
|
if (!property.originalProperty.type) {
|
|
506
565
|
return undefined;
|
|
507
566
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const
|
|
567
|
+
// Handle array types - get the element type
|
|
568
|
+
if (ts.isArrayTypeNode(property.originalProperty.type)) {
|
|
569
|
+
const elementType = property.originalProperty.type.elementType;
|
|
570
|
+
// Check if it's a utility type with type arguments (e.g., PayloadEntity<Branch>[])
|
|
571
|
+
if (ts.isTypeReferenceNode(elementType) &&
|
|
572
|
+
elementType.typeArguments &&
|
|
573
|
+
elementType.typeArguments.length > 0) {
|
|
574
|
+
const firstTypeArg = elementType.typeArguments[0];
|
|
575
|
+
if (firstTypeArg) {
|
|
576
|
+
const argType = this.checker.getTypeAtLocation(firstTypeArg);
|
|
577
|
+
const argSymbol = argType.getSymbol();
|
|
578
|
+
if (argSymbol && argSymbol.declarations) {
|
|
579
|
+
const classDecl = argSymbol.declarations.find(decl => ts.isClassDeclaration(decl));
|
|
580
|
+
if (classDecl)
|
|
581
|
+
return classDecl;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const type = this.checker.getTypeAtLocation(elementType);
|
|
511
586
|
const symbol = type.getSymbol();
|
|
512
587
|
if (symbol && symbol.declarations) {
|
|
513
|
-
|
|
588
|
+
// Return the first class declaration found
|
|
589
|
+
const classDecl = symbol.declarations.find(decl => ts.isClassDeclaration(decl));
|
|
590
|
+
return classDecl || symbol.declarations[0];
|
|
514
591
|
}
|
|
592
|
+
return undefined;
|
|
515
593
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
594
|
+
// Handle non-array types
|
|
595
|
+
// Check if it's a utility type with type arguments (e.g., PayloadEntity<Branch>)
|
|
596
|
+
if (ts.isTypeReferenceNode(property.originalProperty.type) &&
|
|
597
|
+
property.originalProperty.type.typeArguments &&
|
|
598
|
+
property.originalProperty.type.typeArguments.length > 0) {
|
|
599
|
+
const firstTypeArg = property.originalProperty.type.typeArguments[0];
|
|
600
|
+
if (firstTypeArg) {
|
|
601
|
+
const argType = this.checker.getTypeAtLocation(firstTypeArg);
|
|
602
|
+
const argSymbol = argType.getSymbol();
|
|
603
|
+
if (argSymbol && argSymbol.declarations) {
|
|
604
|
+
const classDecl = argSymbol.declarations.find(decl => ts.isClassDeclaration(decl));
|
|
605
|
+
if (classDecl)
|
|
606
|
+
return classDecl;
|
|
607
|
+
}
|
|
521
608
|
}
|
|
522
609
|
}
|
|
610
|
+
const type = this.checker.getTypeAtLocation(property.originalProperty.type);
|
|
611
|
+
const symbol = type.getSymbol();
|
|
612
|
+
if (symbol && symbol.declarations) {
|
|
613
|
+
// Return the first class declaration found
|
|
614
|
+
const classDecl = symbol.declarations.find(decl => ts.isClassDeclaration(decl));
|
|
615
|
+
return classDecl || symbol.declarations[0];
|
|
616
|
+
}
|
|
523
617
|
return undefined;
|
|
524
618
|
}
|
|
525
619
|
isArrayProperty(propertyDeclaration) {
|
|
@@ -656,7 +750,58 @@ class SchemaTransformer {
|
|
|
656
750
|
transformedSchema.set(declaration.name.text, schema);
|
|
657
751
|
return schema;
|
|
658
752
|
}
|
|
753
|
+
getSchemaFromEnum(property) {
|
|
754
|
+
let typeNode = property.originalProperty.type;
|
|
755
|
+
if (ts.isArrayTypeNode(typeNode)) {
|
|
756
|
+
typeNode = typeNode.elementType;
|
|
757
|
+
}
|
|
758
|
+
const type = this.checker.getTypeAtLocation(typeNode);
|
|
759
|
+
if (type.symbol && type.symbol.exports) {
|
|
760
|
+
const values = [];
|
|
761
|
+
type.symbol.exports.forEach(member => {
|
|
762
|
+
const declaration = member.valueDeclaration;
|
|
763
|
+
if (declaration && ts.isEnumMember(declaration)) {
|
|
764
|
+
const value = this.checker.getConstantValue(declaration);
|
|
765
|
+
if (value !== undefined) {
|
|
766
|
+
values.push(value);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
if (values.length > 0) {
|
|
771
|
+
const propertySchema = { type: 'object' };
|
|
772
|
+
propertySchema.enum = values;
|
|
773
|
+
const isString = values.every(v => typeof v === 'string');
|
|
774
|
+
const isNumber = values.every(v => typeof v === 'number');
|
|
775
|
+
if (isString) {
|
|
776
|
+
propertySchema.type = 'string';
|
|
777
|
+
}
|
|
778
|
+
else if (isNumber) {
|
|
779
|
+
propertySchema.type = 'number';
|
|
780
|
+
}
|
|
781
|
+
else {
|
|
782
|
+
propertySchema.type = 'string';
|
|
783
|
+
}
|
|
784
|
+
if (property.isArray) {
|
|
785
|
+
const itemsSchema = { ...propertySchema };
|
|
786
|
+
propertySchema.type = 'array';
|
|
787
|
+
propertySchema.items = itemsSchema;
|
|
788
|
+
delete propertySchema.enum;
|
|
789
|
+
return propertySchema;
|
|
790
|
+
}
|
|
791
|
+
else {
|
|
792
|
+
return propertySchema;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return undefined;
|
|
797
|
+
}
|
|
659
798
|
getSchemaFromPrimitive(property) {
|
|
799
|
+
if (property.isEnum) {
|
|
800
|
+
const enumSchema = this.getSchemaFromEnum(property);
|
|
801
|
+
if (enumSchema) {
|
|
802
|
+
return enumSchema;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
660
805
|
const propertySchema = { type: 'object' };
|
|
661
806
|
const propertyType = property.type.toLowerCase().replace('[]', '').trim();
|
|
662
807
|
let isFile = false;
|
|
@@ -744,8 +889,40 @@ class SchemaTransformer {
|
|
|
744
889
|
ts.isTypeReferenceNode(typeNode) // Omit, Pick, Partial, etc.
|
|
745
890
|
);
|
|
746
891
|
}
|
|
747
|
-
|
|
748
|
-
|
|
892
|
+
applyEnumDecorator(decorator, schema) {
|
|
893
|
+
if (decorator.arguments.length === 0)
|
|
894
|
+
return;
|
|
895
|
+
const arg = decorator.arguments[0];
|
|
896
|
+
if (arg && typeof arg === 'object' && 'kind' in arg) {
|
|
897
|
+
const type = this.checker.getTypeAtLocation(arg);
|
|
898
|
+
if (type.symbol && type.symbol.exports) {
|
|
899
|
+
const values = [];
|
|
900
|
+
type.symbol.exports.forEach(member => {
|
|
901
|
+
const declaration = member.valueDeclaration;
|
|
902
|
+
if (declaration && ts.isEnumMember(declaration)) {
|
|
903
|
+
const value = this.checker.getConstantValue(declaration);
|
|
904
|
+
if (value !== undefined) {
|
|
905
|
+
values.push(value);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
if (values.length > 0) {
|
|
910
|
+
schema.enum = values;
|
|
911
|
+
const isString = values.every(v => typeof v === 'string');
|
|
912
|
+
const isNumber = values.every(v => typeof v === 'number');
|
|
913
|
+
if (isString) {
|
|
914
|
+
schema.type = 'string';
|
|
915
|
+
}
|
|
916
|
+
else if (isNumber) {
|
|
917
|
+
schema.type = 'number';
|
|
918
|
+
}
|
|
919
|
+
else {
|
|
920
|
+
schema.type = 'string';
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
749
926
|
applyDecorators(property, schema) {
|
|
750
927
|
for (const decorator of property.decorators) {
|
|
751
928
|
const decoratorName = decorator.name;
|
|
@@ -809,22 +986,22 @@ class SchemaTransformer {
|
|
|
809
986
|
property.isOptional = true;
|
|
810
987
|
break;
|
|
811
988
|
case constants.validatorDecorators.MinLength.name:
|
|
812
|
-
schema.minLength = decorator.arguments[0];
|
|
989
|
+
schema.minLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
|
|
813
990
|
break;
|
|
814
991
|
case constants.validatorDecorators.MaxLength.name:
|
|
815
|
-
schema.maxLength = decorator.arguments[0];
|
|
992
|
+
schema.maxLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
|
|
816
993
|
break;
|
|
817
994
|
case constants.validatorDecorators.Length.name:
|
|
818
|
-
schema.minLength = decorator.arguments[0];
|
|
995
|
+
schema.minLength = this.getSafeDecoratorArgument(decorator.arguments[0]);
|
|
819
996
|
if (decorator.arguments[1]) {
|
|
820
|
-
schema.maxLength = decorator.arguments[1];
|
|
997
|
+
schema.maxLength = this.getSafeDecoratorArgument(decorator.arguments[1]);
|
|
821
998
|
}
|
|
822
999
|
break;
|
|
823
1000
|
case constants.validatorDecorators.Min.name:
|
|
824
|
-
schema.minimum = decorator.arguments[0];
|
|
1001
|
+
schema.minimum = this.getSafeDecoratorArgument(decorator.arguments[0]);
|
|
825
1002
|
break;
|
|
826
1003
|
case constants.validatorDecorators.Max.name:
|
|
827
|
-
schema.maximum = decorator.arguments[0];
|
|
1004
|
+
schema.maximum = this.getSafeDecoratorArgument(decorator.arguments[0]);
|
|
828
1005
|
break;
|
|
829
1006
|
case constants.validatorDecorators.IsPositive.name:
|
|
830
1007
|
schema.minimum = 0;
|
|
@@ -837,13 +1014,18 @@ class SchemaTransformer {
|
|
|
837
1014
|
property.isOptional = false;
|
|
838
1015
|
break;
|
|
839
1016
|
case constants.validatorDecorators.ArrayMinSize.name:
|
|
840
|
-
schema.minItems = decorator.arguments[0];
|
|
1017
|
+
schema.minItems = this.getSafeDecoratorArgument(decorator.arguments[0]);
|
|
841
1018
|
break;
|
|
842
1019
|
case constants.validatorDecorators.ArrayMaxSize.name:
|
|
843
|
-
schema.maxItems = decorator.arguments[0];
|
|
1020
|
+
schema.maxItems = this.getSafeDecoratorArgument(decorator.arguments[0]);
|
|
844
1021
|
break;
|
|
845
1022
|
case constants.validatorDecorators.IsEnum.name:
|
|
846
|
-
|
|
1023
|
+
if (!property.isArray) {
|
|
1024
|
+
this.applyEnumDecorator(decorator, schema);
|
|
1025
|
+
}
|
|
1026
|
+
else if (schema.items) {
|
|
1027
|
+
this.applyEnumDecorator(decorator, schema.items);
|
|
1028
|
+
}
|
|
847
1029
|
break;
|
|
848
1030
|
}
|
|
849
1031
|
}
|
package/dist/run.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
export {};
|