@tsoa-next/cli 8.0.0 → 8.0.1-dev.44.a051324b

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.
Files changed (77) hide show
  1. package/dist/api.d.ts +2 -1
  2. package/dist/api.js +172 -144
  3. package/dist/api.js.map +1 -1
  4. package/dist/cli.d.ts +1 -2
  5. package/dist/cli.js +2 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/metadataGeneration/controllerGenerator.js +10 -13
  8. package/dist/metadataGeneration/controllerGenerator.js.map +1 -1
  9. package/dist/metadataGeneration/exceptions.d.ts +3 -3
  10. package/dist/metadataGeneration/exceptions.js +5 -12
  11. package/dist/metadataGeneration/exceptions.js.map +1 -1
  12. package/dist/metadataGeneration/extension.js +2 -2
  13. package/dist/metadataGeneration/extension.js.map +1 -1
  14. package/dist/metadataGeneration/initializer-value.js +90 -80
  15. package/dist/metadataGeneration/initializer-value.js.map +1 -1
  16. package/dist/metadataGeneration/metadataGenerator.js +20 -22
  17. package/dist/metadataGeneration/metadataGenerator.js.map +1 -1
  18. package/dist/metadataGeneration/methodGenerator.d.ts +1 -0
  19. package/dist/metadataGeneration/methodGenerator.js +39 -27
  20. package/dist/metadataGeneration/methodGenerator.js.map +1 -1
  21. package/dist/metadataGeneration/parameterGenerator.d.ts +5 -0
  22. package/dist/metadataGeneration/parameterGenerator.js +79 -90
  23. package/dist/metadataGeneration/parameterGenerator.js.map +1 -1
  24. package/dist/metadataGeneration/transformer/dateTransformer.js +3 -5
  25. package/dist/metadataGeneration/transformer/dateTransformer.js.map +1 -1
  26. package/dist/metadataGeneration/transformer/enumTransformer.d.ts +2 -0
  27. package/dist/metadataGeneration/transformer/enumTransformer.js +34 -12
  28. package/dist/metadataGeneration/transformer/enumTransformer.js.map +1 -1
  29. package/dist/metadataGeneration/transformer/primitiveTransformer.js +2 -4
  30. package/dist/metadataGeneration/transformer/primitiveTransformer.js.map +1 -1
  31. package/dist/metadataGeneration/transformer/propertyTransformer.d.ts +1 -0
  32. package/dist/metadataGeneration/transformer/propertyTransformer.js +14 -19
  33. package/dist/metadataGeneration/transformer/propertyTransformer.js.map +1 -1
  34. package/dist/metadataGeneration/transformer/referenceTransformer.d.ts +2 -0
  35. package/dist/metadataGeneration/transformer/referenceTransformer.js +35 -18
  36. package/dist/metadataGeneration/transformer/referenceTransformer.js.map +1 -1
  37. package/dist/metadataGeneration/transformer/transformer.js +3 -4
  38. package/dist/metadataGeneration/transformer/transformer.js.map +1 -1
  39. package/dist/metadataGeneration/typeResolver.d.ts +79 -0
  40. package/dist/metadataGeneration/typeResolver.js +906 -735
  41. package/dist/metadataGeneration/typeResolver.js.map +1 -1
  42. package/dist/module/generate-routes.d.ts +1 -2
  43. package/dist/module/generate-routes.js +18 -17
  44. package/dist/module/generate-routes.js.map +1 -1
  45. package/dist/module/generate-spec.js +1 -3
  46. package/dist/module/generate-spec.js.map +1 -1
  47. package/dist/routeGeneration/defaultRouteGenerator.js +2 -2
  48. package/dist/routeGeneration/defaultRouteGenerator.js.map +1 -1
  49. package/dist/routeGeneration/routeGenerator.d.ts +2 -0
  50. package/dist/routeGeneration/routeGenerator.js +20 -30
  51. package/dist/routeGeneration/routeGenerator.js.map +1 -1
  52. package/dist/swagger/specGenerator.js +1 -1
  53. package/dist/swagger/specGenerator.js.map +1 -1
  54. package/dist/swagger/specGenerator2.d.ts +12 -0
  55. package/dist/swagger/specGenerator2.js +200 -170
  56. package/dist/swagger/specGenerator2.js.map +1 -1
  57. package/dist/swagger/specGenerator3.d.ts +17 -87
  58. package/dist/swagger/specGenerator3.js +178 -172
  59. package/dist/swagger/specGenerator3.js.map +1 -1
  60. package/dist/utils/decoratorUtils.js +25 -32
  61. package/dist/utils/decoratorUtils.js.map +1 -1
  62. package/dist/utils/fs.d.ts +1 -1
  63. package/dist/utils/fs.js +5 -5
  64. package/dist/utils/fs.js.map +1 -1
  65. package/dist/utils/headerTypeHelpers.js +2 -5
  66. package/dist/utils/headerTypeHelpers.js.map +1 -1
  67. package/dist/utils/importClassesFromDirectories.js +2 -2
  68. package/dist/utils/importClassesFromDirectories.js.map +1 -1
  69. package/dist/utils/jsDocUtils.js +4 -11
  70. package/dist/utils/jsDocUtils.js.map +1 -1
  71. package/dist/utils/pathUtils.js +2 -2
  72. package/dist/utils/pathUtils.js.map +1 -1
  73. package/dist/utils/swaggerUtils.js +7 -1
  74. package/dist/utils/swaggerUtils.js.map +1 -1
  75. package/dist/utils/validatorUtils.js +60 -97
  76. package/dist/utils/validatorUtils.js.map +1 -1
  77. package/package.json +2 -2
@@ -51,7 +51,10 @@ const propertyTransformer_1 = require("./transformer/propertyTransformer");
51
51
  const referenceTransformer_1 = require("./transformer/referenceTransformer");
52
52
  const localReferenceTypeCache = {};
53
53
  const inProgressTypes = {};
54
+ const escapedDoubleQuote = String.raw `\"`;
55
+ const backslash = '\\';
54
56
  const hasInitializer = (declaration) => 'initializer' in declaration && declaration.initializer !== undefined;
57
+ const objectHasOwn = Object.hasOwn;
55
58
  const getSyntheticOrigin = (symbol) => {
56
59
  const symbolWithLinks = symbol;
57
60
  return symbolWithLinks.links?.syntheticOrigin;
@@ -60,16 +63,46 @@ const isAsciiLetter = (char) => {
60
63
  if (!char) {
61
64
  return false;
62
65
  }
63
- const code = char.charCodeAt(0);
66
+ const code = char.codePointAt(0) ?? -1;
64
67
  return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
65
68
  };
66
69
  const isRefTypeTokenCharacter = (char) => {
67
70
  if (!char) {
68
71
  return false;
69
72
  }
70
- const code = char.charCodeAt(0);
73
+ const code = char.codePointAt(0) ?? -1;
71
74
  return isAsciiLetter(char) || (code >= 48 && code <= 57) || char === '_';
72
75
  };
76
+ const readRefTypeToken = (value, startIndex) => {
77
+ let nextIndex = startIndex;
78
+ while (nextIndex < value.length && isRefTypeTokenCharacter(value[nextIndex])) {
79
+ nextIndex += 1;
80
+ }
81
+ if (value[nextIndex] === '?') {
82
+ nextIndex += 1;
83
+ }
84
+ return {
85
+ token: value.slice(startIndex, nextIndex),
86
+ nextIndex,
87
+ };
88
+ };
89
+ const readRefTypeLiteralTypeSegment = (value, colonIndex) => {
90
+ if (value[colonIndex] !== ':') {
91
+ return undefined;
92
+ }
93
+ const typeStart = colonIndex + 1;
94
+ let typeEnd = typeStart;
95
+ while (typeEnd < value.length && isAsciiLetter(value[typeEnd])) {
96
+ typeEnd += 1;
97
+ }
98
+ if (typeEnd === typeStart) {
99
+ return undefined;
100
+ }
101
+ return {
102
+ replacement: `-${value.slice(typeStart, typeEnd)}`,
103
+ nextIndex: typeEnd,
104
+ };
105
+ };
73
106
  const replaceTypeLiteralPropertySeparators = (value) => {
74
107
  let formatted = '';
75
108
  let index = 0;
@@ -79,29 +112,15 @@ const replaceTypeLiteralPropertySeparators = (value) => {
79
112
  index += 1;
80
113
  continue;
81
114
  }
82
- const tokenStart = index;
83
- while (index < value.length && isRefTypeTokenCharacter(value[index])) {
84
- index += 1;
85
- }
86
- if (value[index] === '?') {
87
- index += 1;
88
- }
89
- const token = value.slice(tokenStart, index);
90
- if (value[index] === ':') {
91
- const typeStart = index + 1;
92
- let typeEnd = typeStart;
93
- while (typeEnd < value.length && isAsciiLetter(value[typeEnd])) {
94
- typeEnd += 1;
95
- }
96
- if (typeEnd > typeStart) {
97
- formatted += token;
98
- formatted += '-';
99
- formatted += value.slice(typeStart, typeEnd);
100
- index = typeEnd;
101
- continue;
102
- }
103
- }
115
+ const { nextIndex, token } = readRefTypeToken(value, index);
104
116
  formatted += token;
117
+ const literalTypeSegment = readRefTypeLiteralTypeSegment(value, nextIndex);
118
+ if (literalTypeSegment) {
119
+ formatted += literalTypeSegment.replacement;
120
+ index = literalTypeSegment.nextIndex;
121
+ continue;
122
+ }
123
+ index = nextIndex;
105
124
  }
106
125
  return formatted;
107
126
  };
@@ -114,7 +133,7 @@ const replaceIndexedAccessSegments = (value) => {
114
133
  index += 1;
115
134
  continue;
116
135
  }
117
- const previousCharacter = formatted[formatted.length - 1];
136
+ const previousCharacter = formatted.at(-1);
118
137
  if (!(isAsciiLetter(previousCharacter) || previousCharacter === '}' || previousCharacter === ']' || previousCharacter === ')')) {
119
138
  formatted += value[index];
120
139
  index += 1;
@@ -156,356 +175,300 @@ class TypeResolver {
156
175
  });
157
176
  }
