@tsoa-next/cli 8.0.3 → 8.0.4

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.
@@ -53,8 +53,26 @@ const localReferenceTypeCache = {};
53
53
  const inProgressTypes = {};
54
54
  const escapedDoubleQuote = String.raw `\"`;
55
55
  const backslash = '\\';
56
+ const symbolModuleOriginCache = new WeakMap();
57
+ const ioTsUtilityTypeCache = new WeakMap();
56
58
  const hasInitializer = (declaration) => 'initializer' in declaration && declaration.initializer !== undefined;
57
59
  const objectHasOwn = Object.hasOwn;
60
+ const getSymbolModuleOriginCache = (typeChecker) => {
61
+ let cache = symbolModuleOriginCache.get(typeChecker);
62
+ if (!cache) {
63
+ cache = new WeakMap();
64
+ symbolModuleOriginCache.set(typeChecker, cache);
65
+ }
66
+ return cache;
67
+ };
68
+ const getIoTsUtilityTypeCache = (typeChecker) => {
69
+ let cache = ioTsUtilityTypeCache.get(typeChecker);
70
+ if (!cache) {
71
+ cache = new WeakMap();
72
+ ioTsUtilityTypeCache.set(typeChecker, cache);
73
+ }
74
+ return cache;
75
+ };
58
76
  const getSyntheticOrigin = (symbol) => {
59
77
  const symbolWithLinks = symbol;
60
78
  return symbolWithLinks.links?.syntheticOrigin;
@@ -175,6 +193,10 @@ class TypeResolver {
175
193
  });
176
194
  }
177
195
  resolve() {
196
+ const recoverableTypeReference = this.getRecoverableTypeReferenceNode();
197
+ if (recoverableTypeReference) {
198
+ return this.resolveTypeReferenceNode(recoverableTypeReference, this.current, this.context, this.parentNode);
199
+ }
178
200
  const parentJsDocTagNames = this.parentNode ? (0, jsDocUtils_1.getJSDocTagNames)(this.parentNode) : undefined;
179
201
  const primitiveType = new primitiveTransformer_1.PrimitiveTransformer().transform(this.current.defaultNumberType, this.typeNode, parentJsDocTagNames);
180
202
  if (primitiveType) {
@@ -187,6 +209,13 @@ class TypeResolver {
187
209
  (0, flowUtils_1.throwUnless)(ts.isTypeReferenceNode(this.typeNode), new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[this.typeNode.kind]}`, this.typeNode));
188
210
  return this.resolveTypeReferenceNode(this.typeNode, this.current, this.context, this.parentNode);
189
211
  }
212
+ getRecoverableTypeReferenceNode() {
213
+ if (!ts.isIdentifier(this.typeNode) && !ts.isQualifiedName(this.typeNode)) {
214
+ return undefined;
215
+ }
216
+ const parent = this.typeNode.parent;
217
+ return parent && ts.isTypeReferenceNode(parent) && parent.typeName === this.typeNode ? parent : undefined;
218
+ }
190
219
  resolveNonReferenceTypeNode() {
191
220
  return (this.resolveArrayTypeNode() ??
192
221
  this.resolveRestTypeNode() ??
@@ -505,7 +534,7 @@ class TypeResolver {
505
534
  if (!contextualType) {
506
535
  return undefined;
507
536
  }
508
- const subResult = new TypeResolver(contextualType.type, current, parentNode, context).resolve();
537
+ const subResult = new TypeResolver(contextualType.type, current, parentNode, context, contextualType.resolvedType).resolve();
509
538
  if (subResult.dataType === 'any') {
510
539
  return this.createStringAndNumberUnion();
511
540
  }
@@ -617,14 +646,15 @@ class TypeResolver {
617
646
  }
618
647
  resolveIndexedAccessLiteralType(typeNode, typeChecker, current, context, objectType, indexType) {
619
648
  const propertyName = ts.isStringLiteral(indexType.literal) || ts.isNumericLiteral(indexType.literal) ? indexType.literal.text : indexType.literal.getText();
620
- const symbol = typeChecker.getPropertyOfType(typeChecker.getTypeFromTypeNode(objectType), propertyName);
621
- (0, flowUtils_1.throwUnless)(symbol, new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeChecker.typeToString(typeChecker.getTypeFromTypeNode(objectType))}`, typeNode));
649
+ const { type: resolvedObjectType, typeNode: resolvedObjectTypeNode } = this.resolveContextualIndexedAccessObjectType(objectType, typeChecker, context);
650
+ const symbol = typeChecker.getPropertyOfType(resolvedObjectType, propertyName);
651
+ (0, flowUtils_1.throwUnless)(symbol, new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeChecker.typeToString(resolvedObjectType)}`, typeNode));
622
652
  if (this.symbolHasTypeDeclaration(symbol.valueDeclaration)) {
623
653
  return new TypeResolver(symbol.valueDeclaration.type, current, typeNode, context).resolve();
624
654
  }
625
- const declarationType = typeChecker.getTypeOfSymbolAtLocation(symbol, objectType);
655
+ const declarationType = typeChecker.getTypeOfSymbolAtLocation(symbol, resolvedObjectTypeNode);
626
656
  try {
627
- return new TypeResolver(typeChecker.typeToTypeNode(declarationType, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
657
+ return new TypeResolver(typeChecker.typeToTypeNode(declarationType, resolvedObjectTypeNode, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
628
658
  }
629
659
  catch {
630
660
  const typeNodeForError = typeChecker.typeToTypeNode(declarationType, undefined, ts.NodeBuilderFlags.NoTruncation);
@@ -632,6 +662,24 @@ class TypeResolver {
632
662
  throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeName}`, typeNode);
