ts-class-to-openapi 1.0.6 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__test__/entities/circular-reference-cases.d.ts +1 -0
- package/dist/__test__/entities/circular-reference-classes.d.ts +110 -0
- package/dist/__test__/entities/complex-circular-dependencies.d.ts +71 -0
- package/dist/__test__/entities/deep-nested-classes.d.ts +1 -0
- package/dist/__test__/entities/generic-circular-classes.d.ts +57 -0
- package/dist/__test__/test.d.ts +1 -4
- package/dist/__test__/testCases/circular-references.test.d.ts +1 -0
- package/dist/index.esm.js +111 -31
- package/dist/index.js +111 -31
- package/dist/run.js +112 -32
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,10 +16,10 @@ const jsPrimitives = {
|
|
|
16
16
|
Object: { type: 'Object', value: 'object' },
|
|
17
17
|
Array: { type: 'Array', value: 'array' },
|
|
18
18
|
Date: { type: 'Date', value: 'string', format: 'date-time' },
|
|
19
|
-
Buffer: { type: 'Buffer'
|
|
20
|
-
Uint8Array: { type: 'Uint8Array'
|
|
19
|
+
Buffer: { type: 'Buffer'},
|
|
20
|
+
Uint8Array: { type: 'Uint8Array'},
|
|
21
21
|
UploadFile: { type: 'UploadFile', value: 'string', format: 'binary' },
|
|
22
|
-
File: { type: 'File'
|
|
22
|
+
File: { type: 'File'},
|
|
23
23
|
};
|
|
24
24
|
const validatorDecorators = {
|
|
25
25
|
Length: { name: 'Length'},
|
|
@@ -68,9 +68,42 @@ class SchemaTransformer {
|
|
|
68
68
|
this.program = ts.createProgram(fileNames, tsOptions);
|
|
69
69
|
this.checker = this.program.getTypeChecker();
|
|
70
70
|
}
|
|
71
|
-
getPropertiesByClassDeclaration(classNode) {
|
|
71
|
+
getPropertiesByClassDeclaration(classNode, visitedDeclarations = new Set()) {
|
|
72
|
+
if (visitedDeclarations.has(classNode)) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
visitedDeclarations.add(classNode);
|
|
76
|
+
// if no heritage clauses, get properties directly from class
|
|
77
|
+
if (!classNode.heritageClauses) {
|
|
78
|
+
return this.getPropertiesByClassMembers(classNode.members, classNode);
|
|
79
|
+
} // use heritage clauses to get properties from base classes
|
|
80
|
+
else {
|
|
81
|
+
const heritageClause = classNode.heritageClauses[0];
|
|
82
|
+
if (heritageClause &&
|
|
83
|
+
heritageClause.token === ts.SyntaxKind.ExtendsKeyword) {
|
|
84
|
+
const type = heritageClause.types[0];
|
|
85
|
+
let properties = [];
|
|
86
|
+
let baseProperties = [];
|
|
87
|
+
if (!type)
|
|
88
|
+
return [];
|
|
89
|
+
const symbol = this.checker.getSymbolAtLocation(type.expression);
|
|
90
|
+
if (!symbol)
|
|
91
|
+
return [];
|
|
92
|
+
const declaration = symbol.declarations?.[0];
|
|
93
|
+
if (declaration && ts.isClassDeclaration(declaration)) {
|
|
94
|
+
baseProperties = this.getPropertiesByClassDeclaration(declaration, visitedDeclarations);
|
|
95
|
+
}
|
|
96
|
+
properties = this.getPropertiesByClassMembers(classNode.members, classNode);
|
|
97
|
+
return baseProperties.concat(properties);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
return this.getPropertiesByClassMembers(classNode.members, classNode);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
getPropertiesByClassMembers(members, parentClassNode) {
|
|
72
105
|
const properties = [];
|
|
73
|
-
for (const member of
|
|
106
|
+
for (const member of members) {
|
|
74
107
|
if (ts.isPropertyDeclaration(member) &&
|
|
75
108
|
member.name &&
|
|
76
109
|
ts.isIdentifier(member.name)) {
|
|
@@ -80,6 +113,8 @@ class SchemaTransformer {
|
|
|
80
113
|
const isOptional = !!member.questionToken;
|
|
81
114
|
const isGeneric = this.isPropertyTypeGeneric(member);
|
|
82
115
|
const isPrimitive = this.isPrimitiveType(type);
|
|
116
|
+
const isClassType = this.isClassType(member);
|
|
117
|
+
const isArray = this.isArrayProperty(member);
|
|
83
118
|
const property = {
|
|
84
119
|
name: propertyName,
|
|
85
120
|
type,
|
|
@@ -88,9 +123,25 @@ class SchemaTransformer {
|
|
|
88
123
|
isGeneric,
|
|
89
124
|
originalProperty: member,
|
|
90
125
|
isPrimitive,
|
|
91
|
-
isClassType
|
|
92
|
-
isArray
|
|
126
|
+
isClassType,
|
|
127
|
+
isArray,
|
|
128
|
+
isRef: false,
|
|
93
129
|
};
|
|
130
|
+
// Check for self-referencing properties to mark as $ref
|
|
131
|
+
if (property.isClassType) {
|
|
132
|
+
const declaration = this.getDeclarationProperty(property);
|
|
133
|
+
if (parentClassNode) {
|
|
134
|
+
if (declaration &&
|
|
135
|
+
declaration.name &&
|
|
136
|
+
this.checker.getSymbolAtLocation(declaration.name) ===
|
|
137
|
+
this.checker.getSymbolAtLocation(parentClassNode.name)) {
|
|
138
|
+
property.isRef = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
debugger;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
94
145
|
properties.push(property);
|
|
95
146
|
}
|
|
96
147
|
}
|
|
@@ -458,7 +509,7 @@ class SchemaTransformer {
|
|
|
458
509
|
}
|
|
459
510
|
return ts.isArrayTypeNode(propertyDeclaration.type);
|
|
460
511
|
}
|
|
461
|
-
getSchemaFromProperties({ properties, visitedClass, transformedSchema, }) {
|
|
512
|
+
getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration, }) {
|
|
462
513
|
let schema = {};
|
|
463
514
|
const required = [];
|
|
464
515
|
for (const property of properties) {
|
|
@@ -466,6 +517,7 @@ class SchemaTransformer {
|
|
|
466
517
|
property,
|
|
467
518
|
visitedClass,
|
|
468
519
|
transformedSchema,
|
|
520
|
+
classDeclaration,
|
|
469
521
|
});
|
|
470
522
|
// this.applyDecorators(property, schema as SchemaType)
|
|
471
523
|
if (!property.isOptional) {
|
|
@@ -478,17 +530,35 @@ class SchemaTransformer {
|
|
|
478
530
|
required: required.length ? required : undefined,
|
|
479
531
|
};
|
|
480
532
|
}
|
|
481
|
-
getSchemaFromProperty({ property, visitedClass, transformedSchema, }) {
|
|
533
|
+
getSchemaFromProperty({ property, visitedClass, transformedSchema, classDeclaration, }) {
|
|
482
534
|
let schema = {};
|
|
483
535
|
if (property.isPrimitive) {
|
|
484
536
|
schema = this.getSchemaFromPrimitive(property);
|
|
485
537
|
}
|
|
486
538
|
else if (property.isClassType) {
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
539
|
+
const declaration = this.getDeclarationProperty(property);
|
|
540
|
+
if (property.isRef && classDeclaration.name) {
|
|
541
|
+
// Self-referencing property, handle as a reference to avoid infinite recursion
|
|
542
|
+
if (property.isArray) {
|
|
543
|
+
schema.type = 'array';
|
|
544
|
+
schema.items = {
|
|
545
|
+
$ref: `#/components/schemas/${classDeclaration.name.text}`,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
else {
|
|
549
|
+
schema = {
|
|
550
|
+
$ref: `#/components/schemas/${classDeclaration.name.text}`,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
else {
|
|
555
|
+
schema = this.getSchemaFromClass({
|
|
556
|
+
isArray: property.isArray,
|
|
557
|
+
visitedClass,
|
|
558
|
+
transformedSchema,
|
|
559
|
+
declaration,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
492
562
|
}
|
|
493
563
|
else {
|
|
494
564
|
schema = { type: 'object', properties: {}, additionalProperties: true };
|
|
@@ -496,21 +566,26 @@ class SchemaTransformer {
|
|
|
496
566
|
this.applyDecorators(property, schema);
|
|
497
567
|
return schema;
|
|
498
568
|
}
|
|
499
|
-
getSchemaFromClass({
|
|
569
|
+
getSchemaFromClass({ transformedSchema = new Map(), visitedClass = new Set(), declaration, isArray, }) {
|
|
500
570
|
let schema = { type: 'object' };
|
|
501
|
-
const declaration = this.getDeclarationProperty(property);
|
|
502
571
|
if (!declaration ||
|
|
503
572
|
!ts.isClassDeclaration(declaration) ||
|
|
504
573
|
!declaration.name) {
|
|
505
574
|
return { type: 'object' };
|
|
506
575
|
}
|
|
507
576
|
if (visitedClass.has(declaration)) {
|
|
508
|
-
if (
|
|
509
|
-
|
|
577
|
+
if (isArray) {
|
|
578
|
+
schema.type = 'array';
|
|
579
|
+
schema.items = {
|
|
580
|
+
$ref: `#/components/schemas/${declaration.name.text}`,
|
|
581
|
+
};
|
|
510
582
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
583
|
+
else {
|
|
584
|
+
schema = {
|
|
585
|
+
$ref: `#/components/schemas/${declaration.name.text}`,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
return schema;
|
|
514
589
|
}
|
|
515
590
|
visitedClass.add(declaration);
|
|
516
591
|
const properties = this.getPropertiesByClassDeclaration(declaration);
|
|
@@ -518,8 +593,9 @@ class SchemaTransformer {
|
|
|
518
593
|
properties,
|
|
519
594
|
visitedClass,
|
|
520
595
|
transformedSchema: transformedSchema,
|
|
596
|
+
classDeclaration: declaration,
|
|
521
597
|
});
|
|
522
|
-
if (
|
|
598
|
+
if (isArray) {
|
|
523
599
|
schema.type = 'array';
|
|
524
600
|
schema.items = {
|
|
525
601
|
type: transformerProps.type,
|
|
@@ -533,14 +609,12 @@ class SchemaTransformer {
|
|
|
533
609
|
schema.required = transformerProps.required;
|
|
534
610
|
}
|
|
535
611
|
transformedSchema.set(declaration.name.text, schema);
|
|
536
|
-
if (schema.properties && Object.keys(schema.properties).length === 0) {
|
|
537
|
-
schema = { type: 'object', properties: {}, additionalProperties: true };
|
|
538
|
-
}
|
|
539
612
|
return schema;
|
|
540
613
|
}
|
|
541
614
|
getSchemaFromPrimitive(property) {
|
|
542
615
|
const propertySchema = { type: 'object' };
|
|
543
616
|
const propertyType = property.type.toLowerCase().replace('[]', '').trim();
|
|
617
|
+
let isFile = false;
|
|
544
618
|
switch (propertyType) {
|
|
545
619
|
case constants.jsPrimitives.String.value:
|
|
546
620
|
propertySchema.type = constants.jsPrimitives.String.value;
|
|
@@ -557,12 +631,13 @@ class SchemaTransformer {
|
|
|
557
631
|
propertySchema.type = constants.jsPrimitives.Date.value;
|
|
558
632
|
propertySchema.format = constants.jsPrimitives.Date.format;
|
|
559
633
|
break;
|
|
560
|
-
case constants.jsPrimitives.Buffer.
|
|
561
|
-
case constants.jsPrimitives.Uint8Array.
|
|
562
|
-
case constants.jsPrimitives.File.
|
|
563
|
-
case constants.jsPrimitives.UploadFile.
|
|
634
|
+
case constants.jsPrimitives.Buffer.type.toLocaleLowerCase():
|
|
635
|
+
case constants.jsPrimitives.Uint8Array.type.toLocaleLowerCase():
|
|
636
|
+
case constants.jsPrimitives.File.type.toLocaleLowerCase():
|
|
637
|
+
case constants.jsPrimitives.UploadFile.type.toLocaleLowerCase():
|
|
564
638
|
propertySchema.type = constants.jsPrimitives.UploadFile.value;
|
|
565
639
|
propertySchema.format = constants.jsPrimitives.UploadFile.format;
|
|
640
|
+
isFile = true;
|
|
566
641
|
break;
|
|
567
642
|
case constants.jsPrimitives.Array.value:
|
|
568
643
|
propertySchema.type = constants.jsPrimitives.Array.value;
|
|
@@ -581,7 +656,9 @@ class SchemaTransformer {
|
|
|
581
656
|
}
|
|
582
657
|
if (property.isArray) {
|
|
583
658
|
propertySchema.type = `array`;
|
|
584
|
-
propertySchema.items = {
|
|
659
|
+
propertySchema.items = {
|
|
660
|
+
type: isFile ? constants.jsPrimitives.UploadFile.value : propertyType,
|
|
661
|
+
};
|
|
585
662
|
}
|
|
586
663
|
return propertySchema;
|
|
587
664
|
}
|
|
@@ -697,7 +774,10 @@ class SchemaTransformer {
|
|
|
697
774
|
return { name: cls.name, schema: {} };
|
|
698
775
|
}
|
|
699
776
|
const properties = this.getPropertiesByClassDeclaration(result.node);
|
|
700
|
-
schema = this.getSchemaFromProperties({
|
|
777
|
+
schema = this.getSchemaFromProperties({
|
|
778
|
+
properties,
|
|
779
|
+
classDeclaration: result.node,
|
|
780
|
+
});
|
|
701
781
|
return { name: cls.name, schema };
|
|
702
782
|
}
|
|
703
783
|
}
|