158
177
  resolve() {
159
- const partentJsDocTagNames = this.parentNode ? (0, jsDocUtils_1.getJSDocTagNames)(this.parentNode) : undefined;
160
- const primitiveType = new primitiveTransformer_1.PrimitiveTransformer().transform(this.current.defaultNumberType, this.typeNode, partentJsDocTagNames);
178
+ const parentJsDocTagNames = this.parentNode ? (0, jsDocUtils_1.getJSDocTagNames)(this.parentNode) : undefined;
179
+ const primitiveType = new primitiveTransformer_1.PrimitiveTransformer().transform(this.current.defaultNumberType, this.typeNode, parentJsDocTagNames);
161
180
  if (primitiveType) {
162
181
  return primitiveType;
163
182
  }
164
- if (ts.isArrayTypeNode(this.typeNode)) {
165
- const arrayMetaType = {
166
- dataType: 'array',
167
- elementType: new TypeResolver(this.typeNode.elementType, this.current, this.parentNode, this.context).resolve(),
168
- };
169
- return arrayMetaType;
183
+ const nonReferenceType = this.resolveNonReferenceTypeNode();
184
+ if (nonReferenceType) {
185
+ return nonReferenceType;
170
186
  }
171
- if (ts.isRestTypeNode(this.typeNode)) {
172
- return new TypeResolver(this.typeNode.type, this.current, this.parentNode, this.context).resolve();
187
+ (0, flowUtils_1.throwUnless)(ts.isTypeReferenceNode(this.typeNode), new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[this.typeNode.kind]}`, this.typeNode));
188
+ return this.resolveTypeReferenceNode(this.typeNode, this.current, this.context, this.parentNode);
189
+ }
190
+ resolveNonReferenceTypeNode() {
191
+ return (this.resolveArrayTypeNode() ??
192
+ this.resolveRestTypeNode() ??
193
+ this.resolveUnionTypeNode() ??
194
+ this.resolveIntersectionTypeNode() ??
195
+ this.resolveTupleTypeNode() ??
196
+ this.resolveAnyOrUnknownTypeNode() ??
197
+ this.resolveLiteralTypeNode() ??
198
+ this.resolveTypeLiteralNode() ??
199
+ this.resolveObjectKeywordTypeNode() ??
200
+ this.resolveMappedTypeNode() ??
201
+ this.resolveConditionalTypeNode() ??
202
+ this.resolveTypeOperatorTypeNode() ??
203
+ this.resolveIndexedAccessTypeNodeWrapper() ??
204
+ this.resolveTemplateLiteralTypeNode() ??
205
+ this.resolveParenthesizedTypeNode());
206
+ }
207
+ resolveArrayTypeNode() {
208
+ if (!ts.isArrayTypeNode(this.typeNode)) {
209
+ return undefined;
173
210
  }
174
- if (ts.isUnionTypeNode(this.typeNode)) {
175
- const types = this.typeNode.types.map(type => {
176
- return new TypeResolver(type, this.current, this.parentNode, this.context).resolve();
177
- });
178
- const unionMetaType = {
179
- dataType: 'union',
180
- types,
181
- };
182
- return unionMetaType;
211
+ return {
212
+ dataType: 'array',
213
+ elementType: new TypeResolver(this.typeNode.elementType, this.current, this.parentNode, this.context).resolve(),
214
+ };
215
+ }
216
+ resolveRestTypeNode() {
217
+ if (!ts.isRestTypeNode(this.typeNode)) {
218
+ return undefined;
183
219
  }
184
- if (ts.isIntersectionTypeNode(this.typeNode)) {
185
- const types = this.typeNode.types
186
- .filter(type => !this.isIoTsBrandMarker(type, this.current.typeChecker))
187
- .map(type => {
188
- return new TypeResolver(type, this.current, this.parentNode, this.context).resolve();
189
- });
190
- const intersectionMetaType = {
191
- dataType: 'intersection',
192
- types,
193
- };
194
- return intersectionMetaType;
195
- }
196
- if (ts.isTupleTypeNode(this.typeNode)) {
197
- const elementTypes = [];
198
- let restType;
199
- for (const element of this.typeNode.elements) {
200
- if (ts.isRestTypeNode(element)) {
201
- const resolvedRest = new TypeResolver(element.type, this.current, element, this.context).resolve();
202
- if (resolvedRest.dataType === 'array') {
203
- restType = resolvedRest.elementType;
204
- }
205
- else {
206
- restType = resolvedRest;
207
- }
208
- }
209
- else {
210
- const typeNode = ts.isNamedTupleMember(element) ? element.type : element;
211
- const type = new TypeResolver(typeNode, this.current, element, this.context).resolve();
212
- elementTypes.push(type);
213
- }
220
+ return new TypeResolver(this.typeNode.type, this.current, this.parentNode, this.context).resolve();
221
+ }
222
+ resolveUnionTypeNode() {
223
+ if (!ts.isUnionTypeNode(this.typeNode)) {
224
+ return undefined;
225
+ }
226
+ return {
227
+ dataType: 'union',
228
+ types: this.typeNode.types.map(type => new TypeResolver(type, this.current, this.parentNode, this.context).resolve()),
229
+ };
230
+ }
231
+ resolveIntersectionTypeNode() {
232
+ if (!ts.isIntersectionTypeNode(this.typeNode)) {
233
+ return undefined;
234
+ }
235
+ return {
236
+ dataType: 'intersection',
237
+ types: this.typeNode.types.filter(type => !this.isIoTsBrandMarker(type, this.current.typeChecker)).map(type => new TypeResolver(type, this.current, this.parentNode, this.context).resolve()),
238
+ };
239
+ }
240
+ resolveTupleTypeNode() {
241
+ if (!ts.isTupleTypeNode(this.typeNode)) {
242
+ return undefined;
243
+ }
244
+ const elementTypes = [];
245
+ let restType;
246
+ for (const element of this.typeNode.elements) {
247
+ if (ts.isRestTypeNode(element)) {
248
+ restType = this.resolveTupleRestTypeNode(element);
249
+ continue;
214
250
  }
251
+ const typeNode = ts.isNamedTupleMember(element) ? element.type : element;
252
+ elementTypes.push(new TypeResolver(typeNode, this.current, element, this.context).resolve());
253
+ }
254
+ return {
255
+ dataType: 'tuple',
256
+ types: elementTypes,
257
+ ...(restType ? { restType } : {}),
258
+ };
259
+ }
260
+ resolveTupleRestTypeNode(element) {
261
+ const resolvedRest = new TypeResolver(element.type, this.current, element, this.context).resolve();
262
+ return resolvedRest.dataType === 'array' ? resolvedRest.elementType : resolvedRest;
263
+ }
264
+ resolveAnyOrUnknownTypeNode() {
265
+ if (this.typeNode.kind !== ts.SyntaxKind.AnyKeyword && this.typeNode.kind !== ts.SyntaxKind.UnknownKeyword) {
266
+ return undefined;
267
+ }
268
+ return { dataType: 'any' };
269
+ }
270
+ resolveLiteralTypeNode() {
271
+ if (!ts.isLiteralTypeNode(this.typeNode)) {
272
+ return undefined;
273
+ }
274
+ return {
275
+ dataType: 'enum',
276
+ enums: [this.getLiteralValue(this.typeNode)],
277
+ };
278
+ }
279
+ resolveTypeLiteralNode() {
280
+ if (!ts.isTypeLiteralNode(this.typeNode)) {
281
+ return undefined;
282
+ }
283
+ const properties = this.typeNode.members.filter(ts.isPropertySignature).reduce((result, propertySignature) => [this.resolveTypeLiteralProperty(propertySignature), ...result], []);
284
+ return {
285
+ additionalProperties: this.resolveTypeLiteralAdditionalProperties(this.typeNode),
286
+ dataType: 'nestedObjectLiteral',
287
+ properties,
288
+ };
289
+ }
290
+ resolveTypeLiteralProperty(propertySignature) {
291
+ return {
292
+ example: this.getNodeExample(propertySignature),
293
+ default: TypeResolver.getDefault(propertySignature),
294
+ description: this.getNodeDescription(propertySignature),
295
+ format: this.getNodeFormat(propertySignature),
296
+ name: this.getPropertyName(propertySignature),
297
+ required: !propertySignature.questionToken,
298
+ type: new TypeResolver(propertySignature.type, this.current, propertySignature, this.context).resolve(),
299
+ validators: (0, validatorUtils_1.getPropertyValidators)(propertySignature) || {},
300
+ deprecated: (0, jsDocUtils_1.isExistJSDocTag)(propertySignature, tag => tag.tagName.text === 'deprecated'),
301
+ title: this.getNodeTitle(propertySignature),
302
+ extensions: this.getNodeExtension(propertySignature),
303
+ };
304
+ }
305
+ resolveTypeLiteralAdditionalProperties(typeLiteralNode) {
306
+ const indexMember = typeLiteralNode.members.find(member => ts.isIndexSignatureDeclaration(member));
307
+ if (!indexMember) {
308
+ return undefined;
309
+ }
310
+ const indexType = new TypeResolver(indexMember.parameters[0].type, this.current, this.parentNode, this.context).resolve();
311
+ (0, flowUtils_1.throwUnless)(indexType.dataType === 'string', new exceptions_1.GenerateMetadataError(`Only string indexers are supported.`, this.typeNode));
312
+ return new TypeResolver(indexMember.type, this.current, this.parentNode, this.context).resolve();
313
+ }
314
+ resolveObjectKeywordTypeNode() {
315
+ if (this.typeNode.kind !== ts.SyntaxKind.ObjectKeyword) {
316
+ return undefined;
317
+ }
318
+ return { dataType: 'object' };
319
+ }
320
+ resolveMappedTypeNode() {
321
+ if (!ts.isMappedTypeNode(this.typeNode)) {
322
+ return undefined;
323
+ }
324
+ return this.resolveMappedType(this.getReferencer(), this.typeNode);
325
+ }
326
+ getOriginalMappedDeclaration(prop) {
327
+ const declaration = prop.declarations?.[0];
328
+ if (declaration) {
329
+ return declaration;
330
+ }
331
+ const syntheticOrigin = getSyntheticOrigin(prop);
332
+ if (syntheticOrigin?.name === prop.name) {
333
+ // Otherwise loses jsDoc like in intellisense.
334
+ return syntheticOrigin.declarations?.[0];
335
+ }
336
+ return undefined;
337
+ }
338
+ isIgnoredMappedProperty(prop) {
339
+ const declaration = this.getOriginalMappedDeclaration(prop);
340
+ if (!declaration) {
341
+ return false;
342
+ }
343
+ const ignoredTargets = !ts.isPropertyDeclaration(declaration) && !ts.isPropertySignature(declaration) && !ts.isParameter(declaration);
344
+ return (0, jsDocUtils_1.getJSDocTagNames)(declaration).includes('ignore') || ignoredTargets;
345
+ }
346
+ resolveMappedType(type, mappedTypeNode) {
347
+ if (this.hasFlag(type, ts.TypeFlags.Union)) {
215
348
  return {
216
- dataType: 'tuple',
217
- types: elementTypes,
218
- ...(restType ? { restType } : {}),
349
+ dataType: 'union',
350
+ types: type.types.map(unionType => this.resolveMappedType(unionType, mappedTypeNode)),
219
351
  };
220
352
  }
221
- if (this.typeNode.kind === ts.SyntaxKind.AnyKeyword || this.typeNode.kind === ts.SyntaxKind.UnknownKeyword) {
222
- const literallyAny = {
223
- dataType: 'any',
224
- };
225
- return literallyAny;
353
+ if (this.hasFlag(type, ts.TypeFlags.Undefined)) {
354
+ return { dataType: 'undefined' };
226
355
  }
227
- if (ts.isLiteralTypeNode(this.typeNode)) {
228
- const enumType = {
356
+ if (this.hasFlag(type, ts.TypeFlags.Null)) {
357
+ return {
229
358
  dataType: 'enum',
230
- enums: [this.getLiteralValue(this.typeNode)],
231
- };
232
- return enumType;
233
- }
234
- if (ts.isTypeLiteralNode(this.typeNode)) {
235
- const properties = this.typeNode.members.filter(ts.isPropertySignature).reduce((res, propertySignature) => {
236
- const type = new TypeResolver(propertySignature.type, this.current, propertySignature, this.context).resolve();
237
- const def = TypeResolver.getDefault(propertySignature);
238
- const property = {
239
- example: this.getNodeExample(propertySignature),
240
- default: def,
241
- description: this.getNodeDescription(propertySignature),
242
- format: this.getNodeFormat(propertySignature),
243
- name: this.getPropertyName(propertySignature),
244
- required: !propertySignature.questionToken,
245
- type,
246
- validators: (0, validatorUtils_1.getPropertyValidators)(propertySignature) || {},
247
- deprecated: (0, jsDocUtils_1.isExistJSDocTag)(propertySignature, tag => tag.tagName.text === 'deprecated'),
248
- title: this.getNodeTitle(propertySignature),
249
- extensions: this.getNodeExtension(propertySignature),
250
- };
251
- return [property, ...res];
252
- }, []);
253
- const indexMember = this.typeNode.members.find(member => ts.isIndexSignatureDeclaration(member));
254
- let additionalType;
255
- if (indexMember) {
256
- /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
257
- const indexSignatureDeclaration = indexMember;
258
- const indexType = new TypeResolver(indexSignatureDeclaration.parameters[0].type, this.current, this.parentNode, this.context).resolve();
259
- (0, flowUtils_1.throwUnless)(indexType.dataType === 'string', new exceptions_1.GenerateMetadataError(`Only string indexers are supported.`, this.typeNode));
260
- additionalType = new TypeResolver(indexSignatureDeclaration.type, this.current, this.parentNode, this.context).resolve();
261
- }
262
- const objLiteral = {
263
- additionalProperties: indexMember && additionalType,
264
- dataType: 'nestedObjectLiteral',
265
- properties,
359
+ enums: [null],
266
360
  };
267
- return objLiteral;
268
361
  }
269
- if (this.typeNode.kind === ts.SyntaxKind.ObjectKeyword) {
270
- return { dataType: 'object' };
362
+ if (this.hasFlag(type, ts.TypeFlags.Object)) {
363
+ return this.resolveMappedObjectType(type, mappedTypeNode);
271
364
  }
272
- if (ts.isMappedTypeNode(this.typeNode)) {
273
- const mappedTypeNode = this.typeNode;
274
- const getOneOrigDeclaration = (prop) => {
275
- const declaration = prop.declarations?.[0];
276
- if (declaration) {
277
- return declaration;
278
- }
279
- const syntheticOrigin = getSyntheticOrigin(prop);
280
- if (syntheticOrigin && syntheticOrigin.name === prop.name) {
281
- //Otherwise losts jsDoc like in intellisense
282
- return syntheticOrigin.declarations?.[0];
283
- }
284
- return undefined;
285
- };
286
- const isIgnored = (prop) => {
287
- const declaration = getOneOrigDeclaration(prop);
288
- return (declaration !== undefined &&
289
- ((0, jsDocUtils_1.getJSDocTagNames)(declaration).some(tag => tag === 'ignore') || (!ts.isPropertyDeclaration(declaration) && !ts.isPropertySignature(declaration) && !ts.isParameter(declaration))));
290
- };
291
- const calcMappedType = (type) => {
292
- if (this.hasFlag(type, ts.TypeFlags.Union)) {
293
- //Intersections are not interesting somehow...
294
- const types = type.types;
295
- const resolvedTypes = types.map(calcMappedType);
296
- return {
297
- dataType: 'union',
298
- types: resolvedTypes,
299
- };
300
- }
301
- else if (this.hasFlag(type, ts.TypeFlags.Undefined)) {
302
- return {
303
- dataType: 'undefined',
304
- };
305
- }
306
- else if (this.hasFlag(type, ts.TypeFlags.Null)) {
307
- return {
308
- dataType: 'enum',
309
- enums: [null],
310
- };
311
- }
312
- else if (this.hasFlag(type, ts.TypeFlags.Object)) {
313
- const typeProperties = type.getProperties();
314
- const properties = typeProperties
315
- // Ignore methods, getter, setter and @ignored props
316
- .filter(property => isIgnored(property) === false)
317
- // Transform to property
318
- .map(property => {
319
- const propertyType = this.current.typeChecker.getTypeOfSymbolAtLocation(property, this.typeNode);
320
- const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation);
321
- const parent = getOneOrigDeclaration(property); //If there are more declarations, we need to get one of them, from where we want to recognize jsDoc
322
- const type = new TypeResolver(typeNode, this.current, parent, this.context, propertyType).resolve();
323
- const required = !this.hasFlag(property, ts.SymbolFlags.Optional);
324
- const comments = property.getDocumentationComment(this.current.typeChecker);
325
- const description = comments.length ? ts.displayPartsToString(comments) : undefined;
326
- const initializer = parent && hasInitializer(parent) ? parent.initializer : undefined;
327
- const def = initializer ? (0, initializer_value_1.getInitializerValue)(initializer, this.current.typeChecker) : parent ? TypeResolver.getDefault(parent) : undefined;
328
- // Push property
329
- return {
330
- name: property.getName(),
331
- required,
332
- deprecated: parent
333
- ? (0, jsDocUtils_1.isExistJSDocTag)(parent, tag => tag.tagName.text === 'deprecated') || (0, decoratorUtils_1.isDecorator)(parent, (_identifier, canonicalName) => canonicalName === 'Deprecated', this.current.typeChecker)
334
- : false,
335
- type,
336
- default: def,
337
- // validators are disjunct via types, so it is now OK.
338
- // if a type not changes while mapping, we need validators
339
- // if a type changes, then the validators will be not relevant
340
- validators: (parent ? (0, validatorUtils_1.getPropertyValidators)(parent) : {}) || {},
341
- description,
342
- format: parent ? this.getNodeFormat(parent) : undefined,
343
- example: parent ? this.getNodeExample(parent) : undefined,
344
- extensions: parent ? this.getNodeExtension(parent) : undefined,
345
- };
346
- });
347
- const objectLiteral = {
348
- dataType: 'nestedObjectLiteral',
349
- properties,
350
- };
351
- const indexInfos = this.current.typeChecker.getIndexInfosOfType(type);
352
- const indexTypes = indexInfos.flatMap(indexInfo => {
353
- const typeNode = this.current.typeChecker.typeToTypeNode(indexInfo.type, undefined, ts.NodeBuilderFlags.NoTruncation);
354
- if (typeNode.kind === ts.SyntaxKind.NeverKeyword) {
355
- // { [k: string]: never; }
356
- return [];
357
- }
358
- const type = new TypeResolver(typeNode, this.current, mappedTypeNode, this.context, indexInfo.type).resolve();
359
- return [type];
360
- });
361
- if (indexTypes.length) {
362
- if (indexTypes.length === 1) {
363
- objectLiteral.additionalProperties = indexTypes[0];
364
- }
365
- else {
366
- // { [k: string]: string; } & { [k: number]: number; }
367
- // A | B is sometimes A type or B type, sometimes optionally accepts both A & B members.
368
- // Most people & TSOA thinks that A | B can be only A or only B.
369
- // So we can accept this merge
370
- //Every additional property key assumed as string
371
- objectLiteral.additionalProperties = {
372
- dataType: 'union',
373
- types: indexTypes,
374
- };
375
- }
376
- }
377
- return objectLiteral;
378
- }
379
- // Known issues & easy to implement: Partial<string>, Partial<never>, ... But I think a programmer not writes types like this
380
- throw new exceptions_1.GenerateMetadataError(`Unhandled mapped type has found, flags: ${type.flags}`, this.typeNode);
381
- };
382
- const referencer = this.getReferencer();
383
- const result = calcMappedType(referencer);
384
- return result;
385
- }
386
- if (ts.isConditionalTypeNode(this.typeNode)) {
387
- const referencer = this.getReferencer();
388
- const resolvedNode = this.current.typeChecker.typeToTypeNode(referencer, undefined, ts.NodeBuilderFlags.NoTruncation);
389
- return new TypeResolver(resolvedNode, this.current, this.typeNode, this.context, referencer).resolve();
390
- }
391
- // keyof & readonly arrays
392
- if (ts.isTypeOperatorNode(this.typeNode)) {
393
- return this.resolveTypeOperatorNode(this.typeNode, this.current.typeChecker, this.current, this.context, this.parentNode, this.referencer);
394
- }
395
- // Indexed type
396
- if (ts.isIndexedAccessTypeNode(this.typeNode)) {
397
- return this.resolveIndexedAccessTypeNode(this.typeNode, this.current.typeChecker, this.current, this.context);
398
- }
399
- if (ts.isTemplateLiteralTypeNode(this.typeNode)) {
400
- const type = this.getReferencer();
401
- (0, flowUtils_1.throwUnless)(type.isUnion() && type.types.every((unionElementType) => unionElementType.isStringLiteral()), new exceptions_1.GenerateMetadataError(`Could not the type of ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode), this.typeNode)}`, this.typeNode));
402
- // `a${'c' | 'd'}b`
403
- const stringLiteralEnum = {
404
- dataType: 'enum',
405
- enums: type.types.map((stringLiteralType) => stringLiteralType.value),
365
+ // Known issues & easy to implement: Partial<string>, Partial<never>, ...
366
+ throw new exceptions_1.GenerateMetadataError(`Unhandled mapped type has found, flags: ${type.flags}`, this.typeNode);
367
+ }
368
+ resolveMappedObjectType(type, mappedTypeNode) {
369
+ const properties = type
370
+ .getProperties()
371
+ .filter(property => !this.isIgnoredMappedProperty(property))
372
+ .map(property => this.resolveMappedProperty(property));
373
+ const objectLiteral = {
374
+ dataType: 'nestedObjectLiteral',
375
+ properties,
376
+ };
377
+ const indexTypes = this.resolveMappedIndexTypes(type, mappedTypeNode);
378
+ if (indexTypes.length === 1) {
379
+ objectLiteral.additionalProperties = indexTypes[0];
380
+ }
381
+ else if (indexTypes.length > 1) {
382
+ objectLiteral.additionalProperties = {
383
+ dataType: 'union',
384
+ types: indexTypes,
406
385
  };
407
- return stringLiteralEnum;
408
386
  }
409
- if (ts.isParenthesizedTypeNode(this.typeNode)) {
410
- return new TypeResolver(this.typeNode.type, this.current, this.typeNode, this.context, this.referencer).resolve();
387
+ return objectLiteral;
388
+ }
389
+ resolveMappedProperty(property) {
390
+ const propertyType = this.current.typeChecker.getTypeOfSymbolAtLocation(property, this.typeNode);
391
+ const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation);
392
+ const parent = this.getOriginalMappedDeclaration(property);
393
+ const comments = property.getDocumentationComment(this.current.typeChecker);
394
+ return {
395
+ name: property.getName(),
396
+ required: !this.hasFlag(property, ts.SymbolFlags.Optional),
397
+ deprecated: this.isDeprecatedMappedProperty(parent),
398
+ type: new TypeResolver(typeNode, this.current, parent, this.context, propertyType).resolve(),
399
+ default: this.getMappedPropertyDefault(parent),
400
+ validators: (parent ? (0, validatorUtils_1.getPropertyValidators)(parent) : {}) || {},
401
+ description: comments.length ? ts.displayPartsToString(comments) : undefined,
402
+ format: parent ? this.getNodeFormat(parent) : undefined,
403
+ example: parent ? this.getNodeExample(parent) : undefined,
404
+ extensions: parent ? this.getNodeExtension(parent) : undefined,
405
+ };
406
+ }
407
+ isDeprecatedMappedProperty(parent) {
408
+ if (!parent) {
409
+ return false;
411
410
  }
412
- (0, flowUtils_1.throwUnless)(this.typeNode.kind === ts.SyntaxKind.TypeReference, new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[this.typeNode.kind]}`, this.typeNode));
413
- return this.resolveTypeReferenceNode(this.typeNode, this.current, this.context, this.parentNode);
411
+ return (0, jsDocUtils_1.isExistJSDocTag)(parent, tag => tag.tagName.text === 'deprecated') || (0, decoratorUtils_1.isDecorator)(parent, (_identifier, canonicalName) => canonicalName === 'Deprecated', this.current.typeChecker);
412
+ }
413
+ getMappedPropertyDefault(parent) {
414
+ if (!parent) {
415
+ return undefined;
416
+ }
417
+ if (hasInitializer(parent)) {
418
+ return (0, initializer_value_1.getInitializerValue)(parent.initializer, this.current.typeChecker);
419
+ }
420
+ return TypeResolver.getDefault(parent);
421
+ }
422
+ resolveMappedIndexTypes(type, mappedTypeNode) {
423
+ return this.current.typeChecker.getIndexInfosOfType(type).flatMap(indexInfo => {
424
+ const typeNode = this.current.typeChecker.typeToTypeNode(indexInfo.type, undefined, ts.NodeBuilderFlags.NoTruncation);
425
+ if (typeNode.kind === ts.SyntaxKind.NeverKeyword) {
426
+ return [];
427
+ }
428
+ return [new TypeResolver(typeNode, this.current, mappedTypeNode, this.context, indexInfo.type).resolve()];
429
+ });
430
+ }
431
+ resolveConditionalTypeNode() {
432
+ if (!ts.isConditionalTypeNode(this.typeNode)) {
433
+ return undefined;
434
+ }
435
+ const referencer = this.getReferencer();
436
+ const resolvedNode = this.current.typeChecker.typeToTypeNode(referencer, undefined, ts.NodeBuilderFlags.NoTruncation);
437
+ return new TypeResolver(resolvedNode, this.current, this.typeNode, this.context, referencer).resolve();
438
+ }
439
+ resolveTypeOperatorTypeNode() {
440
+ if (!ts.isTypeOperatorNode(this.typeNode)) {
441
+ return undefined;
442
+ }
443
+ return this.resolveTypeOperatorNode(this.typeNode, this.current.typeChecker, this.current, this.context, this.parentNode, this.referencer);
444
+ }
445
+ resolveIndexedAccessTypeNodeWrapper() {
446
+ if (!ts.isIndexedAccessTypeNode(this.typeNode)) {
447
+ return undefined;
448
+ }
449
+ return this.resolveIndexedAccessTypeNode(this.typeNode, this.current.typeChecker, this.current, this.context);
450
+ }
451
+ resolveTemplateLiteralTypeNode() {
452
+ if (!ts.isTemplateLiteralTypeNode(this.typeNode)) {
453
+ return undefined;
454
+ }
455
+ const type = this.getReferencer();
456
+ (0, flowUtils_1.throwUnless)(type.isUnion() && type.types.every((unionElementType) => unionElementType.isStringLiteral()), new exceptions_1.GenerateMetadataError(`Could not the type of ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode), this.typeNode)}`, this.typeNode));
457
+ return {
458
+ dataType: 'enum',
459
+ enums: type.types.map((stringLiteralType) => stringLiteralType.value),
460
+ };
461
+ }
462
+ resolveParenthesizedTypeNode() {
463
+ if (!ts.isParenthesizedTypeNode(this.typeNode)) {
464
+ return undefined;
465
+ }
466
+ return new TypeResolver(this.typeNode.type, this.current, this.typeNode, this.context, this.referencer).resolve();
414
467
  }