633
663
  }
634
664
  }
665
+ resolveContextualIndexedAccessObjectType(objectType, typeChecker, context) {
666
+ const contextualTypeNode = this.getContextualIndexedAccessObjectTypeNode(objectType, context);
667
+ const resolvedTypeNode = contextualTypeNode ?? objectType;
668
+ const contextualType = contextualTypeNode && ts.isTypeReferenceNode(objectType) && ts.isIdentifier(objectType.typeName) ? context[objectType.typeName.text] : undefined;
669
+ return {
670
+ type: contextualType?.resolvedType ?? typeChecker.getTypeFromTypeNode(resolvedTypeNode),
671
+ typeNode: resolvedTypeNode,
672
+ };
673
+ }
674
+ getContextualIndexedAccessObjectTypeNode(objectType, context) {
675
+ if (ts.isParenthesizedTypeNode(objectType)) {
676
+ return this.getContextualIndexedAccessObjectTypeNode(objectType.type, context);
677
+ }
678
+ if (!ts.isTypeReferenceNode(objectType) || !ts.isIdentifier(objectType.typeName)) {
679
+ return undefined;
680
+ }
681
+ return context[objectType.typeName.text]?.type;
682
+ }
635
683
  symbolHasTypeDeclaration(node) {
636
684
  return node !== undefined && objectHasOwn(node, 'type') && node.type !== undefined;
637
685
  }
