ts-class-to-openapi 1.4.0 → 1.4.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/dist/index.cjs +347 -196
- package/dist/index.mjs +347 -196
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -28,6 +28,53 @@ path = __toESM(path);
|
|
|
28
28
|
//#region src/transformer.fixtures.ts
|
|
29
29
|
const constants = {
|
|
30
30
|
TS_CONFIG_DEFAULT_PATH: path.default.resolve(process.cwd(), "tsconfig.json"),
|
|
31
|
+
JS_KEYWORDS: new Set([
|
|
32
|
+
"constructor",
|
|
33
|
+
"static",
|
|
34
|
+
"get",
|
|
35
|
+
"set",
|
|
36
|
+
"async",
|
|
37
|
+
"return",
|
|
38
|
+
"if",
|
|
39
|
+
"else",
|
|
40
|
+
"for",
|
|
41
|
+
"while",
|
|
42
|
+
"switch",
|
|
43
|
+
"case",
|
|
44
|
+
"break",
|
|
45
|
+
"throw",
|
|
46
|
+
"new",
|
|
47
|
+
"delete",
|
|
48
|
+
"typeof",
|
|
49
|
+
"void",
|
|
50
|
+
"try",
|
|
51
|
+
"catch",
|
|
52
|
+
"finally",
|
|
53
|
+
"this",
|
|
54
|
+
"super",
|
|
55
|
+
"class",
|
|
56
|
+
"const",
|
|
57
|
+
"let",
|
|
58
|
+
"var",
|
|
59
|
+
"function",
|
|
60
|
+
"yield",
|
|
61
|
+
"await",
|
|
62
|
+
"import",
|
|
63
|
+
"export",
|
|
64
|
+
"default",
|
|
65
|
+
"extends",
|
|
66
|
+
"implements",
|
|
67
|
+
"in",
|
|
68
|
+
"of",
|
|
69
|
+
"do",
|
|
70
|
+
"with",
|
|
71
|
+
"continue",
|
|
72
|
+
"instanceof",
|
|
73
|
+
"true",
|
|
74
|
+
"false",
|
|
75
|
+
"null",
|
|
76
|
+
"undefined"
|
|
77
|
+
]),
|
|
31
78
|
jsPrimitives: {
|
|
32
79
|
String: {
|
|
33
80
|
type: "String",
|
|
@@ -189,6 +236,8 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
189
236
|
maxCacheSize;
|
|
190
237
|
autoCleanup;
|
|
191
238
|
classFileIndex = /* @__PURE__ */ new Map();
|
|
239
|
+
transformCallIndex = /* @__PURE__ */ new Map();
|
|
240
|
+
nonGenericTransformCalls = /* @__PURE__ */ new Set();
|
|
192
241
|
constructor(tsConfigPath = constants.TS_CONFIG_DEFAULT_PATH, options = {}) {
|
|
193
242
|
this.maxCacheSize = options.maxCacheSize ?? 100;
|
|
194
243
|
this.autoCleanup = options.autoCleanup ?? true;
|
|
@@ -200,19 +249,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
200
249
|
const { options: tsOptions, fileNames } = typescript.default.parseJsonConfigFileContent(config, typescript.default.sys, "./");
|
|
201
250
|
this.program = typescript.default.createProgram(fileNames, tsOptions);
|
|
202
251
|
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
|
-
});
|
|
252
|
+
this.buildTransformCallIndex();
|
|
216
253
|
}
|
|
217
254
|
getPropertiesByClassDeclaration(classNode, visitedDeclarations = /* @__PURE__ */ new Set(), genericTypeMap = /* @__PURE__ */ new Map()) {
|
|
218
255
|
if (visitedDeclarations.has(classNode)) return [];
|
|
@@ -247,76 +284,89 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
247
284
|
}
|
|
248
285
|
getPropertiesByClassMembers(members, parentClassNode, genericTypeMap = /* @__PURE__ */ new Map()) {
|
|
249
286
|
const properties = [];
|
|
250
|
-
for (const member of members) {
|
|
251
|
-
var
|
|
252
|
-
if (
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
287
|
+
for (const member of members) if (typescript.default.isPropertyDeclaration(member) && (typescript.default.isIdentifier(member.name) || typescript.default.isStringLiteral(member.name))) {
|
|
288
|
+
var _property$originalPro;
|
|
289
|
+
if (member.modifiers) {
|
|
290
|
+
if (member.modifiers.some((m) => m.kind === typescript.default.SyntaxKind.PrivateKeyword || m.kind === typescript.default.SyntaxKind.ProtectedKeyword)) continue;
|
|
291
|
+
}
|
|
292
|
+
const propertyName = member.name.text;
|
|
293
|
+
const type = this.getPropertyType(member, genericTypeMap);
|
|
294
|
+
const decorators = this.extractDecorators(member);
|
|
295
|
+
const isOptional = !!member.questionToken;
|
|
296
|
+
const isGeneric = this.isPropertyTypeGeneric(member);
|
|
297
|
+
const isEnum = this.isEnum(member);
|
|
298
|
+
const isPrimitive = this.isPrimitiveType(type) || isEnum;
|
|
299
|
+
const isClassType = this.isClassType(member);
|
|
300
|
+
const isArray = this.isArrayProperty(member);
|
|
301
|
+
const isTypeLiteral = this.isTypeLiteral(member);
|
|
302
|
+
let genericClassReference = void 0;
|
|
303
|
+
if (isGeneric && !isPrimitive) {
|
|
304
|
+
const baseTypeName = type.replace(/\[\]$/, "").trim();
|
|
305
|
+
if (!this.isPrimitiveType(baseTypeName)) {
|
|
306
|
+
const matches = this.classFileIndex.get(baseTypeName);
|
|
307
|
+
if (matches && matches.length > 0 && matches[0]) genericClassReference = matches[0].node;
|
|
256
308
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (property.isClassType) {
|
|
278
|
-
const declaration = this.getDeclarationProperty(property);
|
|
279
|
-
if (parentClassNode) {
|
|
280
|
-
if (declaration && declaration.name && this.checker.getSymbolAtLocation(declaration.name) === this.checker.getSymbolAtLocation(parentClassNode.name)) property.isRef = true;
|
|
281
|
-
}
|
|
309
|
+
}
|
|
310
|
+
const property = {
|
|
311
|
+
name: propertyName,
|
|
312
|
+
type,
|
|
313
|
+
decorators,
|
|
314
|
+
isOptional,
|
|
315
|
+
isGeneric,
|
|
316
|
+
originalProperty: member,
|
|
317
|
+
isPrimitive,
|
|
318
|
+
isClassType,
|
|
319
|
+
isArray,
|
|
320
|
+
isEnum,
|
|
321
|
+
isRef: false,
|
|
322
|
+
isTypeLiteral,
|
|
323
|
+
genericClassReference
|
|
324
|
+
};
|
|
325
|
+
if (property.isClassType) {
|
|
326
|
+
const declaration = this.getDeclarationProperty(property);
|
|
327
|
+
if (parentClassNode) {
|
|
328
|
+
if (declaration && declaration.name && this.checker.getSymbolAtLocation(declaration.name) === this.checker.getSymbolAtLocation(parentClassNode.name)) property.isRef = true;
|
|
282
329
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
330
|
+
}
|
|
331
|
+
if (property.isTypeLiteral && property.originalProperty.type !== void 0 && typescript.default.isTypeReferenceNode(property.originalProperty.type) && ((_property$originalPro = property.originalProperty.type.typeArguments) === null || _property$originalPro === void 0 ? void 0 : _property$originalPro.length) === 1) {
|
|
332
|
+
const typeArguments = property.originalProperty.type.typeArguments;
|
|
333
|
+
if (typeArguments && typeArguments[0]) {
|
|
334
|
+
const firstTypeArg = typeArguments[0];
|
|
335
|
+
if (typescript.default.isTypeReferenceNode(firstTypeArg)) {
|
|
336
|
+
const symbol = this.checker.getTypeAtLocation(firstTypeArg).getSymbol();
|
|
337
|
+
if (symbol && symbol.declarations) {
|
|
338
|
+
const classDeclaration = symbol.declarations.find((decl) => typescript.default.isClassDeclaration(decl));
|
|
339
|
+
if (classDeclaration && typescript.default.isClassDeclaration(classDeclaration)) property.typeLiteralClassReference = classDeclaration;
|
|
293
340
|
}
|
|
294
341
|
}
|
|
295
342
|
}
|
|
296
|
-
properties.push(property);
|
|
297
343
|
}
|
|
344
|
+
properties.push(property);
|
|
298
345
|
}
|
|
299
346
|
return properties;
|
|
300
347
|
}
|
|
301
348
|
getPropertyType(property, genericTypeMap = /* @__PURE__ */ new Map()) {
|
|
302
349
|
if (property.type) return this.getTypeNodeToString(property.type, genericTypeMap);
|
|
303
350
|
const type = this.checker.getTypeAtLocation(property);
|
|
304
|
-
return this.
|
|
351
|
+
return this.checker.typeToString(type);
|
|
305
352
|
}
|
|
306
353
|
getTypeNodeToString(typeNode, genericTypeMap = /* @__PURE__ */ new Map()) {
|
|
307
|
-
if (typescript.default.isTypeReferenceNode(typeNode)
|
|
308
|
-
|
|
354
|
+
if (typescript.default.isTypeReferenceNode(typeNode)) {
|
|
355
|
+
let typeName;
|
|
356
|
+
if (typescript.default.isIdentifier(typeNode.typeName)) typeName = typeNode.typeName.text;
|
|
357
|
+
else typeName = typeNode.typeName.right.text;
|
|
309
358
|
if (genericTypeMap.has(typeName)) return genericTypeMap.get(typeName);
|
|
310
359
|
if (typeName.toLowerCase() === "uploadfile") return "UploadFile";
|
|
311
360
|
if (typeName.toLowerCase() === "uploadfiledto") return "UploadFileDto";
|
|
312
361
|
if (typeNode.typeArguments && typeNode.typeArguments.length > 0) {
|
|
362
|
+
if (typeName === "Array") return `${this.getTypeNodeToString(typeNode.typeArguments[0], genericTypeMap)}[]`;
|
|
313
363
|
const firstTypeArg = typeNode.typeArguments[0];
|
|
314
364
|
if (firstTypeArg && typescript.default.isTypeReferenceNode(firstTypeArg) && typescript.default.isIdentifier(firstTypeArg.typeName)) {
|
|
315
365
|
if (firstTypeArg.typeName.text.toLowerCase() === "uploadfile") return "UploadFile";
|
|
316
366
|
}
|
|
317
367
|
return this.resolveGenericType(typeNode);
|
|
318
368
|
}
|
|
319
|
-
return
|
|
369
|
+
return typeName;
|
|
320
370
|
}
|
|
321
371
|
switch (typeNode.kind) {
|
|
322
372
|
case typescript.default.SyntaxKind.StringKeyword: return constants.jsPrimitives.String.type;
|
|
@@ -331,31 +381,31 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
331
381
|
if (meaningfulTypes.length > 0 && meaningfulTypes[0]) return meaningfulTypes[0];
|
|
332
382
|
if (types.length > 0 && types[0]) return types[0];
|
|
333
383
|
return "object";
|
|
334
|
-
default:
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return typeText;
|
|
384
|
+
default: {
|
|
385
|
+
const resolvedType = this.checker.getTypeAtLocation(typeNode);
|
|
386
|
+
const resolved = this.checker.typeToString(resolvedType);
|
|
387
|
+
if (genericTypeMap && genericTypeMap.has(resolved)) return genericTypeMap.get(resolved);
|
|
388
|
+
if (this.isPrimitiveType(resolved)) return resolved;
|
|
389
|
+
if (resolved === "Date") return constants.jsPrimitives.Date.type;
|
|
390
|
+
if (resolved === "Buffer") return constants.jsPrimitives.Buffer.type;
|
|
391
|
+
if (resolved === "Uint8Array") return constants.jsPrimitives.Uint8Array.type;
|
|
392
|
+
return resolved;
|
|
393
|
+
}
|
|
345
394
|
}
|
|
346
395
|
}
|
|
347
396
|
resolveGenericType(typeNode) {
|
|
348
|
-
|
|
397
|
+
let typeName;
|
|
398
|
+
if (typescript.default.isIdentifier(typeNode.typeName)) typeName = typeNode.typeName.text;
|
|
399
|
+
else typeName = typeNode.typeName.right.text;
|
|
349
400
|
const typeArguments = typeNode.typeArguments;
|
|
350
401
|
if (!typeArguments || typeArguments.length === 0) return typeName;
|
|
351
402
|
const type = this.checker.getTypeAtLocation(typeNode);
|
|
352
|
-
|
|
353
|
-
|
|
403
|
+
if (!(type.flags & typescript.default.TypeFlags.Any)) {
|
|
404
|
+
const resolvedType = this.checker.typeToString(type);
|
|
405
|
+
if (resolvedType && resolvedType !== typeName) return resolvedType;
|
|
406
|
+
}
|
|
354
407
|
return typeName;
|
|
355
408
|
}
|
|
356
|
-
getStringFromType(type) {
|
|
357
|
-
return this.checker.typeToString(type);
|
|
358
|
-
}
|
|
359
409
|
extractDecorators(member) {
|
|
360
410
|
const decorators = [];
|
|
361
411
|
if (member.modifiers) {
|
|
@@ -375,6 +425,7 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
375
425
|
}
|
|
376
426
|
getDecoratorName(callExpression) {
|
|
377
427
|
if (typescript.default.isIdentifier(callExpression.expression)) return callExpression.expression.text;
|
|
428
|
+
if (typescript.default.isPropertyAccessExpression(callExpression.expression)) return callExpression.expression.name.text;
|
|
378
429
|
return "unknown";
|
|
379
430
|
}
|
|
380
431
|
getDecoratorArguments(callExpression) {
|
|
@@ -387,7 +438,13 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
387
438
|
});
|
|
388
439
|
}
|
|
389
440
|
getSafeDecoratorArgument(arg) {
|
|
390
|
-
if (arg && typeof arg === "object" && "kind" in arg)
|
|
441
|
+
if (arg && typeof arg === "object" && "kind" in arg) {
|
|
442
|
+
const node = arg;
|
|
443
|
+
const type = this.checker.getTypeAtLocation(node);
|
|
444
|
+
if (type.isNumberLiteral()) return type.value;
|
|
445
|
+
if (type.isStringLiteral()) return type.value;
|
|
446
|
+
return this.checker.typeToString(type);
|
|
447
|
+
}
|
|
391
448
|
return arg;
|
|
392
449
|
}
|
|
393
450
|
isPropertyTypeGeneric(property) {
|
|
@@ -411,15 +468,18 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
411
468
|
isGenericTypeFromSymbol(type) {
|
|
412
469
|
if (this.isSimpleArrayType(type)) return false;
|
|
413
470
|
if (type.aliasTypeArguments && type.aliasTypeArguments.length > 0) return true;
|
|
414
|
-
if (type.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
471
|
+
if (type.flags & typescript.default.TypeFlags.Object && type.objectFlags & typescript.default.ObjectFlags.Reference) {
|
|
472
|
+
var _typeArgs$;
|
|
473
|
+
const typeArgs = this.checker.getTypeArguments(type);
|
|
474
|
+
if (typeArgs.length > 0 && ((_typeArgs$ = typeArgs[0]) === null || _typeArgs$ === void 0 || (_typeArgs$ = _typeArgs$.getSymbol()) === null || _typeArgs$ === void 0 ? void 0 : _typeArgs$.getName()) === "Array") {
|
|
475
|
+
const symbol = type.getSymbol();
|
|
476
|
+
if (symbol && symbol.getName() === "Array") {
|
|
477
|
+
const elementType = typeArgs[0];
|
|
478
|
+
return elementType ? this.isUtilityTypeFromType(elementType) : false;
|
|
479
|
+
}
|
|
480
|
+
const elementType = typeArgs[0];
|
|
481
|
+
return elementType ? this.isUtilityTypeFromType(elementType) : false;
|
|
420
482
|
}
|
|
421
|
-
const elementType = type.typeArguments[0];
|
|
422
|
-
return this.isUtilityTypeFromType(elementType);
|
|
423
483
|
}
|
|
424
484
|
if (type.flags & typescript.default.TypeFlags.TypeParameter) return true;
|
|
425
485
|
if (type.flags & typescript.default.TypeFlags.Conditional) return true;
|
|
@@ -451,12 +511,18 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
451
511
|
isSimpleArrayType(type) {
|
|
452
512
|
const symbol = type.getSymbol();
|
|
453
513
|
if (!symbol || symbol.getName() !== "Array") return false;
|
|
454
|
-
if (type.
|
|
455
|
-
const
|
|
456
|
-
if (
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
514
|
+
if (type.flags & typescript.default.TypeFlags.Object && type.objectFlags & typescript.default.ObjectFlags.Reference) {
|
|
515
|
+
const typeArgs = this.checker.getTypeArguments(type);
|
|
516
|
+
if (typeArgs.length === 1) {
|
|
517
|
+
const elementType = typeArgs[0];
|
|
518
|
+
if (this.isUtilityTypeFromType(elementType)) return false;
|
|
519
|
+
if (elementType.flags & typescript.default.TypeFlags.Object && elementType.objectFlags & typescript.default.ObjectFlags.Reference) {
|
|
520
|
+
if (this.checker.getTypeArguments(elementType).length > 0) return false;
|
|
521
|
+
}
|
|
522
|
+
const elementSymbol = elementType.getSymbol();
|
|
523
|
+
if (elementSymbol && elementSymbol.getName() !== "Array") return false;
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
460
526
|
}
|
|
461
527
|
return false;
|
|
462
528
|
}
|
|
@@ -513,40 +579,41 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
513
579
|
}
|
|
514
580
|
return matches[0];
|
|
515
581
|
}
|
|
516
|
-
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
582
|
+
findBestMatch(cls, matches) {
|
|
583
|
+
const runtimeProps = this.extractRuntimePropertyNames(cls);
|
|
584
|
+
let bestMatch;
|
|
585
|
+
let bestScore = -1;
|
|
586
|
+
for (const match of matches) {
|
|
587
|
+
let score = 0;
|
|
588
|
+
for (const member of match.node.members) if (typescript.default.isPropertyDeclaration(member) && member.name && (typescript.default.isIdentifier(member.name) || typescript.default.isStringLiteral(member.name))) {
|
|
589
|
+
if (runtimeProps.has(member.name.text)) score++;
|
|
590
|
+
}
|
|
591
|
+
if (score > bestScore) {
|
|
592
|
+
bestScore = score;
|
|
593
|
+
bestMatch = match;
|
|
594
|
+
}
|
|
529
595
|
}
|
|
530
|
-
return
|
|
596
|
+
return bestMatch;
|
|
531
597
|
}
|
|
532
|
-
|
|
533
|
-
|
|
598
|
+
/**
|
|
599
|
+
* Safely extracts property names from a class constructor without instantiation.
|
|
600
|
+
* Parses the class source via Function.prototype.toString() and inspects
|
|
601
|
+
* the prototype for method names.
|
|
602
|
+
*/
|
|
603
|
+
extractRuntimePropertyNames(cls) {
|
|
604
|
+
const names = /* @__PURE__ */ new Set();
|
|
534
605
|
try {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
matchesMap[index] = fountProperties;
|
|
547
|
-
});
|
|
548
|
-
const maxMatches = Math.max(...Object.values(matchesMap));
|
|
549
|
-
return matches[Object.values(matchesMap).findIndex((value) => value === maxMatches)];
|
|
606
|
+
const source = Function.prototype.toString.call(cls);
|
|
607
|
+
const thisAssign = /this\.(\w+)\s*=/g;
|
|
608
|
+
let m;
|
|
609
|
+
while ((m = thisAssign.exec(source)) !== null) if (m[1]) names.add(m[1]);
|
|
610
|
+
const classField = /[;}](\w+)(?=[=;}\s])/g;
|
|
611
|
+
while ((m = classField.exec(source)) !== null) if (m[1] && !constants.JS_KEYWORDS.has(m[1])) names.add(m[1]);
|
|
612
|
+
} catch {}
|
|
613
|
+
try {
|
|
614
|
+
for (const name of Object.getOwnPropertyNames(cls.prototype)) if (name !== "constructor") names.add(name);
|
|
615
|
+
} catch {}
|
|
616
|
+
return names;
|
|
550
617
|
}
|
|
551
618
|
getFilteredSourceFiles(sourceOptions) {
|
|
552
619
|
if (sourceOptions === null || sourceOptions === void 0 ? void 0 : sourceOptions.isExternal) return this.program.getSourceFiles().filter((sf) => {
|
|
@@ -561,77 +628,60 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
561
628
|
if (!propertyDeclaration.type) return false;
|
|
562
629
|
let typeNode = propertyDeclaration.type;
|
|
563
630
|
if (typescript.default.isArrayTypeNode(typeNode)) typeNode = typeNode.elementType;
|
|
564
|
-
if (typescript.default.
|
|
565
|
-
const
|
|
566
|
-
|
|
631
|
+
if (typescript.default.isUnionTypeNode(typeNode)) {
|
|
632
|
+
const nonNullTypes = typeNode.types.filter((t) => t.kind !== typescript.default.SyntaxKind.NullKeyword && t.kind !== typescript.default.SyntaxKind.UndefinedKeyword);
|
|
633
|
+
if (nonNullTypes.length === 1 && nonNullTypes[0]) typeNode = nonNullTypes[0];
|
|
567
634
|
}
|
|
635
|
+
if (typescript.default.isTypeReferenceNode(typeNode)) return !!(this.checker.getTypeAtLocation(typeNode).flags & typescript.default.TypeFlags.EnumLike);
|
|
568
636
|
return false;
|
|
569
637
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
if (argSymbol.declarations.some((decl) => typescript.default.isClassDeclaration(decl))) return true;
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
const symbol = this.checker.getTypeAtLocation(elementType).getSymbol();
|
|
584
|
-
if (symbol && symbol.declarations) return symbol.declarations.some((decl) => typescript.default.isClassDeclaration(decl));
|
|
585
|
-
return false;
|
|
586
|
-
} else {
|
|
587
|
-
if (typescript.default.isTypeReferenceNode(propertyDeclaration.type) && propertyDeclaration.type.typeArguments && propertyDeclaration.type.typeArguments.length > 0) {
|
|
588
|
-
const firstTypeArg = propertyDeclaration.type.typeArguments[0];
|
|
589
|
-
if (firstTypeArg) {
|
|
590
|
-
const argSymbol = this.checker.getTypeAtLocation(firstTypeArg).getSymbol();
|
|
591
|
-
if (argSymbol && argSymbol.declarations) {
|
|
592
|
-
if (argSymbol.declarations.some((decl) => typescript.default.isClassDeclaration(decl))) return true;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
const symbol = this.checker.getTypeAtLocation(propertyDeclaration.type).getSymbol();
|
|
597
|
-
if (symbol && symbol.declarations) return symbol.declarations.some((decl) => typescript.default.isClassDeclaration(decl));
|
|
598
|
-
return false;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
getDeclarationProperty(property) {
|
|
602
|
-
if (!property.originalProperty.type) return;
|
|
603
|
-
if (typescript.default.isArrayTypeNode(property.originalProperty.type)) {
|
|
604
|
-
const elementType = property.originalProperty.type.elementType;
|
|
605
|
-
if (typescript.default.isTypeReferenceNode(elementType) && elementType.typeArguments && elementType.typeArguments.length > 0) {
|
|
606
|
-
const firstTypeArg = elementType.typeArguments[0];
|
|
607
|
-
if (firstTypeArg) {
|
|
608
|
-
const argSymbol = this.checker.getTypeAtLocation(firstTypeArg).getSymbol();
|
|
609
|
-
if (argSymbol && argSymbol.declarations) {
|
|
610
|
-
const classDecl = argSymbol.declarations.find((decl) => typescript.default.isClassDeclaration(decl));
|
|
611
|
-
if (classDecl) return classDecl;
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
const symbol = this.checker.getTypeAtLocation(elementType).getSymbol();
|
|
616
|
-
if (symbol && symbol.declarations) return symbol.declarations.find((decl) => typescript.default.isClassDeclaration(decl)) || symbol.declarations[0];
|
|
617
|
-
return;
|
|
618
|
-
}
|
|
619
|
-
if (typescript.default.isTypeReferenceNode(property.originalProperty.type) && property.originalProperty.type.typeArguments && property.originalProperty.type.typeArguments.length > 0) {
|
|
620
|
-
const firstTypeArg = property.originalProperty.type.typeArguments[0];
|
|
638
|
+
/**
|
|
639
|
+
* Resolves a type node to its underlying symbol via the type-checker.
|
|
640
|
+
* For type references with type arguments (e.g., PayloadEntity<Person>),
|
|
641
|
+
* it checks the first type argument for a class declaration first.
|
|
642
|
+
* Returns the ts.Symbol or undefined.
|
|
643
|
+
*/
|
|
644
|
+
resolveClassSymbolFromTypeNode(typeNode) {
|
|
645
|
+
if (typescript.default.isTypeReferenceNode(typeNode) && typeNode.typeArguments && typeNode.typeArguments.length > 0) {
|
|
646
|
+
const firstTypeArg = typeNode.typeArguments[0];
|
|
621
647
|
if (firstTypeArg) {
|
|
622
648
|
const argSymbol = this.checker.getTypeAtLocation(firstTypeArg).getSymbol();
|
|
623
649
|
if (argSymbol && argSymbol.declarations) {
|
|
624
|
-
|
|
625
|
-
if (classDecl) return classDecl;
|
|
650
|
+
if (argSymbol.declarations.some((decl) => typescript.default.isClassDeclaration(decl))) return argSymbol;
|
|
626
651
|
}
|
|
627
652
|
}
|
|
628
653
|
}
|
|
629
|
-
|
|
630
|
-
|
|
654
|
+
return this.checker.getTypeAtLocation(typeNode).getSymbol() ?? void 0;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Resolves the element type node from an array property declaration.
|
|
658
|
+
* Handles both `T[]` and `Array<T>` syntax.
|
|
659
|
+
*/
|
|
660
|
+
resolveArrayElementTypeNode(propertyDeclaration) {
|
|
661
|
+
var _propertyDeclaration$;
|
|
662
|
+
if (!propertyDeclaration.type) return void 0;
|
|
663
|
+
if (typescript.default.isArrayTypeNode(propertyDeclaration.type)) return propertyDeclaration.type.elementType;
|
|
664
|
+
if (typescript.default.isTypeReferenceNode(propertyDeclaration.type) && ((_propertyDeclaration$ = propertyDeclaration.type.typeArguments) === null || _propertyDeclaration$ === void 0 ? void 0 : _propertyDeclaration$[0])) return propertyDeclaration.type.typeArguments[0];
|
|
665
|
+
}
|
|
666
|
+
isClassType(propertyDeclaration) {
|
|
667
|
+
if (!propertyDeclaration.type) return false;
|
|
668
|
+
const typeNode = this.isArrayProperty(propertyDeclaration) ? this.resolveArrayElementTypeNode(propertyDeclaration) : propertyDeclaration.type;
|
|
669
|
+
if (!typeNode) return false;
|
|
670
|
+
const symbol = this.resolveClassSymbolFromTypeNode(typeNode);
|
|
671
|
+
if (symbol && symbol.declarations) return symbol.declarations.some((decl) => typescript.default.isClassDeclaration(decl));
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
getDeclarationProperty(property) {
|
|
675
|
+
if (!property.originalProperty.type) return;
|
|
676
|
+
const typeNode = typescript.default.isArrayTypeNode(property.originalProperty.type) ? property.originalProperty.type.elementType : property.originalProperty.type;
|
|
677
|
+
const symbol = this.resolveClassSymbolFromTypeNode(typeNode);
|
|
678
|
+
if (symbol && symbol.declarations) return symbol.declarations.find((decl) => typescript.default.isClassDeclaration(decl)) ?? symbol.declarations[0];
|
|
631
679
|
}
|
|
632
680
|
isArrayProperty(propertyDeclaration) {
|
|
633
681
|
if (!propertyDeclaration.type) return false;
|
|
634
|
-
|
|
682
|
+
if (typescript.default.isArrayTypeNode(propertyDeclaration.type)) return true;
|
|
683
|
+
if (typescript.default.isTypeReferenceNode(propertyDeclaration.type) && typescript.default.isIdentifier(propertyDeclaration.type.typeName) && propertyDeclaration.type.typeName.text === "Array") return true;
|
|
684
|
+
return false;
|
|
635
685
|
}
|
|
636
686
|
getSchemaFromProperties({ properties, visitedClass, transformedSchema, classDeclaration }) {
|
|
637
687
|
let schema = {};
|
|
@@ -666,11 +716,34 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
666
716
|
visitedClass,
|
|
667
717
|
transformedSchema
|
|
668
718
|
});
|
|
669
|
-
else schema = {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
719
|
+
else if (property.isGeneric) if (property.genericClassReference) schema = this.getSchemaFromClass({
|
|
720
|
+
isArray: property.isArray,
|
|
721
|
+
visitedClass,
|
|
722
|
+
transformedSchema,
|
|
723
|
+
declaration: property.genericClassReference
|
|
724
|
+
});
|
|
725
|
+
else {
|
|
726
|
+
const inner = {
|
|
727
|
+
type: "object",
|
|
728
|
+
properties: {},
|
|
729
|
+
additionalProperties: true
|
|
730
|
+
};
|
|
731
|
+
schema = property.isArray ? {
|
|
732
|
+
type: "array",
|
|
733
|
+
items: inner
|
|
734
|
+
} : inner;
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
const inner = {
|
|
738
|
+
type: "object",
|
|
739
|
+
properties: {},
|
|
740
|
+
additionalProperties: true
|
|
741
|
+
};
|
|
742
|
+
schema = property.isArray ? {
|
|
743
|
+
type: "array",
|
|
744
|
+
items: inner
|
|
745
|
+
} : inner;
|
|
746
|
+
}
|
|
674
747
|
this.applyDecorators(property, schema);
|
|
675
748
|
return schema;
|
|
676
749
|
}
|
|
@@ -766,7 +839,12 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
766
839
|
if (enumSchema) return enumSchema;
|
|
767
840
|
}
|
|
768
841
|
const propertySchema = { type: "object" };
|
|
769
|
-
|
|
842
|
+
let baseTypeNode = property.originalProperty.type;
|
|
843
|
+
if (property.isArray && baseTypeNode) baseTypeNode = this.resolveArrayElementTypeNode(property.originalProperty) ?? baseTypeNode;
|
|
844
|
+
const resolvedType = this.checker.getTypeAtLocation(baseTypeNode ?? property.originalProperty);
|
|
845
|
+
let propertyType;
|
|
846
|
+
if (resolvedType.flags & typescript.default.TypeFlags.TypeParameter) propertyType = property.type.toLowerCase().replace(/\[\]$/, "").trim();
|
|
847
|
+
else propertyType = this.checker.typeToString(resolvedType).toLowerCase();
|
|
770
848
|
let isFile = false;
|
|
771
849
|
switch (propertyType) {
|
|
772
850
|
case constants.jsPrimitives.String.value:
|
|
@@ -803,17 +881,26 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
803
881
|
propertySchema.type = constants.jsPrimitives.Symbol.value;
|
|
804
882
|
break;
|
|
805
883
|
case constants.jsPrimitives.Object.value:
|
|
884
|
+
case "unknown":
|
|
885
|
+
case "any":
|
|
806
886
|
propertySchema.type = constants.jsPrimitives.Object.value;
|
|
887
|
+
propertySchema.additionalProperties = true;
|
|
807
888
|
break;
|
|
808
889
|
default: propertySchema.type = constants.jsPrimitives.String.value;
|
|
809
890
|
}
|
|
810
891
|
if (property.isArray) {
|
|
811
892
|
delete propertySchema.format;
|
|
812
|
-
propertySchema.type
|
|
813
|
-
|
|
814
|
-
type: isFile ? constants.jsPrimitives.UploadFile.value :
|
|
893
|
+
const resolvedItemType = propertySchema.type;
|
|
894
|
+
const itemSchema = {
|
|
895
|
+
type: isFile ? constants.jsPrimitives.UploadFile.value : resolvedItemType,
|
|
815
896
|
format: isFile ? constants.jsPrimitives.UploadFile.format : propertySchema.format
|
|
816
897
|
};
|
|
898
|
+
if (propertySchema.additionalProperties) {
|
|
899
|
+
itemSchema.additionalProperties = true;
|
|
900
|
+
delete propertySchema.additionalProperties;
|
|
901
|
+
}
|
|
902
|
+
propertySchema.type = `array`;
|
|
903
|
+
propertySchema.items = itemSchema;
|
|
817
904
|
}
|
|
818
905
|
return propertySchema;
|
|
819
906
|
}
|
|
@@ -986,12 +1073,58 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
986
1073
|
break;
|
|
987
1074
|
}
|
|
988
1075
|
}
|
|
1076
|
+
/**
|
|
1077
|
+
* Scans all non-declaration source files in the program once and records
|
|
1078
|
+
* every call of the form `transform(Foo<Bar, Baz>)`, keyed by class name.
|
|
1079
|
+
* This means generic resolution in transform() is a pure O(1) Map lookup
|
|
1080
|
+
* with no runtime stack inspection.
|
|
1081
|
+
*/
|
|
1082
|
+
buildTransformCallIndex() {
|
|
1083
|
+
this.program.getSourceFiles().forEach((sf) => {
|
|
1084
|
+
if (sf.isDeclarationFile) return;
|
|
1085
|
+
sf.statements.forEach((stmt) => {
|
|
1086
|
+
if (typescript.default.isClassDeclaration(stmt) && stmt.name) {
|
|
1087
|
+
const name = stmt.name.text;
|
|
1088
|
+
const entry = this.classFileIndex.get(name) || [];
|
|
1089
|
+
entry.push({
|
|
1090
|
+
sourceFile: sf,
|
|
1091
|
+
node: stmt
|
|
1092
|
+
});
|
|
1093
|
+
this.classFileIndex.set(name, entry);
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
const visit = (node) => {
|
|
1097
|
+
if (typescript.default.isCallExpression(node) && node.arguments.length > 0) {
|
|
1098
|
+
const callee = node.expression;
|
|
1099
|
+
if (typescript.default.isIdentifier(callee) && callee.text === "transform" || typescript.default.isPropertyAccessExpression(callee) && callee.name.text === "transform") {
|
|
1100
|
+
const firstArg = node.arguments[0];
|
|
1101
|
+
if (typescript.default.isExpressionWithTypeArguments(firstArg) && firstArg.typeArguments && firstArg.typeArguments.length > 0) {
|
|
1102
|
+
const baseExpr = firstArg.expression;
|
|
1103
|
+
const className = typescript.default.isIdentifier(baseExpr) ? baseExpr.text : typescript.default.isPropertyAccessExpression(baseExpr) ? baseExpr.name.text : void 0;
|
|
1104
|
+
if (className) {
|
|
1105
|
+
var _this$classFileIndex$;
|
|
1106
|
+
const classNode = (_this$classFileIndex$ = this.classFileIndex.get(className)) === null || _this$classFileIndex$ === void 0 || (_this$classFileIndex$ = _this$classFileIndex$[0]) === null || _this$classFileIndex$ === void 0 ? void 0 : _this$classFileIndex$.node;
|
|
1107
|
+
if (classNode === null || classNode === void 0 ? void 0 : classNode.typeParameters) {
|
|
1108
|
+
const typeMap = /* @__PURE__ */ new Map();
|
|
1109
|
+
classNode.typeParameters.forEach((param, i) => {
|
|
1110
|
+
const typeArg = firstArg.typeArguments[i];
|
|
1111
|
+
if (typeArg) typeMap.set(param.name.text, this.getTypeNodeToString(typeArg, /* @__PURE__ */ new Map()));
|
|
1112
|
+
});
|
|
1113
|
+
if (typeMap.size > 0) this.transformCallIndex.set(className, typeMap);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
} else {
|
|
1117
|
+
const className = typescript.default.isIdentifier(firstArg) ? firstArg.text : typescript.default.isPropertyAccessExpression(firstArg) ? firstArg.name.text : void 0;
|
|
1118
|
+
if (className) this.nonGenericTransformCalls.add(className);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
typescript.default.forEachChild(node, visit);
|
|
1123
|
+
};
|
|
1124
|
+
visit(sf);
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
989
1127
|
transform(cls, sourceOptions) {
|
|
990
|
-
if (this.classCache.has(cls)) return this.classCache.get(cls);
|
|
991
|
-
let schema = {
|
|
992
|
-
type: "object",
|
|
993
|
-
properties: {}
|
|
994
|
-
};
|
|
995
1128
|
const result = this.getSourceFileByClass(cls, sourceOptions);
|
|
996
1129
|
if (!result || !(result === null || result === void 0 ? void 0 : result.sourceFile)) {
|
|
997
1130
|
console.warn(`Class ${cls.name} not found in any source file.`);
|
|
@@ -1005,11 +1138,29 @@ var SchemaTransformer = class SchemaTransformer {
|
|
|
1005
1138
|
}
|
|
1006
1139
|
};
|
|
1007
1140
|
}
|
|
1008
|
-
const
|
|
1141
|
+
const genericTypeMap = /* @__PURE__ */ new Map();
|
|
1142
|
+
if (result.node.typeParameters) {
|
|
1143
|
+
const indexedTypeMap = this.transformCallIndex.get(cls.name);
|
|
1144
|
+
const hasNonGenericCall = this.nonGenericTransformCalls.has(cls.name);
|
|
1145
|
+
const allHaveDefaults = result.node.typeParameters.every((p) => !!p.default);
|
|
1146
|
+
if (indexedTypeMap && !(hasNonGenericCall && allHaveDefaults)) for (const [key, value] of indexedTypeMap) genericTypeMap.set(key, value);
|
|
1147
|
+
else for (const param of result.node.typeParameters) if (param.default) genericTypeMap.set(param.name.text, this.getTypeNodeToString(param.default, /* @__PURE__ */ new Map()));
|
|
1148
|
+
}
|
|
1149
|
+
const hasGenericArgs = genericTypeMap.size > 0;
|
|
1150
|
+
if (!hasGenericArgs && this.classCache.has(cls)) return this.classCache.get(cls);
|
|
1151
|
+
let schema = {
|
|
1152
|
+
type: "object",
|
|
1153
|
+
properties: {}
|
|
1154
|
+
};
|
|
1155
|
+
const properties = this.getPropertiesByClassDeclaration(result.node, void 0, genericTypeMap);
|
|
1009
1156
|
schema = this.getSchemaFromProperties({
|
|
1010
1157
|
properties,
|
|
1011
1158
|
classDeclaration: result.node
|
|
1012
1159
|
});
|
|
1160
|
+
if (!hasGenericArgs) this.classCache.set(cls, {
|
|
1161
|
+
name: cls.name,
|
|
1162
|
+
schema
|
|
1163
|
+
});
|
|
1013
1164
|
return {
|
|
1014
1165
|
name: cls.name,
|
|
1015
1166
|
schema
|