415
468
  resolveTypeOperatorNode(typeNode, typeChecker, current, context, parentNode, referencer) {
416
469
  switch (typeNode.operator) {
417
470
  case ts.SyntaxKind.KeyOfKeyword: {
418
- // keyof
419
- const type = typeChecker.getTypeFromTypeNode(typeNode);
420
- if (type.isIndexType()) {
421
- // in case of generic: keyof T. Not handles all possible cases
422
- const symbol = type.type.getSymbol();
423
- if (symbol && symbol.getFlags() & ts.TypeFlags.TypeParameter) {
424
- const typeName = symbol.getEscapedName();
425
- (0, flowUtils_1.throwUnless)(typeof typeName === 'string', new exceptions_1.GenerateMetadataError(`typeName is not string, but ${typeof typeName}`, typeNode));
426
- if (context[typeName]) {
427
- const subResult = new TypeResolver(context[typeName].type, current, parentNode, context).resolve();
428
- if (subResult.dataType === 'any') {
429
- return {
430
- dataType: 'union',
431
- types: [{ dataType: 'string' }, { dataType: 'double' }],
432
- };
433
- }
434
- const properties = subResult.properties?.map(v => v.name);
435
- (0, flowUtils_1.throwUnless)(properties, new exceptions_1.GenerateMetadataError(`TypeOperator 'keyof' on node which have no properties`, context[typeName].type));
436
- return {
437
- dataType: 'enum',
438
- enums: properties,
439
- };
440
- }
441
- }
442
- }
443
- else if (type.isUnion()) {
444
- const literals = type.types.filter((t) => t.isLiteral());
445
- const literalValues = [];
446
- for (const literal of literals) {
447
- (0, flowUtils_1.throwUnless)(typeof literal.value == 'number' || typeof literal.value == 'string', new exceptions_1.GenerateMetadataError(`Not handled key Type, maybe ts.PseudoBigInt ${typeChecker.typeToString(literal)}`, typeNode));
448
- literalValues.push(literal.value);
449
- }
450
- if (!literals.length) {
451
- const length = type.types.length;
452
- const someStringFlag = type.types.some(t => t.flags === ts.TypeFlags.String);
453
- const someNumberFlag = type.types.some(t => t.flags === ts.TypeFlags.Number);
454
- const someSymbolFlag = type.types.some(t => t.flags === ts.TypeFlags.ESSymbol);
455
- if (someStringFlag && someNumberFlag) {
456
- if (length === 2 || (length === 3 && someSymbolFlag)) {
457
- return {
458
- dataType: 'union',
459
- types: [{ dataType: 'string' }, { dataType: 'double' }],
460
- };
461
- }
462
- }
463
- }
464
- // Warn on nonsense (`number`, `typeof Symbol.iterator`)
465
- if (type.types.find(t => !t.isLiteral()) !== undefined) {
466
- const problems = type.types.filter(t => !t.isLiteral()).map(t => typeChecker.typeToString(t));
467
- console.warn(new exceptions_1.GenerateMetaDataWarning(`Skipped non-literal type(s) ${problems.join(', ')}`, typeNode).toString());
468
- }
469
- const stringMembers = literalValues.filter(v => typeof v == 'string');
470
- const numberMembers = literalValues.filter(v => typeof v == 'number');
471
- if (stringMembers.length && numberMembers.length) {
472
- return {
473
- dataType: 'union',
474
- types: [
475
- { dataType: 'enum', enums: stringMembers },
476
- { dataType: 'enum', enums: numberMembers },
477
- ],
478
- };
479
- }
480
- return {
481
- dataType: 'enum',
482
- enums: literalValues,
483
- };
484
- }
485
- else if (type.isLiteral()) {
486
- (0, flowUtils_1.throwUnless)(typeof type.value == 'number' || typeof type.value == 'string', new exceptions_1.GenerateMetadataError(`Not handled indexType, maybe ts.PseudoBigInt ${typeChecker.typeToString(type)}`, typeNode));
487
- return {
488
- dataType: 'enum',
489
- enums: [type.value],
490
- };
491
- }
492
- else if (this.hasFlag(type, ts.TypeFlags.Never)) {
493
- throw new exceptions_1.GenerateMetadataError(`TypeOperator 'keyof' on node produced a never type`, typeNode);
494
- }
495
- else if (this.hasFlag(type, ts.TypeFlags.TemplateLiteral)) {
496
- //Now assumes template literals as string
497
- console.warn(new exceptions_1.GenerateMetaDataWarning(`Template literals are assumed as strings`, typeNode).toString());
498
- return {
499
- dataType: 'string',
500
- };
501
- }
502
- else if (this.hasFlag(type, ts.TypeFlags.Number)) {
503
- return {
504
- dataType: 'double',
505
- };
506
- }
507
- const indexedTypeName = typeChecker.typeToString(typeChecker.getTypeFromTypeNode(typeNode.type));
508
- throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${indexedTypeName}`, typeNode);
471
+ return this.resolveKeyOfTypeOperator(typeNode, typeChecker, current, context, parentNode);
509
472
  }
510
473
  case ts.SyntaxKind.ReadonlyKeyword:
511
474
  // Handle `readonly` arrays
@@ -514,46 +477,176 @@ class TypeResolver {
514
477
  throw new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[typeNode.kind]}`, typeNode);
515
478
  }
516
479
  }