@@ -691,13 +739,13 @@ class TypeResolver {
691
739
  const decodedSymbol = current.typeChecker.getPropertyOfType(codecType, '_A');
692
740
  if (decodedSymbol) {
693
741
  const decodedType = current.typeChecker.getTypeOfSymbolAtLocation(decodedSymbol, codecTypeArgument);
694
- const decodedNode = current.typeChecker.typeToTypeNode(decodedType, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation);
742
+ const decodedNode = this.normalizeTypeNodeFromBuilder(current.typeChecker.typeToTypeNode(decodedType, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation));
695
743
  if (decodedNode) {
696
744
  return new TypeResolver(decodedNode, current, parentNode, context, decodedType).resolve();
697
745
  }
698
746
  }
699
747
  const resolvedType = current.typeChecker.getTypeFromTypeNode(typeNode);
700
- const resolvedNode = current.typeChecker.typeToTypeNode(resolvedType, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation);
748
+ const resolvedNode = this.normalizeTypeNodeFromBuilder(current.typeChecker.typeToTypeNode(resolvedType, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation));
701
749
  if (resolvedNode && !ts.isTypeReferenceNode(resolvedNode)) {
702
750
  return new TypeResolver(resolvedNode, current, parentNode, context, resolvedType).resolve();
703
751
  }
@@ -720,14 +768,15 @@ class TypeResolver {
720
768
  return undefined;
721
769
  case 'Promise':
722
770
  if (typeArguments?.length === 1) {
723
- return new TypeResolver(typeArguments[0], current, parentNode, context).resolve();
771
+ const promisedType = this.getPromisedTypeOfReferencer(current.typeChecker);
772
+ return new TypeResolver(typeArguments[0], current, parentNode, context, promisedType).resolve();
724
773
  }
725
774
  return undefined;
726
775
  case 'String':
727
776
  return { dataType: 'string' };
728
777
  default:
729
778
  if (context[typeName]) {
730
- return new TypeResolver(context[typeName].type, current, parentNode, context).resolve();
779
+ return new TypeResolver(context[typeName].type, current, parentNode, context, context[typeName].resolvedType).resolve();
731
780
  }
732
781
  return undefined;
733
782
  }
@@ -759,46 +808,73 @@ class TypeResolver {
759
808
  if (!symbol || visited.has(symbol)) {
760
809
  return undefined;
761
810
  }
811
+ const cache = getIoTsUtilityTypeCache(typeChecker);
812
+ if (cache.has(symbol)) {
813
+ const cachedType = cache.get(symbol);
814
+ return cachedType || undefined;
815
+ }
762
816
  visited.add(symbol);
763
817
  if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
764
818
  const aliasedSymbol = typeChecker.getAliasedSymbol(symbol);
765
819
  const aliasedType = this.getIoTsUtilityTypeFromSymbol(aliasedSymbol, typeChecker, visited);
766
820
  if (aliasedType) {
821
+ cache.set(symbol, aliasedType);
767
822
  return aliasedType;
768
823
  }
769
824
  }
770
825
  const symbolName = symbol.getName();
771
826
  if ((symbolName === 'TypeOf' || symbolName === 'Branded' || symbolName === 'Brand') && this.symbolComesFromModule(symbol, typeChecker, 'io-ts')) {
827
+ cache.set(symbol, symbolName);
772
828
  return symbolName;
773
829
  }
830
+ cache.set(symbol, false);
774
831
  return undefined;
775
832
  }
