ts-class-to-openapi 1.4.0 → 1.4.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/index.cjs +101 -42
- package/dist/index.mjs +101 -42
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -189,6 +189,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
189
189
|
maxCacheSize;
|
|
190
190
|
autoCleanup;
|
|
191
191
|
classFileIndex = /* @__PURE__ */ new Map();
|
|
192
|
+
transformCallIndex = /* @__PURE__ */ new Map();
|
|
192
193
|
constructor(tsConfigPath = constants.TS_CONFIG_DEFAULT_PATH, options = {}) {
|
|
193
194
|
this.maxCacheSize = options.maxCacheSize ?? 100;
|
|
194
195
|
this.autoCleanup = options.autoCleanup ?? true;
|
|
@@ -200,19 +201,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
200
201
|
const { options: tsOptions, fileNames } = typescript.default.parseJsonConfigFileContent(config, typescript.default.sys, "./");
|
|
201
202
|
this.program = typescript.default.createProgram(fileNames, tsOptions);
|
|
202
203
|
this.checker = this.program.getTypeChecker();
|
|
203
|
-
this.
|
|
204
|
-
sf.statements.forEach((stmt) => {
|
|
205
|
-
if (typescript.default.isClassDeclaration(stmt) && stmt.name) {
|
|
206
|
-
const name = stmt.name.text;
|
|
207
|
-
const entry = this.classFileIndex.get(name) || [];
|
|
208
|
-
entry.push({
|
|
209
|
-
sourceFile: sf,
|
|
210
|
-
node: stmt
|
|
211
|
-
});
|
|
212
|
-
this.classFileIndex.set(name, entry);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
});
|
|
204
|
+
this.buildTransformCallIndex();
|
|
216
205
|
}
|
|
217
206
|
getPropertiesByClassDeclaration(classNode, visitedDeclarations = /* @__PURE__ */ new Set(), genericTypeMap = /* @__PURE__ */ new Map()) {
|
|
218
207
|
if (visitedDeclarations.has(classNode)) return [];
|
|
@@ -260,6 +249,18 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
260
249
|
const isOptional = !!member.questionToken;
|
|
261
250
|
const isGeneric = this.isPropertyTypeGeneric(member);
|
|
262
251
|
const isEnum = this.isEnum(member);
|
|
252
|
+
const isPrimitive = this.isPrimitiveType(type) || isEnum;
|
|
253
|
+
const isClassType = this.isClassType(member);
|
|
254
|
+
const isArray = this.isArrayProperty(member);
|
|
255
|
+
const isTypeLiteral = this.isTypeLiteral(member);
|
|
256
|
+
let genericClassReference = void 0;
|
|
257
|
+
if (isGeneric && !isPrimitive) {
|
|
258
|
+
const baseTypeName = type.replace(/\[\]$/, "").trim();
|
|
259
|
+
if (!this.isPrimitiveType(baseTypeName)) {
|
|
260
|
+
const matches = this.classFileIndex.get(baseTypeName);
|
|
261
|
+
if (matches && matches.length > 0 && matches[0]) genericClassReference = matches[0].node;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
263
264
|
const property = {
|
|
264
265
|
name: propertyName,
|
|
265
266
|
type,
|
|
@@ -267,12 +268,13 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
267
268
|
isOptional,
|
|
268
269
|
isGeneric,
|
|
269
270
|
originalProperty: member,
|
|
270
|
-
isPrimitive
|
|
271
|
-
isClassType
|
|
272
|
-
isArray
|
|
271
|
+
isPrimitive,
|
|
272
|
+
isClassType,
|
|
273
|
+
isArray,
|
|
273
274
|
isEnum,
|
|
274
275
|
isRef: false,
|
|
275
|
-
isTypeLiteral
|
|
276
|
+
isTypeLiteral,
|
|
277
|
+
genericClassReference
|
|
276
278
|
};
|
|
277
279
|
if (property.isClassType) {
|
|
278
280
|
const declaration = this.getDeclarationProperty(property);
|
|
@@ -310,6 +312,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
310
312
|
if (typeName.toLowerCase() === "uploadfile") return "UploadFile";
|
|
311
313
|
if (typeName.toLowerCase() === "uploadfiledto") return "UploadFileDto";
|
|
312
314
|
if (typeNode.typeArguments && typeNode.typeArguments.length > 0) {
|
|
315
|
+
if (typeName === "Array") return `${this.getTypeNodeToString(typeNode.typeArguments[0], genericTypeMap)}[]`;
|
|
313
316
|
const firstTypeArg = typeNode.typeArguments[0];
|
|
314
317
|
if (firstTypeArg && typescript.default.isTypeReferenceNode(firstTypeArg) && typescript.default.isIdentifier(firstTypeArg.typeName)) {
|
|
315
318
|
if (firstTypeArg.typeName.text.toLowerCase() === "uploadfile") return "UploadFile";
|
|
@@ -411,7 +414,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
411
414
|
isGenericTypeFromSymbol(type) {
|
|
412
415
|
if (this.isSimpleArrayType(type)) return false;
|
|
413
416
|
if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) return true;
|
|
414
|
-
if (type.typeArguments && type.typeArguments.length > 0) {
|
|
417
|
+
if (type.typeArguments && type.typeArguments.length > 0 && type.typeArguments[0].symbol.getName() === "Array") {
|
|
415
418
|
const symbol = type.getSymbol();
|
|
416
419
|
if (symbol && symbol.getName() === "Array") {
|
|
417
420
|
const elementType = type.typeArguments[0];
|
|
@@ -456,6 +459,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
456
459
|
if (!elementType) return false;
|
|
457
460
|
if (this.isUtilityTypeFromType(elementType)) return false;
|
|
458
461
|
if (elementType.typeArguments && elementType.typeArguments.length > 0) return false;
|
|
462
|
+
if (type.typeArguments && type.typeArguments[0].symbol && type.typeArguments[0].symbol.getName() !== "Array") return false;
|
|
459
463
|
return true;
|
|
460
464
|
}
|
|
461
465
|
return false;
|
|
@@ -513,22 +517,6 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
513
517
|
}
|
|
514
518
|
return matches[0];
|
|
515
519
|
}
|
|
516
|
-
checkTypeMatch(value, typeNode) {
|
|
517
|
-
const runtimeType = typeof value;
|
|
518
|
-
if (runtimeType === "string" && typeNode.kind === typescript.default.SyntaxKind.StringKeyword) return true;
|
|
519
|
-
if (runtimeType === "number" && typeNode.kind === typescript.default.SyntaxKind.NumberKeyword) return true;
|
|
520
|
-
if (runtimeType === "boolean" && typeNode.kind === typescript.default.SyntaxKind.BooleanKeyword) return true;
|
|
521
|
-
if (Array.isArray(value) && typescript.default.isArrayTypeNode(typeNode)) {
|
|
522
|
-
if (value.length === 0) return true;
|
|
523
|
-
const firstItem = value[0];
|
|
524
|
-
const elementType = typeNode.elementType;
|
|
525
|
-
return this.checkTypeMatch(firstItem, elementType);
|
|
526
|
-
}
|
|
527
|
-
if (runtimeType === "object" && value !== null && !Array.isArray(value)) {
|
|
528
|
-
if (typescript.default.isTypeReferenceNode(typeNode) || typeNode.kind === typescript.default.SyntaxKind.ObjectKeyword) return true;
|
|
529
|
-
}
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
520
|
findBestMatch(cls, matches) {
|
|
533
521
|
let instance = {};
|
|
534
522
|
try {
|
|
@@ -570,7 +558,11 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
570
558
|
isClassType(propertyDeclaration) {
|
|
571
559
|
if (!propertyDeclaration.type) return false;
|
|
572
560
|
if (this.isArrayProperty(propertyDeclaration)) {
|
|
573
|
-
|
|
561
|
+
var _propertyDeclaration$;
|
|
562
|
+
let elementType;
|
|
563
|
+
if (typescript.default.isArrayTypeNode(propertyDeclaration.type)) elementType = propertyDeclaration.type.elementType;
|
|
564
|
+
else if (typescript.default.isTypeReferenceNode(propertyDeclaration.type) && ((_propertyDeclaration$ = propertyDeclaration.type.typeArguments) === null || _propertyDeclaration$ === void 0 ? void 0 : _propertyDeclaration$[0])) elementType = propertyDeclaration.type.typeArguments[0];
|
|
565
|
+
if (!elementType) return false;
|
|
574
566
|
if (typescript.default.isTypeReferenceNode(elementType) && elementType.typeArguments && elementType.typeArguments.length > 0) {
|
|
575
567
|
const firstTypeArg = elementType.typeArguments[0];
|
|
576
568
|
if (firstTypeArg) {
|
|
@@ -631,7 +623,9 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
631
623
|
}
|
|
632
624
|
isArrayProperty(propertyDeclaration) {
|
|
633
625
|
if (!propertyDeclaration.type) return false;
|
|
634
|
-
|
|
626
|
+
if (typescript.default.isArrayTypeNode(propertyDeclaration.type)) return true;
|
|
627
|
+
if (typescript.default.isTypeReferenceNode(propertyDeclaration.type) && typescript.default.isIdentifier(propertyDeclaration.type.typeName) && propertyDeclaration.type.typeName.text === "Array") return true;
|
|
628
|
+
return false;
|
|
635
629
|
}
|
|
636
630
|
getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration }) {
|
|
637
631
|
let schema = {};
|
|
@@ -666,6 +660,17 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
666
660
|
visitedClass,
|
|
667
661
|
transformedSchema
|
|
668
662
|
});
|
|
663
|
+
else if (property.isGeneric) if (property.genericClassReference) schema = this.getSchemaFromClass({
|
|
664
|
+
isArray: property.isArray,
|
|
665
|
+
visitedClass,
|
|
666
|
+
transformedSchema,
|
|
667
|
+
declaration: property.genericClassReference
|
|
668
|
+
});
|
|
669
|
+
else schema = {
|
|
670
|
+
type: "object",
|
|
671
|
+
properties: {},
|
|
672
|
+
additionalProperties: true
|
|
673
|
+
};
|
|
669
674
|
else schema = {
|
|
670
675
|
type: "object",
|
|
671
676
|
properties: {},
|
|
@@ -986,12 +991,55 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
986
991
|
break;
|
|
987
992
|
}
|
|
988
993
|
}
|
|
994
|
+
/**
|
|
995
|
+
* Scans all non-declaration source files in the program once and records
|
|
996
|
+
* every call of the form `transform(Foo<Bar, Baz>)`, keyed by class name.
|
|
997
|
+
* This means generic resolution in transform() is a pure O(1) Map lookup
|
|
998
|
+
* with no runtime stack inspection.
|
|
999
|
+
*/
|
|
1000
|
+
buildTransformCallIndex() {
|
|
1001
|
+
this.program.getSourceFiles().forEach((sf) => {
|
|
1002
|
+
if (sf.isDeclarationFile) return;
|
|
1003
|
+
sf.statements.forEach((stmt) => {
|
|
1004
|
+
if (typescript.default.isClassDeclaration(stmt) && stmt.name) {
|
|
1005
|
+
const name = stmt.name.text;
|
|
1006
|
+
const entry = this.classFileIndex.get(name) || [];
|
|
1007
|
+
entry.push({
|
|
1008
|
+
sourceFile: sf,
|
|
1009
|
+
node: stmt
|
|
1010
|
+
});
|
|
1011
|
+
this.classFileIndex.set(name, entry);
|
|
1012
|
+
}
|
|
1013
|
+
});
|
|
1014
|
+
const visit = (node) => {
|
|
1015
|
+
if (typescript.default.isCallExpression(node) && node.arguments.length > 0) {
|
|
1016
|
+
const callee = node.expression;
|
|
1017
|
+
if (typescript.default.isIdentifier(callee) && callee.text === "transform" || typescript.default.isPropertyAccessExpression(callee) && callee.name.text === "transform") {
|
|
1018
|
+
const firstArg = node.arguments[0];
|
|
1019
|
+
const typeArgs = firstArg.typeArguments;
|
|
1020
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
1021
|
+
const baseExpr = firstArg.expression ?? firstArg;
|
|
1022
|
+
if (typescript.default.isIdentifier(baseExpr)) {
|
|
1023
|
+
var _this$classFileIndex$;
|
|
1024
|
+
const classNode = (_this$classFileIndex$ = this.classFileIndex.get(baseExpr.text)) === null || _this$classFileIndex$ === void 0 || (_this$classFileIndex$ = _this$classFileIndex$[0]) === null || _this$classFileIndex$ === void 0 ? void 0 : _this$classFileIndex$.node;
|
|
1025
|
+
if (classNode === null || classNode === void 0 ? void 0 : classNode.typeParameters) {
|
|
1026
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1027
|
+
classNode.typeParameters.forEach((param, i) => {
|
|
1028
|
+
const typeArg = typeArgs[i];
|
|
1029
|
+
if (typeArg) typeMap.set(param.name.text, this.getTypeNodeToString(typeArg, /* @__PURE__ */ new Map()));
|
|
1030
|
+
});
|
|
1031
|
+
if (typeMap.size > 0) this.transformCallIndex.set(baseExpr.text, typeMap);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
typescript.default.forEachChild(node, visit);
|
|
1038
|
+
};
|
|
1039
|
+
visit(sf);
|
|
1040
|
+
});
|
|
1041
|
+
}
|
|
989
1042
|
transform(cls, sourceOptions) {
|
|
990
|
-
if (this.classCache.has(cls)) return this.classCache.get(cls);
|
|
991
|
-
let schema = {
|
|
992
|
-
type: "object",
|
|
993
|
-
properties: {}
|
|
994
|
-
};
|
|
995
1043
|
const result = this.getSourceFileByClass(cls, sourceOptions);
|
|
996
1044
|
if (!result || !(result === null || result === void 0 ? void 0 : result.sourceFile)) {
|
|
997
1045
|
console.warn(`Class ${cls.name} not found in any source file.`);
|
|
@@ -1005,11 +1053,22 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
1005
1053
|
}
|
|
1006
1054
|
};
|
|
1007
1055
|
}
|
|
1008
|
-
const
|
|
1056
|
+
const genericTypeMap = this.transformCallIndex.get(cls.name) ?? /* @__PURE__ */ new Map();
|
|
1057
|
+
const hasGenericArgs = genericTypeMap.size > 0;
|
|
1058
|
+
if (!hasGenericArgs && this.classCache.has(cls)) return this.classCache.get(cls);
|
|
1059
|
+
let schema = {
|
|
1060
|
+
type: "object",
|
|
1061
|
+
properties: {}
|
|
1062
|
+
};
|
|
1063
|
+
const properties = this.getPropertiesByClassDeclaration(result.node, void 0, genericTypeMap);
|
|
1009
1064
|
schema = this.getSchemaFromProperties({
|
|
1010
1065
|
properties,
|
|
1011
1066
|
classDeclaration: result.node
|
|
1012
1067
|
});
|
|
1068
|
+
if (!hasGenericArgs) this.classCache.set(cls, {
|
|
1069
|
+
name: cls.name,
|
|
1070
|
+
schema
|
|
1071
|
+
});
|
|
1013
1072
|
return {
|
|
1014
1073
|
name: cls.name,
|
|
1015
1074
|
schema
|
package/dist/index.mjs
CHANGED
|
@@ -164,6 +164,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
164
164
|
maxCacheSize;
|
|
165
165
|
autoCleanup;
|
|
166
166
|
classFileIndex = /* @__PURE__ */ new Map();
|
|
167
|
+
transformCallIndex = /* @__PURE__ */ new Map();
|
|
167
168
|
constructor(tsConfigPath = constants.TS_CONFIG_DEFAULT_PATH, options = {}) {
|
|
168
169
|
this.maxCacheSize = options.maxCacheSize ?? 100;
|
|
169
170
|
this.autoCleanup = options.autoCleanup ?? true;
|
|
@@ -175,19 +176,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
175
176
|
const { options: tsOptions, fileNames } = ts.parseJsonConfigFileContent(config, ts.sys, "./");
|
|
176
177
|
this.program = ts.createProgram(fileNames, tsOptions);
|
|
177
178
|
this.checker = this.program.getTypeChecker();
|
|
178
|
-
this.
|
|
179
|
-
sf.statements.forEach((stmt) => {
|
|
180
|
-
if (ts.isClassDeclaration(stmt) && stmt.name) {
|
|
181
|
-
const name = stmt.name.text;
|
|
182
|
-
const entry = this.classFileIndex.get(name) || [];
|
|
183
|
-
entry.push({
|
|
184
|
-
sourceFile: sf,
|
|
185
|
-
node: stmt
|
|
186
|
-
});
|
|
187
|
-
this.classFileIndex.set(name, entry);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
179
|
+
this.buildTransformCallIndex();
|
|
191
180
|
}
|
|
192
181
|
getPropertiesByClassDeclaration(classNode, visitedDeclarations = /* @__PURE__ */ new Set(), genericTypeMap = /* @__PURE__ */ new Map()) {
|
|
193
182
|
if (visitedDeclarations.has(classNode)) return [];
|
|
@@ -235,6 +224,18 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
235
224
|
const isOptional = !!member.questionToken;
|
|
236
225
|
const isGeneric = this.isPropertyTypeGeneric(member);
|
|
237
226
|
const isEnum = this.isEnum(member);
|
|
227
|
+
const isPrimitive = this.isPrimitiveType(type) || isEnum;
|
|
228
|
+
const isClassType = this.isClassType(member);
|
|
229
|
+
const isArray = this.isArrayProperty(member);
|
|
230
|
+
const isTypeLiteral = this.isTypeLiteral(member);
|
|
231
|
+
let genericClassReference = void 0;
|
|
232
|
+
if (isGeneric && !isPrimitive) {
|
|
233
|
+
const baseTypeName = type.replace(/\[\]$/, "").trim();
|
|
234
|
+
if (!this.isPrimitiveType(baseTypeName)) {
|
|
235
|
+
const matches = this.classFileIndex.get(baseTypeName);
|
|
236
|
+
if (matches && matches.length > 0 && matches[0]) genericClassReference = matches[0].node;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
238
239
|
const property = {
|
|
239
240
|
name: propertyName,
|
|
240
241
|
type,
|
|
@@ -242,12 +243,13 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
242
243
|
isOptional,
|
|
243
244
|
isGeneric,
|
|
244
245
|
originalProperty: member,
|
|
245
|
-
isPrimitive
|
|
246
|
-
isClassType
|
|
247
|
-
isArray
|
|
246
|
+
isPrimitive,
|
|
247
|
+
isClassType,
|
|
248
|
+
isArray,
|
|
248
249
|
isEnum,
|
|
249
250
|
isRef: false,
|
|
250
|
-
isTypeLiteral
|
|
251
|
+
isTypeLiteral,
|
|
252
|
+
genericClassReference
|
|
251
253
|
};
|
|
252
254
|
if (property.isClassType) {
|
|
253
255
|
const declaration = this.getDeclarationProperty(property);
|
|
@@ -285,6 +287,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
285
287
|
if (typeName.toLowerCase() === "uploadfile") return "UploadFile";
|
|
286
288
|
if (typeName.toLowerCase() === "uploadfiledto") return "UploadFileDto";
|
|
287
289
|
if (typeNode.typeArguments && typeNode.typeArguments.length > 0) {
|
|
290
|
+
if (typeName === "Array") return `${this.getTypeNodeToString(typeNode.typeArguments[0], genericTypeMap)}[]`;
|
|
288
291
|
const firstTypeArg = typeNode.typeArguments[0];
|
|
289
292
|
if (firstTypeArg && ts.isTypeReferenceNode(firstTypeArg) && ts.isIdentifier(firstTypeArg.typeName)) {
|
|
290
293
|
if (firstTypeArg.typeName.text.toLowerCase() === "uploadfile") return "UploadFile";
|
|
@@ -386,7 +389,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
386
389
|
isGenericTypeFromSymbol(type) {
|
|
387
390
|
if (this.isSimpleArrayType(type)) return false;
|
|
388
391
|
if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) return true;
|
|
389
|
-
if (type.typeArguments && type.typeArguments.length > 0) {
|
|
392
|
+
if (type.typeArguments && type.typeArguments.length > 0 && type.typeArguments[0].symbol.getName() === "Array") {
|
|
390
393
|
const symbol = type.getSymbol();
|
|
391
394
|
if (symbol && symbol.getName() === "Array") {
|
|
392
395
|
const elementType = type.typeArguments[0];
|
|
@@ -431,6 +434,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
431
434
|
if (!elementType) return false;
|
|
432
435
|
if (this.isUtilityTypeFromType(elementType)) return false;
|
|
433
436
|
if (elementType.typeArguments && elementType.typeArguments.length > 0) return false;
|
|
437
|
+
if (type.typeArguments && type.typeArguments[0].symbol && type.typeArguments[0].symbol.getName() !== "Array") return false;
|
|
434
438
|
return true;
|
|
435
439
|
}
|
|
436
440
|
return false;
|
|
@@ -488,22 +492,6 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
488
492
|
}
|
|
489
493
|
return matches[0];
|
|
490
494
|
}
|
|
491
|
-
checkTypeMatch(value, typeNode) {
|
|
492
|
-
const runtimeType = typeof value;
|
|
493
|
-
if (runtimeType === "string" && typeNode.kind === ts.SyntaxKind.StringKeyword) return true;
|
|
494
|
-
if (runtimeType === "number" && typeNode.kind === ts.SyntaxKind.NumberKeyword) return true;
|
|
495
|
-
if (runtimeType === "boolean" && typeNode.kind === ts.SyntaxKind.BooleanKeyword) return true;
|
|
496
|
-
if (Array.isArray(value) && ts.isArrayTypeNode(typeNode)) {
|
|
497
|
-
if (value.length === 0) return true;
|
|
498
|
-
const firstItem = value[0];
|
|
499
|
-
const elementType = typeNode.elementType;
|
|
500
|
-
return this.checkTypeMatch(firstItem, elementType);
|
|
501
|
-
}
|
|
502
|
-
if (runtimeType === "object" && value !== null && !Array.isArray(value)) {
|
|
503
|
-
if (ts.isTypeReferenceNode(typeNode) || typeNode.kind === ts.SyntaxKind.ObjectKeyword) return true;
|
|
504
|
-
}
|
|
505
|
-
return false;
|
|
506
|
-
}
|
|
507
495
|
findBestMatch(cls, matches) {
|
|
508
496
|
let instance = {};
|
|
509
497
|
try {
|
|
@@ -545,7 +533,11 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
545
533
|
isClassType(propertyDeclaration) {
|
|
546
534
|
if (!propertyDeclaration.type) return false;
|
|
547
535
|
if (this.isArrayProperty(propertyDeclaration)) {
|
|
548
|
-
|
|
536
|
+
var _propertyDeclaration$;
|
|
537
|
+
let elementType;
|
|
538
|
+
if (ts.isArrayTypeNode(propertyDeclaration.type)) elementType = propertyDeclaration.type.elementType;
|
|
539
|
+
else if (ts.isTypeReferenceNode(propertyDeclaration.type) && ((_propertyDeclaration$ = propertyDeclaration.type.typeArguments) === null || _propertyDeclaration$ === void 0 ? void 0 : _propertyDeclaration$[0])) elementType = propertyDeclaration.type.typeArguments[0];
|
|
540
|
+
if (!elementType) return false;
|
|
549
541
|
if (ts.isTypeReferenceNode(elementType) && elementType.typeArguments && elementType.typeArguments.length > 0) {
|
|
550
542
|
const firstTypeArg = elementType.typeArguments[0];
|
|
551
543
|
if (firstTypeArg) {
|
|
@@ -606,7 +598,9 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
606
598
|
}
|
|
607
599
|
isArrayProperty(propertyDeclaration) {
|
|
608
600
|
if (!propertyDeclaration.type) return false;
|
|
609
|
-
|
|
601
|
+
if (ts.isArrayTypeNode(propertyDeclaration.type)) return true;
|
|
602
|
+
if (ts.isTypeReferenceNode(propertyDeclaration.type) && ts.isIdentifier(propertyDeclaration.type.typeName) && propertyDeclaration.type.typeName.text === "Array") return true;
|
|
603
|
+
return false;
|
|
610
604
|
}
|
|
611
605
|
getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration }) {
|
|
612
606
|
let schema = {};
|
|
@@ -641,6 +635,17 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
641
635
|
visitedClass,
|
|
642
636
|
transformedSchema
|
|
643
637
|
});
|
|
638
|
+
else if (property.isGeneric) if (property.genericClassReference) schema = this.getSchemaFromClass({
|
|
639
|
+
isArray: property.isArray,
|
|
640
|
+
visitedClass,
|
|
641
|
+
transformedSchema,
|
|
642
|
+
declaration: property.genericClassReference
|
|
643
|
+
});
|
|
644
|
+
else schema = {
|
|
645
|
+
type: "object",
|
|
646
|
+
properties: {},
|
|
647
|
+
additionalProperties: true
|
|
648
|
+
};
|
|
644
649
|
else schema = {
|
|
645
650
|
type: "object",
|
|
646
651
|
properties: {},
|
|
@@ -961,12 +966,55 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
961
966
|
break;
|
|
962
967
|
}
|
|
963
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Scans all non-declaration source files in the program once and records
|
|
971
|
+
* every call of the form `transform(Foo<Bar, Baz>)`, keyed by class name.
|
|
972
|
+
* This means generic resolution in transform() is a pure O(1) Map lookup
|
|
973
|
+
* with no runtime stack inspection.
|
|
974
|
+
*/
|
|
975
|
+
buildTransformCallIndex() {
|
|
976
|
+
this.program.getSourceFiles().forEach((sf) => {
|
|
977
|
+
if (sf.isDeclarationFile) return;
|
|
978
|
+
sf.statements.forEach((stmt) => {
|
|
979
|
+
if (ts.isClassDeclaration(stmt) && stmt.name) {
|
|
980
|
+
const name = stmt.name.text;
|
|
981
|
+
const entry = this.classFileIndex.get(name) || [];
|
|
982
|
+
entry.push({
|
|
983
|
+
sourceFile: sf,
|
|
984
|
+
node: stmt
|
|
985
|
+
});
|
|
986
|
+
this.classFileIndex.set(name, entry);
|
|
987
|
+
}
|
|
988
|
+
});
|
|
989
|
+
const visit = (node) => {
|
|
990
|
+
if (ts.isCallExpression(node) && node.arguments.length > 0) {
|
|
991
|
+
const callee = node.expression;
|
|
992
|
+
if (ts.isIdentifier(callee) && callee.text === "transform" || ts.isPropertyAccessExpression(callee) && callee.name.text === "transform") {
|
|
993
|
+
const firstArg = node.arguments[0];
|
|
994
|
+
const typeArgs = firstArg.typeArguments;
|
|
995
|
+
if (typeArgs && typeArgs.length > 0) {
|
|
996
|
+
const baseExpr = firstArg.expression ?? firstArg;
|
|
997
|
+
if (ts.isIdentifier(baseExpr)) {
|
|
998
|
+
var _this$classFileIndex$;
|
|
999
|
+
const classNode = (_this$classFileIndex$ = this.classFileIndex.get(baseExpr.text)) === null || _this$classFileIndex$ === void 0 || (_this$classFileIndex$ = _this$classFileIndex$[0]) === null || _this$classFileIndex$ === void 0 ? void 0 : _this$classFileIndex$.node;
|
|
1000
|
+
if (classNode === null || classNode === void 0 ? void 0 : classNode.typeParameters) {
|
|
1001
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1002
|
+
classNode.typeParameters.forEach((param, i) => {
|
|
1003
|
+
const typeArg = typeArgs[i];
|
|
1004
|
+
if (typeArg) typeMap.set(param.name.text, this.getTypeNodeToString(typeArg, /* @__PURE__ */ new Map()));
|
|
1005
|
+
});
|
|
1006
|
+
if (typeMap.size > 0) this.transformCallIndex.set(baseExpr.text, typeMap);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
ts.forEachChild(node, visit);
|
|
1013
|
+
};
|
|
1014
|
+
visit(sf);
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
964
1017
|
transform(cls, sourceOptions) {
|
|
965
|
-
if (this.classCache.has(cls)) return this.classCache.get(cls);
|
|
966
|
-
let schema = {
|
|
967
|
-
type: "object",
|
|
968
|
-
properties: {}
|
|
969
|
-
};
|
|
970
1018
|
const result = this.getSourceFileByClass(cls, sourceOptions);
|
|
971
1019
|
if (!result || !(result === null || result === void 0 ? void 0 : result.sourceFile)) {
|
|
972
1020
|
console.warn(`Class ${cls.name} not found in any source file.`);
|
|
@@ -980,11 +1028,22 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
980
1028
|
}
|
|
981
1029
|
};
|
|
982
1030
|
}
|
|
983
|
-
const
|
|
1031
|
+
const genericTypeMap = this.transformCallIndex.get(cls.name) ?? /* @__PURE__ */ new Map();
|
|
1032
|
+
const hasGenericArgs = genericTypeMap.size > 0;
|
|
1033
|
+
if (!hasGenericArgs && this.classCache.has(cls)) return this.classCache.get(cls);
|
|
1034
|
+
let schema = {
|
|
1035
|
+
type: "object",
|
|
1036
|
+
properties: {}
|
|
1037
|
+
};
|
|
1038
|
+
const properties = this.getPropertiesByClassDeclaration(result.node, void 0, genericTypeMap);
|
|
984
1039
|
schema = this.getSchemaFromProperties({
|
|
985
1040
|
properties,
|
|
986
1041
|
classDeclaration: result.node
|
|
987
1042
|
});
|
|
1043
|
+
if (!hasGenericArgs) this.classCache.set(cls, {
|
|
1044
|
+
name: cls.name,
|
|
1045
|
+
schema
|
|
1046
|
+
});
|
|
988
1047
|
return {
|
|
989
1048
|
name: cls.name,
|
|
990
1049
|
schema
|
package/package.json
CHANGED