480
+ resolveKeyOfTypeOperator(typeNode, typeChecker, current, context, parentNode) {
481
+ const type = typeChecker.getTypeFromTypeNode(typeNode);
482
+ const indexedType = this.resolveKeyOfIndexType(type, typeNode, current, context, parentNode);
483
+ if (indexedType) {
484
+ return indexedType;
485
+ }
486
+ if (type.isUnion()) {
487
+ return this.resolveKeyOfUnionType(type, typeNode, typeChecker);
488
+ }
489
+ if (type.isLiteral()) {
490
+ return this.resolveKeyOfLiteralType(type, typeNode, typeChecker);
491
+ }
492
+ return this.resolveFallbackKeyOfType(type, typeNode, typeChecker);
493
+ }
494
+ resolveKeyOfIndexType(type, typeNode, current, context, parentNode) {
495
+ if (!type.isIndexType()) {
496
+ return undefined;
497
+ }
498
+ const symbol = type.type.getSymbol();
499
+ if (!symbol || (symbol.getFlags() & ts.TypeFlags.TypeParameter) === 0) {
500
+ return undefined;
501
+ }
502
+ const typeName = symbol.getEscapedName();
503
+ (0, flowUtils_1.throwUnless)(typeof typeName === 'string', new exceptions_1.GenerateMetadataError(`typeName is not string, but ${typeof typeName}`, typeNode));
504
+ const contextualType = context[typeName];
505
+ if (!contextualType) {
506
+ return undefined;
507
+ }
508
+ const subResult = new TypeResolver(contextualType.type, current, parentNode, context).resolve();
509
+ if (subResult.dataType === 'any') {
510
+ return this.createStringAndNumberUnion();
511
+ }
512
+ const properties = subResult.properties?.map(property => property.name);
513
+ (0, flowUtils_1.throwUnless)(properties, new exceptions_1.GenerateMetadataError(`TypeOperator 'keyof' on node which have no properties`, contextualType.type));
514
+ return {
515
+ dataType: 'enum',
516
+ enums: properties,
517
+ };
518
+ }
519
+ resolveKeyOfUnionType(type, typeNode, typeChecker) {
520
+ const literals = type.types.filter((member) => member.isLiteral());
521
+ if (!literals.length) {
522
+ return this.resolveNonLiteralKeyOfUnionType(type, typeNode, typeChecker);
523
+ }
524
+ this.warnOnSkippedNonLiteralKeyTypes(type, typeNode, typeChecker);
525
+ return this.createLiteralKeyOfUnionType(literals, typeNode, typeChecker);
526
+ }
527
+ resolveNonLiteralKeyOfUnionType(type, typeNode, typeChecker) {
528
+ const typeFlags = new Set(type.types.map(member => member.flags));
529
+ const includesString = typeFlags.has(ts.TypeFlags.String);
530
+ const includesNumber = typeFlags.has(ts.TypeFlags.Number);
531
+ const includesSymbol = typeFlags.has(ts.TypeFlags.ESSymbol);
532
+ if (includesString && includesNumber && (type.types.length === 2 || (type.types.length === 3 && includesSymbol))) {
533
+ return this.createStringAndNumberUnion();
534
+ }
535
+ this.warnOnSkippedNonLiteralKeyTypes(type, typeNode, typeChecker);
536
+ return { dataType: 'enum', enums: [] };
537
+ }
538
+ warnOnSkippedNonLiteralKeyTypes(type, typeNode, typeChecker) {
539
+ const nonLiteralTypes = type.types.filter(member => !member.isLiteral());
540
+ if (!nonLiteralTypes.length) {
541
+ return;
542
+ }
543
+ const problems = nonLiteralTypes.map(member => typeChecker.typeToString(member));
544
+ console.warn(new exceptions_1.GenerateMetaDataWarning(`Skipped non-literal type(s) ${problems.join(', ')}`, typeNode).toString());
545
+ }
546
+ createLiteralKeyOfUnionType(literals, typeNode, typeChecker) {
547
+ const literalValues = literals.map(literal => this.getKeyLiteralValue(literal, typeNode, typeChecker));
548
+ const stringMembers = literalValues.filter((value) => typeof value === 'string');
549
+ const numberMembers = literalValues.filter((value) => typeof value === 'number');
550
+ if (stringMembers.length && numberMembers.length) {
551
+ return {
552
+ dataType: 'union',
553
+ types: [
554
+ { dataType: 'enum', enums: stringMembers },
555
+ { dataType: 'enum', enums: numberMembers },
556
+ ],
557
+ };
558
+ }
559
+ return {
560
+ dataType: 'enum',
561
+ enums: literalValues,
562
+ };
563
+ }
564
+ getKeyLiteralValue(literal, typeNode, typeChecker) {
565
+ (0, flowUtils_1.throwUnless)(typeof literal.value === 'number' || typeof literal.value === 'string', new exceptions_1.GenerateMetadataError(`Not handled key Type, maybe ts.PseudoBigInt ${typeChecker.typeToString(literal)}`, typeNode));
566
+ return literal.value;
567
+ }
568
+ resolveKeyOfLiteralType(type, typeNode, typeChecker) {
569
+ (0, flowUtils_1.throwUnless)(typeof type.value === 'number' || typeof type.value === 'string', new exceptions_1.GenerateMetadataError(`Not handled indexType, maybe ts.PseudoBigInt ${typeChecker.typeToString(type)}`, typeNode));
570
+ return {
571
+ dataType: 'enum',
572
+ enums: [type.value],
573
+ };
574
+ }
575
+ resolveFallbackKeyOfType(type, typeNode, typeChecker) {
576
+ if (this.hasFlag(type, ts.TypeFlags.Never)) {
577
+ throw new exceptions_1.GenerateMetadataError(`TypeOperator 'keyof' on node produced a never type`, typeNode);
578
+ }
579
+ if (this.hasFlag(type, ts.TypeFlags.TemplateLiteral)) {
580
+ console.warn(new exceptions_1.GenerateMetaDataWarning(`Template literals are assumed as strings`, typeNode).toString());
581
+ return { dataType: 'string' };
582
+ }
583
+ if (this.hasFlag(type, ts.TypeFlags.Number)) {
584
+ return { dataType: 'double' };
585
+ }
586
+ const indexedTypeName = typeChecker.typeToString(typeChecker.getTypeFromTypeNode(typeNode.type));
587
+ throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${indexedTypeName}`, typeNode);
588
+ }
589
+ createStringAndNumberUnion() {
590
+ return {
591
+ dataType: 'union',
592
+ types: [{ dataType: 'string' }, { dataType: 'double' }],
593
+ };
594
+ }
517
595
  resolveIndexedAccessTypeNode(typeNode, typeChecker, current, context) {
518
596
  const { indexType, objectType } = typeNode;
519
597
  if ([ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.StringKeyword].includes(indexType.kind)) {
520
- // Indexed by keyword
521
- const isNumberIndexType = indexType.kind === ts.SyntaxKind.NumberKeyword;
522
- const typeOfObjectType = typeChecker.getTypeFromTypeNode(objectType);
523
- const type = isNumberIndexType ? typeOfObjectType.getNumberIndexType() : typeOfObjectType.getStringIndexType();
524
- (0, flowUtils_1.throwUnless)(type, new exceptions_1.GenerateMetadataError(`Could not determine ${isNumberIndexType ? 'number' : 'string'} index on ${typeChecker.typeToString(typeOfObjectType)}`, typeNode));
525
- return new TypeResolver(typeChecker.typeToTypeNode(type, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
526
- }
527
- else if (ts.isLiteralTypeNode(indexType) && (ts.isStringLiteral(indexType.literal) || ts.isNumericLiteral(indexType.literal))) {
528
- // Indexed by literal
529
- const hasType = (node) => node !== undefined && Object.prototype.hasOwnProperty.call(node, 'type');
530
- const symbol = typeChecker.getPropertyOfType(typeChecker.getTypeFromTypeNode(objectType), indexType.literal.text);
531
- (0, flowUtils_1.throwUnless)(symbol, new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeChecker.typeToString(typeChecker.getTypeFromTypeNode(objectType))}`, typeNode));
532
- if (hasType(symbol.valueDeclaration) && symbol.valueDeclaration.type) {
533
- return new TypeResolver(symbol.valueDeclaration.type, current, typeNode, context).resolve();
534
- }
535
- const declaration = typeChecker.getTypeOfSymbolAtLocation(symbol, objectType);
536
- try {
537
- return new TypeResolver(typeChecker.typeToTypeNode(declaration, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
538
- }
539
- catch {
540
- throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeChecker.typeToString(typeChecker.getTypeFromTypeNode(typeChecker.typeToTypeNode(declaration, undefined, ts.NodeBuilderFlags.NoTruncation)))}`, typeNode);
541
- }
598
+ return this.resolveIndexedAccessKeywordType(typeNode, typeChecker, current, context, objectType, indexType);
542
599
  }
543
- else if (ts.isTypeOperatorNode(indexType) && indexType.operator === ts.SyntaxKind.KeyOfKeyword) {
544
- // Indexed by keyof typeof value
545
- const typeOfObjectType = ts.isParenthesizedTypeNode(objectType) ? objectType.type : objectType;
546
- const { type: typeOfIndexType } = indexType;
547
- const isSameTypeQuery = ts.isTypeQueryNode(typeOfObjectType) && ts.isTypeQueryNode(typeOfIndexType) && typeOfObjectType.exprName.getText() === typeOfIndexType.exprName.getText();
548
- const isSameTypeReference = ts.isTypeReferenceNode(typeOfObjectType) && ts.isTypeReferenceNode(typeOfIndexType) && typeOfObjectType.typeName.getText() === typeOfIndexType.typeName.getText();
549
- if (isSameTypeQuery || isSameTypeReference) {
550
- const type = this.getReferencer();
551
- const node = typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation);
552
- return new TypeResolver(node, current, typeNode, context, this.referencer).resolve();
600
+ if (ts.isLiteralTypeNode(indexType) && (ts.isStringLiteral(indexType.literal) || ts.isNumericLiteral(indexType.literal))) {
601
+ return this.resolveIndexedAccessLiteralType(typeNode, typeChecker, current, context, objectType, indexType);
602
+ }
603
+ if (ts.isTypeOperatorNode(indexType) && indexType.operator === ts.SyntaxKind.KeyOfKeyword) {
604
+ const keyedIndexedAccessType = this.resolveKeyedIndexedAccessType(typeNode, typeChecker, current, context, objectType, indexType);
605
+ if (keyedIndexedAccessType) {
606
+ return keyedIndexedAccessType;
553
607
  }
554
608
  }
555
609
  throw new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[typeNode.kind]}`, typeNode);
556
610
  }