776
833
  symbolComesFromModule(symbol, typeChecker, moduleName, visited = new Set()) {
777
834
  if (visited.has(symbol)) {
778
835
  return false;
779
836
  }
837
+ const moduleCache = getSymbolModuleOriginCache(typeChecker);
838
+ const cachedByModule = moduleCache.get(symbol);
839
+ const cachedResult = cachedByModule?.get(moduleName);
840
+ if (cachedResult !== undefined) {
841
+ return cachedResult;
842
+ }
780
843
  visited.add(symbol);
844
+ const comesFromModule = this.symbolDeclarationsComeFromModule(symbol, moduleName) || this.aliasedSymbolComesFromModule(symbol, typeChecker, moduleName, visited);
845
+ if (cachedByModule) {
846
+ cachedByModule.set(moduleName, comesFromModule);
847
+ }
848
+ else {
849
+ moduleCache.set(symbol, new Map([[moduleName, comesFromModule]]));
850
+ }
851
+ return comesFromModule;
852
+ }
853
+ symbolDeclarationsComeFromModule(symbol, moduleName) {
781
854
  const declarations = symbol.declarations || (symbol.valueDeclaration ? [symbol.valueDeclaration] : []);
782
- for (const declaration of declarations) {
783
- let current = declaration.parent;
784
- while (current && !ts.isImportDeclaration(current)) {
785
- current = current.parent;
786
- }
787
- if (current && ts.isImportDeclaration(current) && ts.isStringLiteral(current.moduleSpecifier) && current.moduleSpecifier.text === moduleName) {
788
- return true;
789
- }
790
- const fileName = declaration.getSourceFile().fileName.replaceAll('\\', '/');
791
- if (fileName.includes(`/node_modules/${moduleName}/`)) {
792
- return true;
793
- }
855
+ return declarations.some(declaration => this.declarationComesFromModule(declaration, moduleName));
856
+ }
857
+ declarationComesFromModule(declaration, moduleName) {
858
+ const containingImportModuleSpecifier = this.getContainingImportModuleSpecifier(declaration);
859
+ if (containingImportModuleSpecifier === moduleName) {
860
+ return true;
794
861
  }
795
- if ((symbol.flags & ts.SymbolFlags.Alias) !== 0) {
796
- const aliasedSymbol = typeChecker.getAliasedSymbol(symbol);
797
- if (aliasedSymbol !== symbol) {
798
- return this.symbolComesFromModule(aliasedSymbol, typeChecker, moduleName, visited);
799
- }
862
+ const fileName = declaration.getSourceFile().fileName.replaceAll('\\', '/');
863
+ return fileName.includes(`/node_modules/${moduleName}/`);
864
+ }
865
+ getContainingImportModuleSpecifier(node) {
866
+ let current = node.parent;
867
+ while (current && !ts.isImportDeclaration(current)) {
868
+ current = current.parent;
800
869
  }
801
- return false;
870
+ return current && ts.isStringLiteral(current.moduleSpecifier) ? current.moduleSpecifier.text : undefined;
871
+ }
872
+ aliasedSymbolComesFromModule(symbol, typeChecker, moduleName, visited) {
873
+ if ((symbol.flags & ts.SymbolFlags.Alias) === 0) {
874
+ return false;
875
+ }
876
+ const aliasedSymbol = typeChecker.getAliasedSymbol(symbol);
877
+ return aliasedSymbol !== symbol && this.symbolComesFromModule(aliasedSymbol, typeChecker, moduleName, visited);
802
878
  }
803
879
  getLiteralValue(typeNode) {
804
880
  switch (typeNode.literal.kind) {
@@ -1377,48 +1453,81 @@ class TypeResolver {
1377
1453
  return undefined;
1378
1454
  }
1379
1455
  typeArgumentsToContext(type, targetEntity) {
1380
- let newContext = {};
1381
1456
  // Inline object types don't contribute generic declarations, so they map to an empty context.
1382
- if (!this.current.typeChecker) {
1383
- return newContext;
1384
- }
1385
- if (!ts.isIdentifier(targetEntity) && !ts.isQualifiedName(targetEntity)) {
1386
- return newContext;
1387
- }
1388
- const declarations = this.getModelTypeDeclarations(targetEntity);
1389
- const firstDeclaration = declarations[0];
1390
- const typeParameters = firstDeclaration?.typeParameters;
1391
- if (typeParameters) {
1392
- for (let index = 0; index < typeParameters.length; index++) {
1393
- const typeParameter = typeParameters[index];
1394
- const typeArg = type.typeArguments?.[index];
1395
- let resolvedType;
1396
- let name;
1397
- // Argument may be a forward reference from context
1398
- if (typeArg && ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName) && this.context[typeArg.typeName.text]) {
1399
- resolvedType = this.context[typeArg.typeName.text].type;
1400
- name = this.context[typeArg.typeName.text].name;
1401
- }
1402
- else if (typeArg) {
1403
- resolvedType = typeArg;
1404
- }
1405
- else if (typeParameter.default) {
1406
- resolvedType = typeParameter.default;
1407
- }
1408
- else {
1409
- throw new exceptions_1.GenerateMetadataError(`Could not find a value for type parameter ${typeParameter.name.text}`, type);
1410
- }
1411
- newContext = {
1412
- ...newContext,
1413
- [typeParameter.name.text]: {
1414
- type: resolvedType,
1415
- name: name || this.calcTypeName(resolvedType),
1416
- },
1417
- };
1418
- }
1457
+ const typeParameters = this.getTypeParametersForTargetEntity(targetEntity);
1458
+ if (!typeParameters?.length) {
1459
+ return {};
1460
+ }
1461
+ let newContext = {};
1462
+ for (let index = 0; index < typeParameters.length; index += 1) {
1463
+ const typeParameter = typeParameters[index];
1464
+ const resolvedType = this.resolveContextualTypeArgument(type, typeParameter, index);
1465
+ newContext = {
1466
+ ...newContext,
1467
+ [typeParameter.name.text]: {
1468
+ type: resolvedType.type,
1469
+ name: resolvedType.name || this.calcTypeName(resolvedType.type),
1470
+ resolvedType: resolvedType.resolvedType,
1471
+ },
1472
+ };
1419
1473
  }
1420
1474
  return newContext;
1421
1475
  }
1476
+ getTypeParametersForTargetEntity(targetEntity) {
1477
+ if (!this.current.typeChecker || (!ts.isIdentifier(targetEntity) && !ts.isQualifiedName(targetEntity))) {
1478
+ return undefined;
1479
+ }
1480
+ const firstDeclaration = this.getModelTypeDeclarations(targetEntity)[0];
1481
+ return firstDeclaration?.typeParameters;
1482
+ }
1483
+ resolveContextualTypeArgument(type, typeParameter, index) {
1484
+ const typeArgument = type.typeArguments?.[index];
1485
+ const contextualType = this.getForwardReferencedContextType(typeArgument);
1486
+ if (contextualType) {
1487
+ return contextualType;
1488
+ }
1489
+ const resolvedType = typeArgument ?? typeParameter.default;
1490
+ if (!resolvedType) {
1491
+ throw new exceptions_1.GenerateMetadataError(`Could not find a value for type parameter ${typeParameter.name.text}`, type);
1492
+ }
1493
+ return {
1494
+ type: resolvedType,
1495
+ name: undefined,
1496
+ resolvedType: this.getResolvedTypeForContextTypeArgument(resolvedType, index),
1497
+ };
1498
+ }
1499
+ getForwardReferencedContextType(typeArgument) {
1500
+ if (!typeArgument || !ts.isTypeReferenceNode(typeArgument) || !ts.isIdentifier(typeArgument.typeName)) {
1501
+ return undefined;
1502
+ }
1503
+ return this.context[typeArgument.typeName.text];
1504
+ }
1505
+ getResolvedTypeForContextTypeArgument(typeNode, index) {
1506
+ if (typeNode.pos === -1) {
1507
+ return this.getReferencerTypeArgument(index);
1508
+ }
1509
+ return this.current.typeChecker.getTypeFromTypeNode(typeNode);
1510
+ }
1511
+ getReferencerTypeArgument(index) {
1512
+ const referencer = this.referencer;
1513
+ return referencer?.aliasTypeArguments?.[index] ?? referencer?.typeArguments?.[index];
1514
+ }
1515
+ getPromisedTypeOfReferencer(typeChecker) {
1516
+ if (!this.referencer) {
1517
+ return undefined;
1518
+ }
1519
+ const extendedTypeChecker = typeChecker;
1520
+ return extendedTypeChecker.getPromisedTypeOfPromise?.(this.referencer);
1521
+ }
1522
+ normalizeTypeNodeFromBuilder(node) {
1523
+ if (!node) {
1524
+ return undefined;
1525
+ }
1526
+ if (ts.isIdentifier(node) || ts.isQualifiedName(node)) {
1527
+ return ts.factory.createTypeReferenceNode(node, undefined);
1528
+ }
1529
+ return node;
1530
+ }
1422
1531
  getReferenceAliasProperties(referenceType) {
1423
1532
  let type = referenceType;
1424
1533
  while (type.dataType === 'refAlias') {