611
+ resolveIndexedAccessKeywordType(typeNode, typeChecker, current, context, objectType, indexType) {
612
+ const isNumberIndexType = indexType.kind === ts.SyntaxKind.NumberKeyword;
613
+ const typeOfObjectType = typeChecker.getTypeFromTypeNode(objectType);
614
+ const indexedType = isNumberIndexType ? typeOfObjectType.getNumberIndexType() : typeOfObjectType.getStringIndexType();
615
+ (0, flowUtils_1.throwUnless)(indexedType, new exceptions_1.GenerateMetadataError(`Could not determine ${isNumberIndexType ? 'number' : 'string'} index on ${typeChecker.typeToString(typeOfObjectType)}`, typeNode));
616
+ return new TypeResolver(typeChecker.typeToTypeNode(indexedType, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
617
+ }
618
+ resolveIndexedAccessLiteralType(typeNode, typeChecker, current, context, objectType, indexType) {
619
+ 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));
622
+ if (this.symbolHasTypeDeclaration(symbol.valueDeclaration)) {
623
+ return new TypeResolver(symbol.valueDeclaration.type, current, typeNode, context).resolve();
624
+ }
625
+ const declarationType = typeChecker.getTypeOfSymbolAtLocation(symbol, objectType);
626
+ try {
627
+ return new TypeResolver(typeChecker.typeToTypeNode(declarationType, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
628
+ }
629
+ catch {
630
+ const typeNodeForError = typeChecker.typeToTypeNode(declarationType, undefined, ts.NodeBuilderFlags.NoTruncation);
631
+ const typeName = typeChecker.typeToString(typeChecker.getTypeFromTypeNode(typeNodeForError));
632
+ throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeName}`, typeNode);
633
+ }
634
+ }
635
+ symbolHasTypeDeclaration(node) {
636
+ return node !== undefined && objectHasOwn(node, 'type') && node.type !== undefined;
637
+ }
638
+ resolveKeyedIndexedAccessType(typeNode, typeChecker, current, context, objectType, indexType) {
639
+ const typeOfObjectType = ts.isParenthesizedTypeNode(objectType) ? objectType.type : objectType;
640
+ const typeOfIndexType = indexType.type;
641
+ const isSameTypeQuery = ts.isTypeQueryNode(typeOfObjectType) && ts.isTypeQueryNode(typeOfIndexType) && typeOfObjectType.exprName.getText() === typeOfIndexType.exprName.getText();
642
+ const isSameTypeReference = ts.isTypeReferenceNode(typeOfObjectType) && ts.isTypeReferenceNode(typeOfIndexType) && typeOfObjectType.typeName.getText() === typeOfIndexType.typeName.getText();
643
+ if (!isSameTypeQuery && !isSameTypeReference) {
644
+ return undefined;
645
+ }
646
+ const type = this.getReferencer();
647
+ const node = typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation);
648
+ return new TypeResolver(node, current, typeNode, context, this.referencer).resolve();
649
+ }
557
650
  resolveTypeReferenceNode(typeNode, current, context, parentNode) {
558
651
  const { typeName } = typeNode;
559
652
  const resolvedTypeArguments = typeNode.typeArguments ? [...typeNode.typeArguments] : undefined;
@@ -716,15 +809,15 @@ class TypeResolver {
716
809
  case ts.SyntaxKind.StringLiteral:
717
810
  return typeNode.literal.text;
718
811
  case ts.SyntaxKind.NumericLiteral:
719
- return parseFloat(typeNode.literal.text);
812
+ return Number.parseFloat(typeNode.literal.text);
720
813
  case ts.SyntaxKind.PrefixUnaryExpression:
721
814
  // make sure to only handle the MinusToken here
722
815
  (0, flowUtils_1.throwUnless)(typeNode.literal.operator === ts.SyntaxKind.MinusToken, new exceptions_1.GenerateMetadataError(`Couldn't resolve literal node: ${typeNode.literal.getText()}`));
723
- return parseFloat(typeNode.literal.getText());
816
+ return Number.parseFloat(typeNode.literal.getText());
724
817
  case ts.SyntaxKind.NullKeyword:
725
818
  return null;
726
819
  default:
727
- (0, flowUtils_1.throwUnless)(Object.prototype.hasOwnProperty.call(typeNode.literal, 'text'), new exceptions_1.GenerateMetadataError(`Couldn't resolve literal node: ${typeNode.literal.getText()}`));
820
+ (0, flowUtils_1.throwUnless)(objectHasOwn(typeNode.literal, 'text'), new exceptions_1.GenerateMetadataError(`Couldn't resolve literal node: ${typeNode.literal.getText()}`));
728
821
  return typeNode.literal.text;
729
822
  }
730
823
  }
@@ -765,92 +858,83 @@ class TypeResolver {
765
858
  }
766
859
  //Generates type name for type references
767
860
  calcRefTypeName(type) {
768
- const getEntityName = (type) => {
769
- if (ts.isIdentifier(type)) {
770
- return type.text;
771
- }
772
- return `${getEntityName(type.left)}.${type.right.text}`;
773
- };
774
- let name = getEntityName(type);
775
- if (this.context[name]) {
776
- //resolve name only interesting if entity is not qualifiedName
777
- name = this.context[name].name; //Not needed to check unicity, because generic parameters are checked previously
861
+ const contextualName = this.context[this.getEntityNameText(type)]?.name;
862
+ if (contextualName) {
863
+ return contextualName;
864
+ }
865
+ const declarations = this.getModelTypeDeclarations(type);
866
+ if (!declarations.length) {
867
+ return this.getFallbackRefTypeName(type);
868
+ }
869
+ const name = this.getDeclarationBasedRefTypeName(type, declarations);
870
+ this.current.CheckModelUnicity(name, declarations.map(declaration => ({
871
+ fileName: declaration.getSourceFile().fileName,
872
+ pos: declaration.pos,
873
+ })));
874
+ return name;
875
+ }
876
+ getEntityNameText(type) {
877
+ if (ts.isIdentifier(type)) {
878
+ return type.text;
778
879
  }
779
- else {
780
- const declarations = this.getModelTypeDeclarations(type);
781
- // Handle cases where declarations is empty (e.g., inline object types in generics)
782
- if (!declarations || declarations.length === 0) {
783
- // Check if this is a simple identifier (like Date, String, etc.)
784
- if (ts.isIdentifier(type)) {
785
- // For simple identifiers, just return the name
786
- return type.text;
787
- }
788
- // For inline object types, we should use the resolve method to get the proper type
789
- // This will handle TypeLiteralNode, UnionTypeNode, etc. properly
790
- // Note: We need to cast to TypeNode since EntityName can be Identifier or QualifiedName
791
- const typeNode = type;
792
- const resolvedType = new TypeResolver(typeNode, this.current, this.parentNode, this.context).resolve();
793
- // Generate a deterministic name for this inline type based on its structure
794
- const typeName = this.calcTypeName(typeNode);
795
- const sanitizedName = typeName
796
- .replace(/[^A-Za-z0-9]/g, '_')
797
- .replace(/_+/g, '_')
798
- .replace(/^_|_$/g, '');
799
- const uniqueName = `Inline_${sanitizedName}`;
800
- // Add to reference types so it can be properly serialized
801
- // We need to create a proper ReferenceType object
802
- const referenceType = {
803
- dataType: 'refAlias',
804
- refName: uniqueName,
805
- type: resolvedType,
806
- validators: {},
807
- deprecated: false,
808
- };
809
- this.current.AddReferenceType(referenceType);
810
- return uniqueName;
811
- }
812
- //Two possible solutions for recognizing different types:
813
- // - Add declaration positions into type names (In an order).
814
- // - It accepts multiple types with same name, if the code compiles, there would be no conflicts in the type names
815
- // - Clear namespaces from type names.
816
- // - Horrible changes can be in the routes.ts in case of teamwork,
817
- // because source files have paths in the computer where data generation runs.
818
- // - Use fully namespaced names
819
- // - Conflicts can be recognized because of the declarations
820
- //
821
- // The second was implemented, it not changes the usual type name formats.
822
- const oneDeclaration = declarations[0]; //Every declarations should be in the same namespace hierarchy
823
- if (oneDeclaration && ts.isEnumMember(oneDeclaration)) {
824
- name = `${oneDeclaration.parent.name.getText()}.${oneDeclaration.name.getText()}`;
825
- }
826
- else {
827
- name = oneDeclaration.name?.getText() || name;
880
+ return `${this.getEntityNameText(type.left)}.${type.right.text}`;
881
+ }
882
+ getFallbackRefTypeName(type) {
883
+ if (ts.isIdentifier(type)) {
884
+ return type.text;
885
+ }
886
+ return this.createInlineReferenceTypeName(type);
887
+ }
888
+ createInlineReferenceTypeName(typeNode) {
889
+ const resolvedType = new TypeResolver(typeNode, this.current, this.parentNode, this.context).resolve();
890
+ const uniqueName = `Inline_${this.sanitizeInlineTypeName(this.calcTypeName(typeNode))}`;
891
+ this.current.AddReferenceType({
892
+ dataType: 'refAlias',
893
+ refName: uniqueName,
894
+ type: resolvedType,
895
+ validators: {},
896
+ deprecated: false,
897
+ });
898
+ return uniqueName;
899
+ }
900
+ sanitizeInlineTypeName(typeName) {
901
+ return typeName
902
+ .replaceAll(/[^A-Za-z0-9]/g, '_')
903
+ .replaceAll(/_+/g, '_')
904
+ .replaceAll(/^_+|_+$/g, '');
905
+ }
906
+ getDeclarationBasedRefTypeName(type, declarations) {
907
+ const declaration = declarations[0];
908
+ let name = this.getDeclarationRefTypeName(declaration, this.getEntityNameText(type));
909
+ let currentNode = declaration.parent;
910
+ let isFirst = true;
911
+ while (!ts.isSourceFile(currentNode)) {
912
+ if (ts.isBlock(currentNode)) {
913
+ break;
828
914
  }
829
- let actNode = oneDeclaration.parent;
830
- let isFirst = true;
831
- const isGlobalDeclaration = (mod) => mod.name.kind === ts.SyntaxKind.Identifier && mod.name.text === 'global';
832
- while (!ts.isSourceFile(actNode)) {
833
- if (ts.isBlock(actNode)) {
834
- break;
835
- }
836
- if (!(isFirst && ts.isEnumDeclaration(actNode)) && !ts.isModuleBlock(actNode)) {
837
- (0, flowUtils_1.throwUnless)(ts.isModuleDeclaration(actNode), new exceptions_1.GenerateMetadataError(`This node kind is unknown: ${actNode.kind}`, type));
838
- if (!isGlobalDeclaration(actNode)) {
839
- const moduleName = actNode.name.text;
840
- name = `${moduleName}.${name}`;
841
- }
915
+ if (this.shouldPrefixDeclarationNamespace(currentNode, isFirst)) {
916
+ (0, flowUtils_1.throwUnless)(ts.isModuleDeclaration(currentNode), new exceptions_1.GenerateMetadataError(`This node kind is unknown: ${currentNode.kind}`, type));
917
+ if (!this.isGlobalDeclaration(currentNode)) {
918
+ name = `${currentNode.name.text}.${name}`;
842
919
  }
843
- isFirst = false;
844
- actNode = actNode.parent;
845
920
  }
846
- const declarationPositions = declarations.map(declaration => ({
847
- fileName: declaration.getSourceFile().fileName,
848
- pos: declaration.pos,
849
- }));
850
- this.current.CheckModelUnicity(name, declarationPositions);
921
+ isFirst = false;
922
+ currentNode = currentNode.parent;
851
923
  }
852
924
  return name;
853
925
  }
926
+ getDeclarationRefTypeName(declaration, fallbackName) {
927
+ if (ts.isEnumMember(declaration)) {
928
+ return `${declaration.parent.name.getText()}.${declaration.name.getText()}`;
929
+ }
930
+ return declaration.name?.getText() ?? fallbackName;
931
+ }
932
+ shouldPrefixDeclarationNamespace(node, isFirst) {
933
+ return !(isFirst && ts.isEnumDeclaration(node)) && !ts.isModuleBlock(node);
934
+ }
935
+ isGlobalDeclaration(node) {
936
+ return node.name.kind === ts.SyntaxKind.Identifier && node.name.text === 'global';
937
+ }
854
938
  calcMemberJsDocProperties(arg) {
855
939
  const def = TypeResolver.getDefault(arg);
856
940
  const isDeprecated = (0, jsDocUtils_1.isExistJSDocTag)(arg, tag => tag.tagName.text === 'deprecated') || (0, decoratorUtils_1.isDecorator)(arg, (_identifier, canonicalName) => canonicalName === 'Deprecated', this.current.typeChecker);
@@ -861,13 +945,13 @@ class TypeResolver {
861
945
  const format = this.getNodeFormat(arg);
862
946
  const example = this.getNodeExample(arg);
863
947
  const extensions = this.getNodeExtension(arg);
864
- const isIgnored = (0, jsDocUtils_1.getJSDocTagNames)(arg).some(tag => tag === 'ignore');
948
+ const isIgnored = (0, jsDocUtils_1.getJSDocTagNames)(arg).includes('ignore');
865
949
  const jsonObj = {
866
950
  default: def,
867
951
  description,
868
952
  validators: validators && Object.keys(validators).length ? validators : undefined,
869
953
  format,
870
- example: example !== undefined ? example : undefined,
954
+ example,
871
955
  extensions: extensions.length ? extensions : undefined,
872
956
  deprecated: isDeprecated ? true : undefined,
873
957
  ignored: isIgnored ? true : undefined,
@@ -885,103 +969,131 @@ class TypeResolver {
885
969
  }
886
970
  //Generates type name for type references
887
971
  calcTypeName(arg) {
888
- if (ts.isLiteralTypeNode(arg)) {
889
- const literalValue = this.getLiteralValue(arg);
890
- if (typeof literalValue == 'string') {
891
- return `'${literalValue}'`;
892
- }
893
- if (literalValue === null) {
894
- return 'null';
895
- }
896
- if (typeof literalValue === 'boolean') {
897
- return literalValue === true ? 'true' : 'false';
898
- }
899
- return `${literalValue}`;
972
+ const literalTypeName = this.getLiteralTypeName(arg);
973
+ if (literalTypeName) {
974
+ return literalTypeName;
900
975
  }
901
976
  const resolvedType = primitiveTransformer_1.PrimitiveTransformer.resolveKindToPrimitive(arg.kind);
902
977
  if (resolvedType) {
903
978
  return resolvedType;
904
979
  }
905
- if (ts.isTypeReferenceNode(arg) || ts.isExpressionWithTypeArguments(arg)) {
906
- return this.calcTypeReferenceTypeName(arg)[1];
980
+ const structuralTypeName = this.getStructuralTypeName(arg);
981
+ if (structuralTypeName) {
982
+ return structuralTypeName;
907
983
  }
908
- else if (ts.isTypeLiteralNode(arg)) {
909
- const members = arg.members.map(member => {
910
- if (ts.isPropertySignature(member)) {
911
- const name = member.name.text;
912
- const typeText = this.calcTypeName(member.type);
913
- return `"${name}"${member.questionToken ? '?' : ''}${this.calcMemberJsDocProperties(member)}: ${typeText}`;
914
- }
915
- else if (ts.isIndexSignatureDeclaration(member)) {
916
- (0, flowUtils_1.throwUnless)(member.parameters.length === 1, new exceptions_1.GenerateMetadataError(`Index signature parameters length != 1`, member));
917
- const indexType = member.parameters[0];
918
- (0, flowUtils_1.throwUnless)(
919
- // now we can't reach this part of code
920
- ts.isParameter(indexType), new exceptions_1.GenerateMetadataError(`indexSignature declaration parameter kind is not SyntaxKind.Parameter`, indexType));
921
- (0, flowUtils_1.throwUnless)(!indexType.questionToken, new exceptions_1.GenerateMetadataError(`Question token has found for an indexSignature declaration`, indexType));
922
- const typeText = this.calcTypeName(member.type);
923
- const indexName = indexType.name.text;
924
- const indexTypeText = this.calcTypeName(indexType.type);
925
- return `["${indexName}": ${indexTypeText}]: ${typeText}`;
926
- }
927
- throw new exceptions_1.GenerateMetadataError(`Unhandled member kind has found: ${member.kind}`, member);
928
- });
929
- return `{${members.join('; ')}}`;
984
+ console.warn(new exceptions_1.GenerateMetaDataWarning(`This kind (${arg.kind}) is unhandled, so the type will be any, and no type conflict checks will made`, arg).toString());
985
+ return 'any';
986
+ }
987
+ getLiteralTypeName(arg) {
988
+ if (!ts.isLiteralTypeNode(arg)) {
989
+ return undefined;
930
990
  }
931
- else if (ts.isArrayTypeNode(arg)) {
932
- const typeName = this.calcTypeName(arg.elementType);
933
- return `${typeName}[]`;
991
+ const literalValue = this.getLiteralValue(arg);
992
+ if (typeof literalValue === 'string') {
993
+ return `'${literalValue}'`;
934
994
  }
935
- else if (ts.isIntersectionTypeNode(arg)) {
936
- const memberTypeNames = arg.types.map(type => this.calcTypeName(type));
937
- return memberTypeNames.join(' & ');
995
+ if (literalValue === null) {
996
+ return 'null';
938
997
  }
939
- else if (ts.isUnionTypeNode(arg)) {
940
- const memberTypeNames = arg.types.map(type => this.calcTypeName(type));
941
- return memberTypeNames.join(' | ');
998
+ if (typeof literalValue === 'boolean') {
999
+ return literalValue ? 'true' : 'false';
942
1000
  }
943
- else if (ts.isTypeOperatorNode(arg)) {
944
- const subTypeName = this.calcTypeName(arg.type);
945
- if (arg.operator === ts.SyntaxKind.KeyOfKeyword) {
946
- return `keyof ${subTypeName}`;
947
- }
948
- else if (arg.operator === ts.SyntaxKind.ReadonlyKeyword) {
949
- return `readonly ${subTypeName}`;
950
- }
951
- throw new exceptions_1.GenerateMetadataError(`Unknown keyword has found: ${arg.operator}`, arg);
1001
+ return `${literalValue}`;
1002
+ }
1003
+ getStructuralTypeName(arg) {
1004
+ return (this.getReferenceLikeTypeName(arg) ??
1005
+ this.getTypeLiteralName(arg) ??
1006
+ this.getArrayTypeName(arg) ??
1007
+ this.getIntersectionTypeName(arg) ??
1008
+ this.getUnionTypeName(arg) ??
1009
+ this.getTypeOperatorTypeName(arg) ??
1010
+ this.getTypeQueryName(arg) ??
1011
+ this.getIndexedAccessTypeName(arg) ??
1012
+ this.getKeywordTypeName(arg) ??
1013
+ this.getConditionalTypeName(arg) ??
1014
+ this.getParenthesizedTypeName(arg));
1015
+ }
1016
+ getReferenceLikeTypeName(arg) {
1017
+ if (!ts.isTypeReferenceNode(arg) && !ts.isExpressionWithTypeArguments(arg)) {
1018
+ return undefined;
952
1019
  }
953
- else if (ts.isTypeQueryNode(arg)) {
954
- const subTypeName = this.calcRefTypeName(arg.exprName);
955
- return `typeof ${subTypeName}`;
1020
+ return this.calcTypeReferenceTypeName(arg)[1];
1021
+ }
1022
+ getTypeLiteralName(arg) {
1023
+ if (!ts.isTypeLiteralNode(arg)) {
1024
+ return undefined;
956
1025
  }
957
- else if (ts.isIndexedAccessTypeNode(arg)) {
958
- const objectTypeName = this.calcTypeName(arg.objectType);
959
- const indexTypeName = this.calcTypeName(arg.indexType);
960
- return `${objectTypeName}[${indexTypeName}]`;
1026
+ return `{${arg.members.map(member => this.getTypeLiteralMemberName(member)).join('; ')}}`;
1027
+ }
1028
+ getTypeLiteralMemberName(member) {
1029
+ if (ts.isPropertySignature(member)) {
1030
+ const name = member.name.text;
1031
+ const typeText = this.calcTypeName(member.type);
1032
+ return `"${name}"${member.questionToken ? '?' : ''}${this.calcMemberJsDocProperties(member)}: ${typeText}`;
961
1033
  }
962
- else if (arg.kind === ts.SyntaxKind.UnknownKeyword) {
963
- return 'unknown';
1034
+ if (ts.isIndexSignatureDeclaration(member)) {
1035
+ return this.getIndexSignatureTypeName(member);
964
1036
  }
965
- else if (arg.kind === ts.SyntaxKind.AnyKeyword) {
966
- return 'any';
1037
+ throw new exceptions_1.GenerateMetadataError(`Unhandled member kind has found: ${member.kind}`, member);
1038
+ }
1039
+ getIndexSignatureTypeName(member) {
1040
+ (0, flowUtils_1.throwUnless)(member.parameters.length === 1, new exceptions_1.GenerateMetadataError(`Index signature parameters length != 1`, member));
1041
+ const indexType = member.parameters[0];
1042
+ (0, flowUtils_1.throwUnless)(ts.isParameter(indexType), new exceptions_1.GenerateMetadataError(`indexSignature declaration parameter kind is not SyntaxKind.Parameter`, indexType));
1043
+ (0, flowUtils_1.throwUnless)(!indexType.questionToken, new exceptions_1.GenerateMetadataError(`Question token has found for an indexSignature declaration`, indexType));
1044
+ const indexName = indexType.name.text;
1045
+ const indexTypeText = this.calcTypeName(indexType.type);
1046
+ return `["${indexName}": ${indexTypeText}]: ${this.calcTypeName(member.type)}`;
1047
+ }
1048
+ getArrayTypeName(arg) {
1049
+ return ts.isArrayTypeNode(arg) ? `${this.calcTypeName(arg.elementType)}[]` : undefined;
1050
+ }
1051
+ getIntersectionTypeName(arg) {
1052
+ return ts.isIntersectionTypeNode(arg) ? arg.types.map(type => this.calcTypeName(type)).join(' & ') : undefined;
1053
+ }
1054
+ getUnionTypeName(arg) {
1055
+ return ts.isUnionTypeNode(arg) ? arg.types.map(type => this.calcTypeName(type)).join(' | ') : undefined;
1056
+ }
1057
+ getTypeOperatorTypeName(arg) {
1058
+ if (!ts.isTypeOperatorNode(arg)) {
1059
+ return undefined;
967
1060
  }
968
- else if (arg.kind === ts.SyntaxKind.NeverKeyword) {
969
- return 'never';
1061
+ const subTypeName = this.calcTypeName(arg.type);
1062
+ if (arg.operator === ts.SyntaxKind.KeyOfKeyword) {
1063
+ return `keyof ${subTypeName}`;
970
1064
  }
971
- else if (ts.isConditionalTypeNode(arg)) {
972
- const checkTypeName = this.calcTypeName(arg.checkType);
973
- const extendsTypeName = this.calcTypeName(arg.extendsType);
974
- const trueTypeName = this.calcTypeName(arg.trueType);
975
- const falseTypeName = this.calcTypeName(arg.falseType);
976
- return `${checkTypeName} extends ${extendsTypeName} ? ${trueTypeName} : ${falseTypeName}`;
1065
+ if (arg.operator === ts.SyntaxKind.ReadonlyKeyword) {
1066
+ return `readonly ${subTypeName}`;
1067
+ }
1068
+ throw new exceptions_1.GenerateMetadataError(`Unknown keyword has found: ${arg.operator}`, arg);
1069
+ }
1070
+ getTypeQueryName(arg) {
1071
+ return ts.isTypeQueryNode(arg) ? `typeof ${this.calcRefTypeName(arg.exprName)}` : undefined;
1072
+ }
1073
+ getIndexedAccessTypeName(arg) {
1074
+ return ts.isIndexedAccessTypeNode(arg) ? `${this.calcTypeName(arg.objectType)}[${this.calcTypeName(arg.indexType)}]` : undefined;
1075
+ }
1076
+ getKeywordTypeName(arg) {
1077
+ if (arg.kind === ts.SyntaxKind.UnknownKeyword) {
1078
+ return 'unknown';
977
1079
  }
978
- else if (ts.isParenthesizedTypeNode(arg)) {
979
- const internalTypeName = this.calcTypeName(arg.type);
980
- return `(${internalTypeName})`; //Parentheses are not really interesting. The type name generation adds parentheses for the clarity
1080
+ if (arg.kind === ts.SyntaxKind.AnyKeyword) {
1081
+ return 'any';
981
1082
  }
982
- const warning = new exceptions_1.GenerateMetaDataWarning(`This kind (${arg.kind}) is unhandled, so the type will be any, and no type conflict checks will made`, arg);
983
- console.warn(warning.toString());
984
- return 'any';
1083
+ return arg.kind === ts.SyntaxKind.NeverKeyword ? 'never' : undefined;
1084
+ }
1085
+ getConditionalTypeName(arg) {
1086
+ if (!ts.isConditionalTypeNode(arg)) {
1087
+ return undefined;
1088
+ }
1089
+ const checkTypeName = this.calcTypeName(arg.checkType);
1090
+ const extendsTypeName = this.calcTypeName(arg.extendsType);
1091
+ const trueTypeName = this.calcTypeName(arg.trueType);
1092
+ const falseTypeName = this.calcTypeName(arg.falseType);
1093
+ return `${checkTypeName} extends ${extendsTypeName} ? ${trueTypeName} : ${falseTypeName}`;
1094
+ }
1095
+ getParenthesizedTypeName(arg) {
1096
+ return ts.isParenthesizedTypeNode(arg) ? `(${this.calcTypeName(arg.type)})` : undefined;
985
1097
  }
986
1098
  //Generates type name for type references
987
1099
  calcTypeReferenceTypeName(node) {
@@ -999,54 +1111,53 @@ class TypeResolver {
999
1111
  const refTypeName = this.getRefTypeName(name);
1000
1112
  this.current.CheckExpressionUnicity(refTypeName, name);
1001
1113
  this.context = this.typeArgumentsToContext(node, type);
1002
- const calcReferenceType = () => {
1003
- try {
1004
- const existingType = localReferenceTypeCache[name];
1005
- if (existingType) {
1006
- return existingType;
1007
- }
1008
- if (inProgressTypes[name]) {
1009
- return this.createCircularDependencyResolver(name, refTypeName);
1010
- }
1011
- inProgressTypes[name] = [];
1012
- const declarations = this.getModelTypeDeclarations(type);
1013
- const referenceTypes = [];
1014
- // If no declarations found, this might be a built-in type or a type that can't be resolved
1015
- if (declarations.length === 0) {
1016
- const fallbackReferenceType = this.getReferenceTypeFromTypeChecker(node, name, refTypeName);
1017
- if (fallbackReferenceType) {
1018
- this.addToLocalReferenceTypeCache(name, fallbackReferenceType);
1019
- return fallbackReferenceType;
1020
- }
1021
- throw new exceptions_1.GenerateMetadataError(`Could not find declarations for type '${name}'. This might be a complex generic type that needs special handling.`);
1022
- }
1023
- for (const declaration of declarations) {
1024
- if (ts.isTypeAliasDeclaration(declaration)) {
1025
- const referencer = node.pos !== -1 ? this.current.typeChecker.getTypeFromTypeNode(node) : undefined;
1026
- referenceTypes.push(new referenceTransformer_1.ReferenceTransformer().transform(declaration, refTypeName, this, referencer));
1027
- }
1028
- else if (enumTransformer_1.EnumTransformer.transformable(declaration)) {
1029
- referenceTypes.push(new enumTransformer_1.EnumTransformer().transform(this, declaration, refTypeName));
1030
- }
1031
- else {
1032
- referenceTypes.push(this.getModelReference(declaration, refTypeName));
1033
- }
1034
- }
1035
- const referenceType = referenceTransformer_1.ReferenceTransformer.merge(referenceTypes);
1036
- this.addToLocalReferenceTypeCache(name, referenceType);
1037
- return referenceType;
1038
- }
1039
- catch (err) {
1040
- delete inProgressTypes[name];
1041
- throw err;
1042
- }
1043
- };
1044
- const result = calcReferenceType();
1114
+ const result = this.resolveReferenceType(node, type, name, refTypeName);
1045
1115
  if (addToRefTypeMap) {
1046
1116
  this.current.AddReferenceType(result);
1047
1117
  }
1048
1118
  return result;
1049
1119
  }
1120
+ resolveReferenceType(node, type, name, refTypeName) {
1121
+ try {
1122
+ const existingType = localReferenceTypeCache[name];
1123
+ if (existingType) {
1124
+ return existingType;
1125
+ }
1126
+ if (inProgressTypes[name]) {
1127
+ return this.createCircularDependencyResolver(name, refTypeName);
1128
+ }
1129
+ inProgressTypes[name] = [];
1130
+ const declarations = this.getModelTypeDeclarations(type);
1131
+ if (!declarations.length) {
1132
+ return this.resolveReferenceTypeFallback(node, name, refTypeName);
1133
+ }
1134
+ const referenceType = referenceTransformer_1.ReferenceTransformer.merge(declarations.map(declaration => this.resolveDeclarationReferenceType(declaration, node, refTypeName)));
1135
+ this.addToLocalReferenceTypeCache(name, referenceType);
1136
+ return referenceType;
1137
+ }
1138
+ catch (error) {
1139
+ delete inProgressTypes[name];
1140
+ throw error;
1141
+ }
1142
+ }
1143
+ resolveReferenceTypeFallback(node, name, refTypeName) {
1144
+ const fallbackReferenceType = this.getReferenceTypeFromTypeChecker(node, name, refTypeName);
1145
+ if (fallbackReferenceType) {
1146
+ this.addToLocalReferenceTypeCache(name, fallbackReferenceType);
1147
+ return fallbackReferenceType;
1148
+ }
1149
+ throw new exceptions_1.GenerateMetadataError(`Could not find declarations for type '${name}'. This might be a complex generic type that needs special handling.`);
1150
+ }
1151
+ resolveDeclarationReferenceType(declaration, node, refTypeName) {
1152
+ if (ts.isTypeAliasDeclaration(declaration)) {
1153
+ const referencer = node.pos !== -1 ? this.current.typeChecker.getTypeFromTypeNode(node) : undefined;
1154
+ return new referenceTransformer_1.ReferenceTransformer().transform(declaration, refTypeName, this, referencer);
1155
+ }
1156
+ if (enumTransformer_1.EnumTransformer.transformable(declaration)) {
1157
+ return new enumTransformer_1.EnumTransformer().transform(this, declaration, refTypeName);
1158
+ }
1159
+ return this.getModelReference(declaration, refTypeName);
1160
+ }
1050
1161
  addToLocalReferenceTypeCache(name, refType) {
1051
1162
  if (inProgressTypes[name]) {
1052
1163
  for (const fn of inProgressTypes[name]) {
@@ -1064,64 +1175,68 @@ class TypeResolver {
1064
1175
  // Handle toJSON methods
1065
1176
  (0, flowUtils_1.throwUnless)(modelType.name, new exceptions_1.GenerateMetadataError("Can't get Symbol from anonymous class", modelType));
1066
1177
  const type = this.current.typeChecker.getTypeAtLocation(modelType.name);
1067
- const toJSON = this.current.typeChecker.getPropertyOfType(type, 'toJSON');
1068
- if (toJSON && toJSON.valueDeclaration && (ts.isMethodDeclaration(toJSON.valueDeclaration) || ts.isMethodSignature(toJSON.valueDeclaration))) {
1069
- let nodeType = toJSON.valueDeclaration.type;
1178
+ const toJSONDeclaration = this.current.typeChecker.getPropertyOfType(type, 'toJSON')?.valueDeclaration;
1179
+ if (toJSONDeclaration && (ts.isMethodDeclaration(toJSONDeclaration) || ts.isMethodSignature(toJSONDeclaration))) {
1180
+ let nodeType = toJSONDeclaration.type;
1070
1181
  if (!nodeType) {
1071
- const signature = this.current.typeChecker.getSignatureFromDeclaration(toJSON.valueDeclaration);
1182
+ const signature = this.current.typeChecker.getSignatureFromDeclaration(toJSONDeclaration);
1072
1183
  const implicitType = this.current.typeChecker.getReturnTypeOfSignature(signature);
1073
1184
  nodeType = this.current.typeChecker.typeToTypeNode(implicitType, undefined, ts.NodeBuilderFlags.NoTruncation);
1074
1185
  }
1075
- const type = new TypeResolver(nodeType, this.current).resolve();
1076
- const referenceType = {
1186
+ return this.withDefinedReferenceMetadata({
1077
1187
  refName: refTypeName,
1078
1188
  dataType: 'refAlias',
1079
1189
  description,
1080
- type,
1190
+ type: new TypeResolver(nodeType, this.current).resolve(),
1081
1191
  validators: {},
1082
1192
  deprecated,
1083
- ...(example !== undefined ? { example } : {}),
1084
- ...(title !== undefined ? { title } : {}),
1085
- };
1086
- return referenceType;
1193
+ }, { example, title });
1087
1194
  }
1088
1195
  const properties = new propertyTransformer_1.PropertyTransformer().transform(this, modelType);
1089
1196
  const additionalProperties = this.getModelAdditionalProperties(modelType);
1090
1197
  const inheritedProperties = this.getModelInheritedProperties(modelType) || [];
1091
- const referenceType = {
1198
+ const referenceType = this.withDefinedReferenceMetadata({
1092
1199
  additionalProperties,
1093
1200
  dataType: 'refObject',
1094
1201
  description,
1095
1202
  properties: inheritedProperties,
1096
1203
  refName: refTypeName,
1097
1204
  deprecated,
1098
- ...(example !== undefined ? { example } : {}),
1099
- ...(title !== undefined ? { title } : {}),
1100
- };
1205
+ }, { example, title });
1101
1206
  referenceType.properties = referenceType.properties.concat(properties);
1102
1207
  return referenceType;
1103
1208
  }
1209
+ withDefinedReferenceMetadata(referenceType, metadata) {
1210
+ if (metadata.example !== undefined) {
1211
+ referenceType.example = metadata.example;
1212
+ }
1213
+ if (metadata.title !== undefined) {
1214
+ referenceType.title = metadata.title;
1215
+ }
1216
+ return referenceType;
1217
+ }
1104
1218
  //Generates a name from the original type expression.
1105
1219
  //This function is not invertable, so it's possible, that 2 type expressions have the same refTypeName.
1106
1220
  getRefTypeName(name) {
1107
1221
  let preformattedName = name //Preformatted name handles most cases
1108
- .replace(/<|>/g, '_')
1109
- .replace(/\s+/g, '')
1110
- .replace(/,/g, '.')
1111
- .replace(/'([^']*)'/g, '$1')
1112
- .replace(/"([^"]*)"/g, '$1')
1113
- .replace(/&/g, '-and-')
1114
- .replace(/\|/g, '-or-')
1115
- .replace(/\[\]/g, '-Array')
1116
- .replace(/{|}/g, '_'); // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__
1222
+ .replaceAll('<', '_')
1223
+ .replaceAll('>', '_')
1224
+ .replaceAll(/\s+/g, '')
1225
+ .replaceAll(',', '.')
1226
+ .replaceAll(/'([^']*)'/g, '$1')
1227
+ .replaceAll(/"([^"]*)"/g, '$1')
1228
+ .replaceAll('&', '-and-')
1229
+ .replaceAll('|', '-or-')
1230
+ .replaceAll('[]', '-Array')
1231
+ .replaceAll(/[{}]/g, '_'); // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__
1117
1232
  preformattedName = replaceTypeLiteralPropertySeparators(preformattedName); // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_
1118
- preformattedName = preformattedName.replace(/;/g, '--');
1233
+ preformattedName = preformattedName.replaceAll(';', '--');
1119
1234
  preformattedName = replaceIndexedAccessSegments(preformattedName); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_,
1120
1235
  //Safety fixes to replace all characters which are not accepted by swagger ui
1121
- let formattedName = preformattedName.replace(/[^A-Za-z0-9\-._]/g, match => {
1122
- return `_${match.charCodeAt(0)}_`;
1236
+ let formattedName = preformattedName.replaceAll(/[^A-Za-z0-9\-._]/g, match => {
1237
+ return `_${match.codePointAt(0) ?? 0}_`;
1123
1238
  });
1124
- formattedName = formattedName.replace(/92_r_92_n/g, '92_n'); //Windows uses \r\n, but linux uses \n.
1239
+ formattedName = formattedName.replaceAll('92_r_92_n', '92_n'); //Windows uses \r\n, but linux uses \n.
1125
1240
  return formattedName;
1126
1241
  }
1127
1242
  createCircularDependencyResolver(refName, refTypeName) {
@@ -1154,7 +1269,7 @@ class TypeResolver {
1154
1269
  symbol = fullEnumSymbol?.exports?.get(typeName);
1155
1270
  }
1156
1271
  // Handle built-in types that don't have declarations in user code
1157
- if (!symbol || !symbol.getDeclarations) {
1272
+ if (!symbol?.getDeclarations) {
1158
1273
  return [];
1159
1274
  }
1160
1275
  const declarations = symbol.getDeclarations();
@@ -1174,7 +1289,7 @@ class TypeResolver {
1174
1289
  if (modelTypes.length > 1) {
1175
1290
  // remove types that are from typescript e.g. 'Account'
1176
1291
  modelTypes = modelTypes.filter(modelType => {
1177
- return modelType.getSourceFile().fileName.replace(/\\/g, '/').toLowerCase().indexOf('node_modules/typescript') <= -1;
1292
+ return modelType.getSourceFile().fileName.replaceAll('\\', '/').toLowerCase().indexOf('node_modules/typescript') <= -1;
1178
1293
  });
1179
1294
  modelTypes = this.getDesignatedModels(modelTypes, typeName);
1180
1295
  }
@@ -1203,7 +1318,7 @@ class TypeResolver {
1203
1318
  const symbols = [resolvedType.aliasSymbol, resolvedType.symbol].filter((symbol) => !!symbol);
1204
1319
  const declarations = symbols.flatMap(symbol => this.getUsableDeclarationsFromSymbol(symbol));
1205
1320
  const uniqueDeclarations = declarations.filter((declaration, index, allDeclarations) => {
1206
- return allDeclarations.findIndex(candidate => candidate === declaration) === index;
1321
+ return allDeclarations.indexOf(declaration) === index;
1207
1322
  });
1208
1323
  return uniqueDeclarations.map(declaration => {
1209
1324
  if (ts.isTypeAliasDeclaration(declaration)) {
@@ -1263,23 +1378,20 @@ class TypeResolver {
1263
1378
  }
1264
1379
  typeArgumentsToContext(type, targetEntity) {
1265
1380
  let newContext = {};
1266
- // Handle cases where targetEntity might be an inline object type
1267
- // Inline object types don't have declarations, so we need to handle them differently
1268
- let declarations;
1269
- try {
1270
- declarations = this.getModelTypeDeclarations(targetEntity);
1381
+ // Inline object types don't contribute generic declarations, so they map to an empty context.
1382
+ if (!this.current.typeChecker) {
1383
+ return newContext;
1271
1384
  }
1272
- catch (_) {
1273
- // If we can't get declarations (e.g., inline object type),
1274
- // we can't process type parameters, so return empty context
1385
+ if (!ts.isIdentifier(targetEntity) && !ts.isQualifiedName(targetEntity)) {
1275
1386
  return newContext;
1276
1387
  }
1388
+ const declarations = this.getModelTypeDeclarations(targetEntity);
1277
1389
  const firstDeclaration = declarations[0];
1278
1390
  const typeParameters = firstDeclaration?.typeParameters;
1279
1391
  if (typeParameters) {
1280
1392
  for (let index = 0; index < typeParameters.length; index++) {
1281
1393
  const typeParameter = typeParameters[index];
1282
- const typeArg = type.typeArguments && type.typeArguments[index];
1394
+ const typeArg = type.typeArguments?.[index];
1283
1395
  let resolvedType;
1284
1396
  let name;
1285
1397
  // Argument may be a forward reference from context
@@ -1307,6 +1419,47 @@ class TypeResolver {
1307
1419
  }
1308
1420
  return newContext;
1309
1421
  }
1422
+ getReferenceAliasProperties(referenceType) {
1423
+ let type = referenceType;
1424
+ while (type.dataType === 'refAlias') {
1425
+ type = type.type;
1426
+ }
1427
+ if (type.dataType === 'refObject' || type.dataType === 'nestedObjectLiteral') {
1428
+ return type.properties;
1429
+ }
1430
+ return [];
1431
+ }
1432
+ appendInheritedProperties(properties, referenceType) {
1433
+ if (!referenceType || referenceType.dataType === 'refEnum') {
1434
+ return properties;
1435
+ }
1436
+ if (referenceType.dataType === 'refAlias') {
1437
+ return [...properties, ...this.getReferenceAliasProperties(referenceType)];
1438
+ }
1439
+ if (referenceType.dataType === 'refObject') {
1440
+ return [...properties, ...(referenceType.properties ?? [])];
1441
+ }
1442
+ return (0, runtime_1.assertNever)(referenceType);
1443
+ }
1444
+ getInheritedReferenceType(typeNode) {
1445
+ if (!ts.isIdentifier(typeNode.expression) && !ts.isQualifiedName(typeNode.expression)) {
1446
+ return undefined;
1447
+ }
1448
+ const resetContext = this.context;
1449
+ this.context = this.typeArgumentsToContext(typeNode, typeNode.expression);
1450
+ try {
1451
+ return this.getReferenceType(typeNode, false);
1452
+ }
1453
+ catch (error) {
1454
+ if (error instanceof exceptions_1.GenerateMetadataError) {
1455
+ return undefined;
1456
+ }
1457
+ throw error;
1458
+ }
1459
+ finally {
1460
+ this.context = resetContext;
1461
+ }
1462
+ }
1310
1463
  getModelInheritedProperties(modelTypeDeclaration) {
1311
1464
  let properties = [];
1312
1465
  const heritageClauses = modelTypeDeclaration.heritageClauses;
@@ -1318,47 +1471,7 @@ class TypeResolver {
1318
1471
  continue;
1319
1472
  }
1320
1473
  for (const t of clause.types) {
1321
- const baseEntityName = t.expression;
1322
- // create subContext
1323
- const resetCtx = this.context;
1324
- this.context = this.typeArgumentsToContext(t, baseEntityName);
1325
- let referenceType;
1326
- try {
1327
- referenceType = this.getReferenceType(t, false);
1328
- }
1329
- catch (error) {
1330
- if (error instanceof exceptions_1.GenerateMetadataError) {
1331
- this.context = resetCtx;
1332
- continue;
1333
- }
1334
- throw error;
1335
- }
1336
- if (referenceType) {
1337
- if (referenceType.dataType === 'refEnum') {
1338
- // since it doesn't have properties to iterate over, then we don't do anything with it
1339
- }
1340
- else if (referenceType.dataType === 'refAlias') {
1341
- let type = referenceType;
1342
- while (type.dataType === 'refAlias') {
1343
- type = type.type;
1344
- }
1345
- if (type.dataType === 'refObject') {
1346
- properties = [...properties, ...type.properties];
1347
- }
1348
- else if (type.dataType === 'nestedObjectLiteral') {
1349
- properties = [...properties, ...type.properties];
1350
- }
1351
- }
1352
- else if (referenceType.dataType === 'refObject') {
1353
- ;
1354
- (referenceType.properties || []).forEach(property => properties.push(property));
1355
- }
1356
- else {
1357
- (0, runtime_1.assertNever)(referenceType);
1358
- }
1359
- }
1360
- // reset subContext
1361
- this.context = resetCtx;
1474
+ properties = this.appendInheritedProperties(properties, this.getInheritedReferenceType(t));
1362
1475
  }
1363
1476
  }
1364
1477
  return properties;
@@ -1369,8 +1482,8 @@ class TypeResolver {
1369
1482
  return undefined;
1370
1483
  }
1371
1484
  /**
1372
- * TODO: Workaround for what seems like a bug in the compiler
1373
- * Warrants more investigation and possibly a PR against typescript
1485
+ * Workaround for a TypeScript compiler quirk tracked for follow-up investigation.
1486
+ * See https://github.com/tsoa-next/tsoa-next/issues for related metadata parsing context.
1374
1487
  */
1375
1488
  if (node.kind === ts.SyntaxKind.Parameter) {
1376
1489
  // TypeScript won't parse jsdoc if the flag is 4, i.e. 'Property'
@@ -1391,9 +1504,10 @@ class TypeResolver {
1391
1504
  getPropertyName(prop) {
1392
1505
  if (ts.isComputedPropertyName(prop.name) && ts.isPropertyAccessExpression(prop.name.expression)) {
1393
1506
  const initializerValue = (0, initializer_value_1.getInitializerValue)(prop.name.expression, this.current.typeChecker);
1394
- if (initializerValue) {
1395
- return initializerValue?.toString();
1507
+ if (typeof initializerValue === 'string' || typeof initializerValue === 'number' || typeof initializerValue === 'boolean') {
1508
+ return `${initializerValue}`;
1396
1509
  }
1510
+ return prop.name.expression.getText();
1397
1511
  }
1398
1512
  return prop.name.text;
1399
1513
  }
@@ -1416,66 +1530,123 @@ class TypeResolver {
1416
1530
  }
1417
1531
  static getDefault(node) {
1418
1532
  const defaultStr = (0, jsDocUtils_1.getJSDocComment)(node, 'default');
1419
- if (typeof defaultStr == 'string' && defaultStr !== 'undefined') {
1420
- let textStartCharacter = undefined;
1421
- const inString = () => textStartCharacter !== undefined;
1422
- let formattedStr = '';
1423
- for (let i = 0; i < defaultStr.length; ++i) {
1424
- const actCharacter = defaultStr[i];
1425
- if (inString()) {
1426
- if (actCharacter === textStartCharacter) {
1427
- formattedStr += '"';
1428
- textStartCharacter = undefined;
1429
- }
1430
- else if (actCharacter === '"') {
1431
- formattedStr += '\\"';
1432
- }
1433
- else if (actCharacter === '\\') {
1434
- ++i;
1435
- if (i < defaultStr.length) {
1436
- const nextCharacter = defaultStr[i];
1437
- if (['n', 't', 'r', 'b', 'f', '\\', '"'].includes(nextCharacter)) {
1438
- formattedStr += '\\' + nextCharacter;
1439
- }
1440
- else if (!['v', '0'].includes(nextCharacter)) {
1441
- //\v, \0 characters are not compatible with JSON
1442
- formattedStr += nextCharacter;
1443
- }
1444
- }
1445
- else {
1446
- formattedStr += actCharacter; // this is a bug, but let the JSON parser decide how to handle it
1447
- }
1448
- }
1449
- else {
1450
- formattedStr += actCharacter;
1451
- }
1452
- }
1453
- else {
1454
- if ([`"`, "'", '`'].includes(actCharacter)) {
1455
- textStartCharacter = actCharacter;
1456
- formattedStr += '"';
1457
- }
1458
- else if (actCharacter === '/' && i + 1 < defaultStr.length && defaultStr[i + 1] === '/') {
1459
- i += 2;
1460
- while (i < defaultStr.length && defaultStr[i] !== '\n') {
1461
- ++i;
1462
- }
1463
- }
1464
- else {
1465
- formattedStr += actCharacter;
1466
- }
1467
- }
1468
- }
1469
- try {
1470
- const parsed = JSON.parse(formattedStr);
1471
- return parsed;
1472
- }
1473
- catch (err) {
1474
- const message = err instanceof Error ? err.message : '-';
1475
- throw new exceptions_1.GenerateMetadataError(`JSON could not parse default str: "${defaultStr}", preformatted: "${formattedStr}"\nmessage: "${message}"`);
1476
- }
1533
+ if (typeof defaultStr !== 'string' || defaultStr === 'undefined') {
1534
+ return undefined;
1477
1535
  }
1478
- return undefined;
1536
+ const formattedStr = this.formatDefaultString(defaultStr);
1537
+ try {
1538
+ return JSON.parse(formattedStr);
1539
+ }
1540
+ catch (error) {
1541
+ const message = error instanceof Error ? error.message : '-';
1542
+ throw new exceptions_1.GenerateMetadataError(`JSON could not parse default str: "${defaultStr}", preformatted: "${formattedStr}"\nmessage: "${message}"`);
1543
+ }
1544
+ }
1545
+ static formatDefaultString(defaultStr) {
1546
+ const initialState = { formatted: '' };
1547
+ let state = initialState;
1548
+ let index = 0;
1549
+ while (index < defaultStr.length) {
1550
+ const formattedCharacter = this.formatDefaultCharacter(defaultStr, index, state);
1551
+ state = {
1552
+ formatted: formattedCharacter.formatted,
1553
+ textStartCharacter: formattedCharacter.textStartCharacter,
1554
+ };
1555
+ index = formattedCharacter.index + 1;
1556
+ }
1557
+ return state.formatted;
1558
+ }
1559
+ static formatDefaultCharacter(defaultStr, index, state) {
1560
+ const character = defaultStr[index];
1561
+ if (state.textStartCharacter !== undefined) {
1562
+ return this.formatDefaultStringCharacter(defaultStr, index, state, character);
1563
+ }
1564
+ return this.formatDefaultNonStringCharacter(defaultStr, index, state, character);
1565
+ }
1566
+ static formatDefaultStringCharacter(defaultStr, index, state, character) {
1567
+ if (character === state.textStartCharacter) {
1568
+ return {
1569
+ formatted: `${state.formatted}"`,
1570
+ index,
1571
+ };
1572
+ }
1573
+ if (character === '"') {
1574
+ return {
1575
+ ...state,
1576
+ formatted: `${state.formatted}${escapedDoubleQuote}`,
1577
+ index,
1578
+ };
1579
+ }
1580
+ if (character !== backslash) {
1581
+ return {
1582
+ ...state,
1583
+ formatted: `${state.formatted}${character}`,
1584
+ index,
1585
+ };
1586
+ }
1587
+ return this.formatEscapedDefaultCharacter(defaultStr, index, state);
1588
+ }
1589
+ static formatEscapedDefaultCharacter(defaultStr, index, state) {
1590
+ const nextIndex = index + 1;
1591
+ if (nextIndex >= defaultStr.length) {
1592
+ return {
1593
+ ...state,
1594
+ formatted: `${state.formatted}${backslash}`,
1595
+ index,
1596
+ };
1597
+ }
1598
+ const nextCharacter = defaultStr[nextIndex];
1599
+ if (['n', 't', 'r', 'b', 'f', backslash, '"'].includes(nextCharacter)) {
1600
+ return {
1601
+ ...state,
1602
+ formatted: `${state.formatted}${backslash}${nextCharacter}`,
1603
+ index: nextIndex,
1604
+ };
1605
+ }
1606
+ if (!['v', '0'].includes(nextCharacter)) {
1607
+ return {
1608
+ ...state,
1609
+ formatted: `${state.formatted}${nextCharacter}`,
1610
+ index: nextIndex,
1611
+ };
1612
+ }
1613
+ return {
1614
+ ...state,
1615
+ index: nextIndex,
1616
+ };
1617
+ }
1618
+ static formatDefaultNonStringCharacter(defaultStr, index, state, character) {
1619
+ if (this.isDefaultStringDelimiter(character)) {
1620
+ return {
1621
+ formatted: `${state.formatted}"`,
1622
+ textStartCharacter: character,
1623
+ index,
1624
+ };
1625
+ }
1626
+ if (this.startsDefaultLineComment(defaultStr, index)) {
1627
+ return this.skipDefaultLineComment(defaultStr, index, state);
1628
+ }
1629
+ return {
1630
+ ...state,
1631
+ formatted: `${state.formatted}${character}`,
1632
+ index,
1633
+ };
1634
+ }
1635
+ static isDefaultStringDelimiter(character) {
1636
+ return character === '"' || character === "'" || character === '`';
1637
+ }
1638
+ static startsDefaultLineComment(defaultStr, index) {
1639
+ return defaultStr[index] === '/' && defaultStr[index + 1] === '/';
1640
+ }
1641
+ static skipDefaultLineComment(defaultStr, index, state) {
1642
+ let nextIndex = index + 2;
1643
+ while (nextIndex < defaultStr.length && defaultStr[nextIndex] !== '\n') {
1644
+ nextIndex += 1;
1645
+ }
1646
+ return {
1647
+ ...state,
1648
+ index: nextIndex,
1649
+ };
1479
1650
  }
1480
1651
  }
1481
1652
  exports.TypeResolver = TypeResolver;