@tsoa-next/cli 8.0.1-dev.43.63e57e36 → 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.
@@ -51,6 +51,8 @@ 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;
55
57
  const objectHasOwn = Object.hasOwn;
56
58
  const getSyntheticOrigin = (symbol) => {
@@ -71,6 +73,36 @@ const isRefTypeTokenCharacter = (char) => {
71
73
  const code = char.codePointAt(0) ?? -1;
72
74
  return isAsciiLetter(char) || (code >= 48 && code <= 57) || char === '_';
73
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
+ };
74
106
  const replaceTypeLiteralPropertySeparators = (value) => {
75
107
  let formatted = '';
76
108
  let index = 0;
@@ -80,29 +112,15 @@ const replaceTypeLiteralPropertySeparators = (value) => {
80
112
  index += 1;
81
113
  continue;
82
114
  }
83
- const tokenStart = index;
84
- while (index < value.length && isRefTypeTokenCharacter(value[index])) {
85
- index += 1;
86
- }
87
- if (value[index] === '?') {
88
- index += 1;
89
- }
90
- const token = value.slice(tokenStart, index);
91
- if (value[index] === ':') {
92
- const typeStart = index + 1;
93
- let typeEnd = typeStart;
94
- while (typeEnd < value.length && isAsciiLetter(value[typeEnd])) {
95
- typeEnd += 1;
96
- }
97
- if (typeEnd > typeStart) {
98
- formatted += token;
99
- formatted += '-';
100
- formatted += value.slice(typeStart, typeEnd);
101
- index = typeEnd;
102
- continue;
103
- }
104
- }
115
+ const { nextIndex, token } = readRefTypeToken(value, index);
105
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;
106
124
  }
107
125
  return formatted;
108
126
  };
@@ -157,362 +175,300 @@ class TypeResolver {
157
175
  });
158
176
  }
159
177
  resolve() {
160
- const partentJsDocTagNames = this.parentNode ? (0, jsDocUtils_1.getJSDocTagNames)(this.parentNode) : undefined;
161
- 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);
162
180
  if (primitiveType) {
163
181
  return primitiveType;
164
182
  }
165
- if (ts.isArrayTypeNode(this.typeNode)) {
166
- const arrayMetaType = {
167
- dataType: 'array',
168
- elementType: new TypeResolver(this.typeNode.elementType, this.current, this.parentNode, this.context).resolve(),
169
- };
170
- return arrayMetaType;
183
+ const nonReferenceType = this.resolveNonReferenceTypeNode();
184
+ if (nonReferenceType) {
185
+ return nonReferenceType;
171
186
  }
172
- if (ts.isRestTypeNode(this.typeNode)) {
173
- 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;
174
210
  }
175
- if (ts.isUnionTypeNode(this.typeNode)) {
176
- const types = this.typeNode.types.map(type => {
177
- return new TypeResolver(type, this.current, this.parentNode, this.context).resolve();
178
- });
179
- const unionMetaType = {
180
- dataType: 'union',
181
- types,
182
- };
183
- 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;
184
219
  }
185
- if (ts.isIntersectionTypeNode(this.typeNode)) {
186
- const types = this.typeNode.types
187
- .filter(type => !this.isIoTsBrandMarker(type, this.current.typeChecker))
188
- .map(type => {
189
- return new TypeResolver(type, this.current, this.parentNode, this.context).resolve();
190
- });
191
- const intersectionMetaType = {
192
- dataType: 'intersection',
193
- types,
194
- };
195
- return intersectionMetaType;
196
- }
197
- if (ts.isTupleTypeNode(this.typeNode)) {
198
- const elementTypes = [];
199
- let restType;
200
- for (const element of this.typeNode.elements) {
201
- if (ts.isRestTypeNode(element)) {
202
- const resolvedRest = new TypeResolver(element.type, this.current, element, this.context).resolve();
203
- if (resolvedRest.dataType === 'array') {
204
- restType = resolvedRest.elementType;
205
- }
206
- else {
207
- restType = resolvedRest;
208
- }
209
- }
210
- else {
211
- const typeNode = ts.isNamedTupleMember(element) ? element.type : element;
212
- const type = new TypeResolver(typeNode, this.current, element, this.context).resolve();
213
- elementTypes.push(type);
214
- }
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;
215
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)) {
216
348
  return {
217
- dataType: 'tuple',
218
- types: elementTypes,
219
- ...(restType ? { restType } : {}),
349
+ dataType: 'union',
350
+ types: type.types.map(unionType => this.resolveMappedType(unionType, mappedTypeNode)),
220
351
  };
221
352
  }
222
- if (this.typeNode.kind === ts.SyntaxKind.AnyKeyword || this.typeNode.kind === ts.SyntaxKind.UnknownKeyword) {
223
- const literallyAny = {
224
- dataType: 'any',
225
- };
226
- return literallyAny;
353
+ if (this.hasFlag(type, ts.TypeFlags.Undefined)) {
354
+ return { dataType: 'undefined' };
227
355
  }
228
- if (ts.isLiteralTypeNode(this.typeNode)) {
229
- const enumType = {
356
+ if (this.hasFlag(type, ts.TypeFlags.Null)) {
357
+ return {
230
358
  dataType: 'enum',
231
- enums: [this.getLiteralValue(this.typeNode)],
232
- };
233
- return enumType;
234
- }
235
- if (ts.isTypeLiteralNode(this.typeNode)) {
236
- const properties = this.typeNode.members.filter(ts.isPropertySignature).reduce((res, propertySignature) => {
237
- const type = new TypeResolver(propertySignature.type, this.current, propertySignature, this.context).resolve();
238
- const def = TypeResolver.getDefault(propertySignature);
239
- const property = {
240
- example: this.getNodeExample(propertySignature),
241
- default: def,
242
- description: this.getNodeDescription(propertySignature),
243
- format: this.getNodeFormat(propertySignature),
244
- name: this.getPropertyName(propertySignature),
245
- required: !propertySignature.questionToken,
246
- type,
247
- validators: (0, validatorUtils_1.getPropertyValidators)(propertySignature) || {},
248
- deprecated: (0, jsDocUtils_1.isExistJSDocTag)(propertySignature, tag => tag.tagName.text === 'deprecated'),
249
- title: this.getNodeTitle(propertySignature),
250
- extensions: this.getNodeExtension(propertySignature),
251
- };
252
- return [property, ...res];
253
- }, []);
254
- const indexMember = this.typeNode.members.find(member => ts.isIndexSignatureDeclaration(member));
255
- let additionalType;
256
- if (indexMember) {
257
- /* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
258
- const indexSignatureDeclaration = indexMember;
259
- const indexType = new TypeResolver(indexSignatureDeclaration.parameters[0].type, this.current, this.parentNode, this.context).resolve();
260
- (0, flowUtils_1.throwUnless)(indexType.dataType === 'string', new exceptions_1.GenerateMetadataError(`Only string indexers are supported.`, this.typeNode));
261
- additionalType = new TypeResolver(indexSignatureDeclaration.type, this.current, this.parentNode, this.context).resolve();
262
- }
263
- const objLiteral = {
264
- additionalProperties: indexMember && additionalType,
265
- dataType: 'nestedObjectLiteral',
266
- properties,
359
+ enums: [null],
267
360
  };
268
- return objLiteral;
269
361
  }
270
- if (this.typeNode.kind === ts.SyntaxKind.ObjectKeyword) {
271
- return { dataType: 'object' };
362
+ if (this.hasFlag(type, ts.TypeFlags.Object)) {
363
+ return this.resolveMappedObjectType(type, mappedTypeNode);
272
364
  }
273
- if (ts.isMappedTypeNode(this.typeNode)) {
274
- const mappedTypeNode = this.typeNode;
275
- const getOneOrigDeclaration = (prop) => {
276
- const declaration = prop.declarations?.[0];
277
- if (declaration) {
278
- return declaration;
279
- }
280
- const syntheticOrigin = getSyntheticOrigin(prop);
281
- if (syntheticOrigin && syntheticOrigin.name === prop.name) {
282
- //Otherwise losts jsDoc like in intellisense
283
- return syntheticOrigin.declarations?.[0];
284
- }
285
- return undefined;
286
- };
287
- const isIgnored = (prop) => {
288
- const declaration = getOneOrigDeclaration(prop);
289
- return (declaration !== undefined &&
290
- ((0, jsDocUtils_1.getJSDocTagNames)(declaration).some(tag => tag === 'ignore') || (!ts.isPropertyDeclaration(declaration) && !ts.isPropertySignature(declaration) && !ts.isParameter(declaration))));
291
- };
292
- const calcMappedType = (type) => {
293
- if (this.hasFlag(type, ts.TypeFlags.Union)) {
294
- //Intersections are not interesting somehow...
295
- const types = type.types;
296
- const resolvedTypes = types.map(calcMappedType);
297
- return {
298
- dataType: 'union',
299
- types: resolvedTypes,
300
- };
301
- }
302
- else if (this.hasFlag(type, ts.TypeFlags.Undefined)) {
303
- return {
304
- dataType: 'undefined',
305
- };
306
- }
307
- else if (this.hasFlag(type, ts.TypeFlags.Null)) {
308
- return {
309
- dataType: 'enum',
310
- enums: [null],
311
- };
312
- }
313
- else if (this.hasFlag(type, ts.TypeFlags.Object)) {
314
- const typeProperties = type.getProperties();
315
- const properties = typeProperties
316
- // Ignore methods, getter, setter and @ignored props
317
- .filter(property => isIgnored(property) === false)
318
- // Transform to property
319
- .map(property => {
320
- const propertyType = this.current.typeChecker.getTypeOfSymbolAtLocation(property, this.typeNode);
321
- const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation);
322
- const parent = getOneOrigDeclaration(property); //If there are more declarations, we need to get one of them, from where we want to recognize jsDoc
323
- const type = new TypeResolver(typeNode, this.current, parent, this.context, propertyType).resolve();
324
- const required = !this.hasFlag(property, ts.SymbolFlags.Optional);
325
- const comments = property.getDocumentationComment(this.current.typeChecker);
326
- const description = comments.length ? ts.displayPartsToString(comments) : undefined;
327
- const initializer = parent && hasInitializer(parent) ? parent.initializer : undefined;
328
- let def;
329
- if (initializer) {
330
- def = (0, initializer_value_1.getInitializerValue)(initializer, this.current.typeChecker);
331
- }
332
- else if (parent) {
333
- def = TypeResolver.getDefault(parent);
334
- }
335
- // Push property
336
- return {
337
- name: property.getName(),
338
- required,
339
- deprecated: parent
340
- ? (0, jsDocUtils_1.isExistJSDocTag)(parent, tag => tag.tagName.text === 'deprecated') || (0, decoratorUtils_1.isDecorator)(parent, (_identifier, canonicalName) => canonicalName === 'Deprecated', this.current.typeChecker)
341
- : false,
342
- type,
343
- default: def,
344
- // validators are disjunct via types, so it is now OK.
345
- // if a type not changes while mapping, we need validators
346
- // if a type changes, then the validators will be not relevant
347
- validators: (parent ? (0, validatorUtils_1.getPropertyValidators)(parent) : {}) || {},
348
- description,
349
- format: parent ? this.getNodeFormat(parent) : undefined,
350
- example: parent ? this.getNodeExample(parent) : undefined,
351
- extensions: parent ? this.getNodeExtension(parent) : undefined,
352
- };
353
- });
354
- const objectLiteral = {
355
- dataType: 'nestedObjectLiteral',
356
- properties,
357
- };
358
- const indexInfos = this.current.typeChecker.getIndexInfosOfType(type);
359
- const indexTypes = indexInfos.flatMap(indexInfo => {
360
- const typeNode = this.current.typeChecker.typeToTypeNode(indexInfo.type, undefined, ts.NodeBuilderFlags.NoTruncation);
361
- if (typeNode.kind === ts.SyntaxKind.NeverKeyword) {
362
- // { [k: string]: never; }
363
- return [];
364
- }
365
- const type = new TypeResolver(typeNode, this.current, mappedTypeNode, this.context, indexInfo.type).resolve();
366
- return [type];
367
- });
368
- if (indexTypes.length) {
369
- if (indexTypes.length === 1) {
370
- objectLiteral.additionalProperties = indexTypes[0];
371
- }
372
- else {
373
- // { [k: string]: string; } & { [k: number]: number; }
374
- // A | B is sometimes A type or B type, sometimes optionally accepts both A & B members.
375
- // Most people & TSOA thinks that A | B can be only A or only B.
376
- // So we can accept this merge
377
- //Every additional property key assumed as string
378
- objectLiteral.additionalProperties = {
379
- dataType: 'union',
380
- types: indexTypes,
381
- };
382
- }
383
- }
384
- return objectLiteral;
385
- }
386
- // Known issues & easy to implement: Partial<string>, Partial<never>, ... But I think a programmer not writes types like this
387
- throw new exceptions_1.GenerateMetadataError(`Unhandled mapped type has found, flags: ${type.flags}`, this.typeNode);
388
- };
389
- const referencer = this.getReferencer();
390
- const result = calcMappedType(referencer);
391
- return result;
392
- }
393
- if (ts.isConditionalTypeNode(this.typeNode)) {
394
- const referencer = this.getReferencer();
395
- const resolvedNode = this.current.typeChecker.typeToTypeNode(referencer, undefined, ts.NodeBuilderFlags.NoTruncation);
396
- return new TypeResolver(resolvedNode, this.current, this.typeNode, this.context, referencer).resolve();
397
- }
398
- // keyof & readonly arrays
399
- if (ts.isTypeOperatorNode(this.typeNode)) {
400
- return this.resolveTypeOperatorNode(this.typeNode, this.current.typeChecker, this.current, this.context, this.parentNode, this.referencer);
401
- }
402
- // Indexed type
403
- if (ts.isIndexedAccessTypeNode(this.typeNode)) {
404
- return this.resolveIndexedAccessTypeNode(this.typeNode, this.current.typeChecker, this.current, this.context);
405
- }
406
- if (ts.isTemplateLiteralTypeNode(this.typeNode)) {
407
- const type = this.getReferencer();
408
- (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));
409
- // `a${'c' | 'd'}b`
410
- const stringLiteralEnum = {
411
- dataType: 'enum',
412
- 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,
413
385
  };
414
- return stringLiteralEnum;
415
386
  }
416
- if (ts.isParenthesizedTypeNode(this.typeNode)) {
417
- 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;
410
+ }
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);
418
419
  }
419
- (0, flowUtils_1.throwUnless)(this.typeNode.kind === ts.SyntaxKind.TypeReference, new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[this.typeNode.kind]}`, this.typeNode));
420
- return this.resolveTypeReferenceNode(this.typeNode, this.current, this.context, this.parentNode);
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();
421
467
  }
422
468
  resolveTypeOperatorNode(typeNode, typeChecker, current, context, parentNode, referencer) {
423
469
  switch (typeNode.operator) {
424
470
  case ts.SyntaxKind.KeyOfKeyword: {
425
- // keyof
426
- const type = typeChecker.getTypeFromTypeNode(typeNode);
427
- if (type.isIndexType()) {
428
- // in case of generic: keyof T. Not handles all possible cases
429
- const symbol = type.type.getSymbol();
430
- if (symbol && symbol.getFlags() & ts.TypeFlags.TypeParameter) {
431
- const typeName = symbol.getEscapedName();
432
- (0, flowUtils_1.throwUnless)(typeof typeName === 'string', new exceptions_1.GenerateMetadataError(`typeName is not string, but ${typeof typeName}`, typeNode));
433
- if (context[typeName]) {
434
- const subResult = new TypeResolver(context[typeName].type, current, parentNode, context).resolve();
435
- if (subResult.dataType === 'any') {
436
- return {
437
- dataType: 'union',
438
- types: [{ dataType: 'string' }, { dataType: 'double' }],
439
- };
440
- }
441
- const properties = subResult.properties?.map(v => v.name);
442
- (0, flowUtils_1.throwUnless)(properties, new exceptions_1.GenerateMetadataError(`TypeOperator 'keyof' on node which have no properties`, context[typeName].type));
443
- return {
444
- dataType: 'enum',
445
- enums: properties,
446
- };
447
- }
448
- }
449
- }
450
- else if (type.isUnion()) {
451
- const literals = type.types.filter((t) => t.isLiteral());
452
- const literalValues = [];
453
- for (const literal of literals) {
454
- (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));
455
- literalValues.push(literal.value);
456
- }
457
- if (!literals.length) {
458
- const length = type.types.length;
459
- const someStringFlag = type.types.some(t => t.flags === ts.TypeFlags.String);
460
- const someNumberFlag = type.types.some(t => t.flags === ts.TypeFlags.Number);
461
- const someSymbolFlag = type.types.some(t => t.flags === ts.TypeFlags.ESSymbol);
462
- if (someStringFlag && someNumberFlag) {
463
- if (length === 2 || (length === 3 && someSymbolFlag)) {
464
- return {
465
- dataType: 'union',
466
- types: [{ dataType: 'string' }, { dataType: 'double' }],
467
- };
468
- }
469
- }
470
- }
471
- // Warn on nonsense (`number`, `typeof Symbol.iterator`)
472
- if (type.types.some(t => !t.isLiteral())) {
473
- const problems = type.types.filter(t => !t.isLiteral()).map(t => typeChecker.typeToString(t));
474
- console.warn(new exceptions_1.GenerateMetaDataWarning(`Skipped non-literal type(s) ${problems.join(', ')}`, typeNode).toString());
475
- }
476
- const stringMembers = literalValues.filter(v => typeof v == 'string');
477
- const numberMembers = literalValues.filter(v => typeof v == 'number');
478
- if (stringMembers.length && numberMembers.length) {
479
- return {
480
- dataType: 'union',
481
- types: [
482
- { dataType: 'enum', enums: stringMembers },
483
- { dataType: 'enum', enums: numberMembers },
484
- ],
485
- };
486
- }
487
- return {
488
- dataType: 'enum',
489
- enums: literalValues,
490
- };
491
- }
492
- else if (type.isLiteral()) {
493
- (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));
494
- return {
495
- dataType: 'enum',
496
- enums: [type.value],
497
- };
498
- }
499
- else if (this.hasFlag(type, ts.TypeFlags.Never)) {
500
- throw new exceptions_1.GenerateMetadataError(`TypeOperator 'keyof' on node produced a never type`, typeNode);
501
- }
502
- else if (this.hasFlag(type, ts.TypeFlags.TemplateLiteral)) {
503
- //Now assumes template literals as string
504
- console.warn(new exceptions_1.GenerateMetaDataWarning(`Template literals are assumed as strings`, typeNode).toString());
505
- return {
506
- dataType: 'string',
507
- };
508
- }
509
- else if (this.hasFlag(type, ts.TypeFlags.Number)) {
510
- return {
511
- dataType: 'double',
512
- };
513
- }
514
- const indexedTypeName = typeChecker.typeToString(typeChecker.getTypeFromTypeNode(typeNode.type));
515
- throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${indexedTypeName}`, typeNode);
471
+ return this.resolveKeyOfTypeOperator(typeNode, typeChecker, current, context, parentNode);
516
472
  }
517
473
  case ts.SyntaxKind.ReadonlyKeyword:
518
474
  // Handle `readonly` arrays
@@ -521,46 +477,176 @@ class TypeResolver {
521
477
  throw new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[typeNode.kind]}`, typeNode);
522
478
  }
523
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
+ }
524
595
  resolveIndexedAccessTypeNode(typeNode, typeChecker, current, context) {
525
596
  const { indexType, objectType } = typeNode;
526
597
  if ([ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.StringKeyword].includes(indexType.kind)) {
527
- // Indexed by keyword
528
- const isNumberIndexType = indexType.kind === ts.SyntaxKind.NumberKeyword;
529
- const typeOfObjectType = typeChecker.getTypeFromTypeNode(objectType);
530
- const type = isNumberIndexType ? typeOfObjectType.getNumberIndexType() : typeOfObjectType.getStringIndexType();
531
- (0, flowUtils_1.throwUnless)(type, new exceptions_1.GenerateMetadataError(`Could not determine ${isNumberIndexType ? 'number' : 'string'} index on ${typeChecker.typeToString(typeOfObjectType)}`, typeNode));
532
- return new TypeResolver(typeChecker.typeToTypeNode(type, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
533
- }
534
- else if (ts.isLiteralTypeNode(indexType) && (ts.isStringLiteral(indexType.literal) || ts.isNumericLiteral(indexType.literal))) {
535
- // Indexed by literal
536
- const hasType = (node) => node !== undefined && objectHasOwn(node, 'type');
537
- const symbol = typeChecker.getPropertyOfType(typeChecker.getTypeFromTypeNode(objectType), indexType.literal.text);
538
- (0, flowUtils_1.throwUnless)(symbol, new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeChecker.typeToString(typeChecker.getTypeFromTypeNode(objectType))}`, typeNode));
539
- if (hasType(symbol.valueDeclaration) && symbol.valueDeclaration.type) {
540
- return new TypeResolver(symbol.valueDeclaration.type, current, typeNode, context).resolve();
541
- }
542
- const declaration = typeChecker.getTypeOfSymbolAtLocation(symbol, objectType);
543
- try {
544
- return new TypeResolver(typeChecker.typeToTypeNode(declaration, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
545
- }
546
- catch {
547
- throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeChecker.typeToString(typeChecker.getTypeFromTypeNode(typeChecker.typeToTypeNode(declaration, undefined, ts.NodeBuilderFlags.NoTruncation)))}`, typeNode);
548
- }
598
+ return this.resolveIndexedAccessKeywordType(typeNode, typeChecker, current, context, objectType, indexType);
599
+ }
600
+ if (ts.isLiteralTypeNode(indexType) && (ts.isStringLiteral(indexType.literal) || ts.isNumericLiteral(indexType.literal))) {
601
+ return this.resolveIndexedAccessLiteralType(typeNode, typeChecker, current, context, objectType, indexType);
549
602
  }
550
- else if (ts.isTypeOperatorNode(indexType) && indexType.operator === ts.SyntaxKind.KeyOfKeyword) {
551
- // Indexed by keyof typeof value
552
- const typeOfObjectType = ts.isParenthesizedTypeNode(objectType) ? objectType.type : objectType;
553
- const { type: typeOfIndexType } = indexType;
554
- const isSameTypeQuery = ts.isTypeQueryNode(typeOfObjectType) && ts.isTypeQueryNode(typeOfIndexType) && typeOfObjectType.exprName.getText() === typeOfIndexType.exprName.getText();
555
- const isSameTypeReference = ts.isTypeReferenceNode(typeOfObjectType) && ts.isTypeReferenceNode(typeOfIndexType) && typeOfObjectType.typeName.getText() === typeOfIndexType.typeName.getText();
556
- if (isSameTypeQuery || isSameTypeReference) {
557
- const type = this.getReferencer();
558
- const node = typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation);
559
- return new TypeResolver(node, current, typeNode, context, this.referencer).resolve();
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;
560
607
  }
561
608
  }
562
609
  throw new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[typeNode.kind]}`, typeNode);
563
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
+ }
564
650
  resolveTypeReferenceNode(typeNode, current, context, parentNode) {
565
651
  const { typeName } = typeNode;
566
652
  const resolvedTypeArguments = typeNode.typeArguments ? [...typeNode.typeArguments] : undefined;
@@ -772,92 +858,83 @@ class TypeResolver {
772
858
  }
773
859
  //Generates type name for type references
774
860
  calcRefTypeName(type) {
775
- const getEntityName = (type) => {
776
- if (ts.isIdentifier(type)) {
777
- return type.text;
778
- }
779
- return `${getEntityName(type.left)}.${type.right.text}`;
780
- };
781
- let name = getEntityName(type);
782
- if (this.context[name]) {
783
- //resolve name only interesting if entity is not qualifiedName
784
- 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;
785
879
  }
786
- else {
787
- const declarations = this.getModelTypeDeclarations(type);
788
- // Handle cases where declarations is empty (e.g., inline object types in generics)
789
- if (!declarations || declarations.length === 0) {
790
- // Check if this is a simple identifier (like Date, String, etc.)
791
- if (ts.isIdentifier(type)) {
792
- // For simple identifiers, just return the name
793
- return type.text;
794
- }
795
- // For inline object types, we should use the resolve method to get the proper type
796
- // This will handle TypeLiteralNode, UnionTypeNode, etc. properly
797
- // Note: We need to cast to TypeNode since EntityName can be Identifier or QualifiedName
798
- const typeNode = type;
799
- const resolvedType = new TypeResolver(typeNode, this.current, this.parentNode, this.context).resolve();
800
- // Generate a deterministic name for this inline type based on its structure
801
- const typeName = this.calcTypeName(typeNode);
802
- const sanitizedName = typeName
803
- .replace(/[^A-Za-z0-9]/g, '_')
804
- .replace(/_+/g, '_')
805
- .replace(/^_|_$/g, '');
806
- const uniqueName = `Inline_${sanitizedName}`;
807
- // Add to reference types so it can be properly serialized
808
- // We need to create a proper ReferenceType object
809
- const referenceType = {
810
- dataType: 'refAlias',
811
- refName: uniqueName,
812
- type: resolvedType,
813
- validators: {},
814
- deprecated: false,
815
- };
816
- this.current.AddReferenceType(referenceType);
817
- return uniqueName;
818
- }
819
- //Two possible solutions for recognizing different types:
820
- // - Add declaration positions into type names (In an order).
821
- // - It accepts multiple types with same name, if the code compiles, there would be no conflicts in the type names
822
- // - Clear namespaces from type names.
823
- // - Horrible changes can be in the routes.ts in case of teamwork,
824
- // because source files have paths in the computer where data generation runs.
825
- // - Use fully namespaced names
826
- // - Conflicts can be recognized because of the declarations
827
- //
828
- // The second was implemented, it not changes the usual type name formats.
829
- const oneDeclaration = declarations[0]; //Every declarations should be in the same namespace hierarchy
830
- if (oneDeclaration && ts.isEnumMember(oneDeclaration)) {
831
- name = `${oneDeclaration.parent.name.getText()}.${oneDeclaration.name.getText()}`;
832
- }
833
- else {
834
- 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;
835
914
  }
836
- let actNode = oneDeclaration.parent;
837
- let isFirst = true;
838
- const isGlobalDeclaration = (mod) => mod.name.kind === ts.SyntaxKind.Identifier && mod.name.text === 'global';
839
- while (!ts.isSourceFile(actNode)) {
840
- if (ts.isBlock(actNode)) {
841
- break;
842
- }
843
- if (!(isFirst && ts.isEnumDeclaration(actNode)) && !ts.isModuleBlock(actNode)) {
844
- (0, flowUtils_1.throwUnless)(ts.isModuleDeclaration(actNode), new exceptions_1.GenerateMetadataError(`This node kind is unknown: ${actNode.kind}`, type));
845
- if (!isGlobalDeclaration(actNode)) {
846
- const moduleName = actNode.name.text;
847
- name = `${moduleName}.${name}`;
848
- }
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}`;
849
919
  }
850
- isFirst = false;
851
- actNode = actNode.parent;
852
920
  }
853
- const declarationPositions = declarations.map(declaration => ({
854
- fileName: declaration.getSourceFile().fileName,
855
- pos: declaration.pos,
856
- }));
857
- this.current.CheckModelUnicity(name, declarationPositions);
921
+ isFirst = false;
922
+ currentNode = currentNode.parent;
858
923
  }
859
924
  return name;
860
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
+ }
861
938
  calcMemberJsDocProperties(arg) {
862
939
  const def = TypeResolver.getDefault(arg);
863
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);
@@ -874,7 +951,7 @@ class TypeResolver {
874
951
  description,
875
952
  validators: validators && Object.keys(validators).length ? validators : undefined,
876
953
  format,
877
- example: example !== undefined ? example : undefined,
954
+ example,
878
955
  extensions: extensions.length ? extensions : undefined,
879
956
  deprecated: isDeprecated ? true : undefined,
880
957
  ignored: isIgnored ? true : undefined,
@@ -892,103 +969,131 @@ class TypeResolver {
892
969
  }
893
970
  //Generates type name for type references
894
971
  calcTypeName(arg) {
895
- if (ts.isLiteralTypeNode(arg)) {
896
- const literalValue = this.getLiteralValue(arg);
897
- if (typeof literalValue == 'string') {
898
- return `'${literalValue}'`;
899
- }
900
- if (literalValue === null) {
901
- return 'null';
902
- }
903
- if (typeof literalValue === 'boolean') {
904
- return literalValue === true ? 'true' : 'false';
905
- }
906
- return `${literalValue}`;
972
+ const literalTypeName = this.getLiteralTypeName(arg);
973
+ if (literalTypeName) {
974
+ return literalTypeName;
907
975
  }
908
976
  const resolvedType = primitiveTransformer_1.PrimitiveTransformer.resolveKindToPrimitive(arg.kind);
909
977
  if (resolvedType) {
910
978
  return resolvedType;
911
979
  }
912
- if (ts.isTypeReferenceNode(arg) || ts.isExpressionWithTypeArguments(arg)) {
913
- return this.calcTypeReferenceTypeName(arg)[1];
980
+ const structuralTypeName = this.getStructuralTypeName(arg);
981
+ if (structuralTypeName) {
982
+ return structuralTypeName;
914
983
  }
915
- else if (ts.isTypeLiteralNode(arg)) {
916
- const members = arg.members.map(member => {
917
- if (ts.isPropertySignature(member)) {
918
- const name = member.name.text;
919
- const typeText = this.calcTypeName(member.type);
920
- return `"${name}"${member.questionToken ? '?' : ''}${this.calcMemberJsDocProperties(member)}: ${typeText}`;
921
- }
922
- else if (ts.isIndexSignatureDeclaration(member)) {
923
- (0, flowUtils_1.throwUnless)(member.parameters.length === 1, new exceptions_1.GenerateMetadataError(`Index signature parameters length != 1`, member));
924
- const indexType = member.parameters[0];
925
- (0, flowUtils_1.throwUnless)(
926
- // now we can't reach this part of code
927
- ts.isParameter(indexType), new exceptions_1.GenerateMetadataError(`indexSignature declaration parameter kind is not SyntaxKind.Parameter`, indexType));
928
- (0, flowUtils_1.throwUnless)(!indexType.questionToken, new exceptions_1.GenerateMetadataError(`Question token has found for an indexSignature declaration`, indexType));
929
- const typeText = this.calcTypeName(member.type);
930
- const indexName = indexType.name.text;
931
- const indexTypeText = this.calcTypeName(indexType.type);
932
- return `["${indexName}": ${indexTypeText}]: ${typeText}`;
933
- }
934
- throw new exceptions_1.GenerateMetadataError(`Unhandled member kind has found: ${member.kind}`, member);
935
- });
936
- 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;
937
990
  }
938
- else if (ts.isArrayTypeNode(arg)) {
939
- const typeName = this.calcTypeName(arg.elementType);
940
- return `${typeName}[]`;
991
+ const literalValue = this.getLiteralValue(arg);
992
+ if (typeof literalValue === 'string') {
993
+ return `'${literalValue}'`;
941
994
  }
942
- else if (ts.isIntersectionTypeNode(arg)) {
943
- const memberTypeNames = arg.types.map(type => this.calcTypeName(type));
944
- return memberTypeNames.join(' & ');
995
+ if (literalValue === null) {
996
+ return 'null';
945
997
  }
946
- else if (ts.isUnionTypeNode(arg)) {
947
- const memberTypeNames = arg.types.map(type => this.calcTypeName(type));
948
- return memberTypeNames.join(' | ');
998
+ if (typeof literalValue === 'boolean') {
999
+ return literalValue ? 'true' : 'false';
949
1000
  }
950
- else if (ts.isTypeOperatorNode(arg)) {
951
- const subTypeName = this.calcTypeName(arg.type);
952
- if (arg.operator === ts.SyntaxKind.KeyOfKeyword) {
953
- return `keyof ${subTypeName}`;
954
- }
955
- else if (arg.operator === ts.SyntaxKind.ReadonlyKeyword) {
956
- return `readonly ${subTypeName}`;
957
- }
958
- 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;
959
1019
  }
960
- else if (ts.isTypeQueryNode(arg)) {
961
- const subTypeName = this.calcRefTypeName(arg.exprName);
962
- return `typeof ${subTypeName}`;
1020
+ return this.calcTypeReferenceTypeName(arg)[1];
1021
+ }
1022
+ getTypeLiteralName(arg) {
1023
+ if (!ts.isTypeLiteralNode(arg)) {
1024
+ return undefined;
963
1025
  }
964
- else if (ts.isIndexedAccessTypeNode(arg)) {
965
- const objectTypeName = this.calcTypeName(arg.objectType);
966
- const indexTypeName = this.calcTypeName(arg.indexType);
967
- 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}`;
968
1033
  }
969
- else if (arg.kind === ts.SyntaxKind.UnknownKeyword) {
970
- return 'unknown';
1034
+ if (ts.isIndexSignatureDeclaration(member)) {
1035
+ return this.getIndexSignatureTypeName(member);
971
1036
  }
972
- else if (arg.kind === ts.SyntaxKind.AnyKeyword) {
973
- 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;
974
1060
  }
975
- else if (arg.kind === ts.SyntaxKind.NeverKeyword) {
976
- return 'never';
1061
+ const subTypeName = this.calcTypeName(arg.type);
1062
+ if (arg.operator === ts.SyntaxKind.KeyOfKeyword) {
1063
+ return `keyof ${subTypeName}`;
977
1064
  }
978
- else if (ts.isConditionalTypeNode(arg)) {
979
- const checkTypeName = this.calcTypeName(arg.checkType);
980
- const extendsTypeName = this.calcTypeName(arg.extendsType);
981
- const trueTypeName = this.calcTypeName(arg.trueType);
982
- const falseTypeName = this.calcTypeName(arg.falseType);
983
- return `${checkTypeName} extends ${extendsTypeName} ? ${trueTypeName} : ${falseTypeName}`;
1065
+ if (arg.operator === ts.SyntaxKind.ReadonlyKeyword) {
1066
+ return `readonly ${subTypeName}`;
984
1067
  }
985
- else if (ts.isParenthesizedTypeNode(arg)) {
986
- const internalTypeName = this.calcTypeName(arg.type);
987
- return `(${internalTypeName})`; //Parentheses are not really interesting. The type name generation adds parentheses for the clarity
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';
988
1079
  }
989
- 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);
990
- console.warn(warning.toString());
991
- return 'any';
1080
+ if (arg.kind === ts.SyntaxKind.AnyKeyword) {
1081
+ return 'any';
1082
+ }
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;
992
1097
  }
993
1098
  //Generates type name for type references
994
1099
  calcTypeReferenceTypeName(node) {
@@ -1006,54 +1111,53 @@ class TypeResolver {
1006
1111
  const refTypeName = this.getRefTypeName(name);
1007
1112
  this.current.CheckExpressionUnicity(refTypeName, name);
1008
1113
  this.context = this.typeArgumentsToContext(node, type);
1009
- const calcReferenceType = () => {
1010
- try {
1011
- const existingType = localReferenceTypeCache[name];
1012
- if (existingType) {
1013
- return existingType;
1014
- }
1015
- if (inProgressTypes[name]) {
1016
- return this.createCircularDependencyResolver(name, refTypeName);
1017
- }
1018
- inProgressTypes[name] = [];
1019
- const declarations = this.getModelTypeDeclarations(type);
1020
- const referenceTypes = [];
1021
- // If no declarations found, this might be a built-in type or a type that can't be resolved
1022
- if (declarations.length === 0) {
1023
- const fallbackReferenceType = this.getReferenceTypeFromTypeChecker(node, name, refTypeName);
1024
- if (fallbackReferenceType) {
1025
- this.addToLocalReferenceTypeCache(name, fallbackReferenceType);
1026
- return fallbackReferenceType;
1027
- }
1028
- throw new exceptions_1.GenerateMetadataError(`Could not find declarations for type '${name}'. This might be a complex generic type that needs special handling.`);
1029
- }
1030
- for (const declaration of declarations) {
1031
- if (ts.isTypeAliasDeclaration(declaration)) {
1032
- const referencer = node.pos !== -1 ? this.current.typeChecker.getTypeFromTypeNode(node) : undefined;
1033
- referenceTypes.push(new referenceTransformer_1.ReferenceTransformer().transform(declaration, refTypeName, this, referencer));
1034
- }
1035
- else if (enumTransformer_1.EnumTransformer.transformable(declaration)) {
1036
- referenceTypes.push(new enumTransformer_1.EnumTransformer().transform(this, declaration, refTypeName));
1037
- }
1038
- else {
1039
- referenceTypes.push(this.getModelReference(declaration, refTypeName));
1040
- }
1041
- }
1042
- const referenceType = referenceTransformer_1.ReferenceTransformer.merge(referenceTypes);
1043
- this.addToLocalReferenceTypeCache(name, referenceType);
1044
- return referenceType;
1045
- }
1046
- catch (err) {
1047
- delete inProgressTypes[name];
1048
- throw err;
1049
- }
1050
- };
1051
- const result = calcReferenceType();
1114
+ const result = this.resolveReferenceType(node, type, name, refTypeName);
1052
1115
  if (addToRefTypeMap) {
1053
1116
  this.current.AddReferenceType(result);
1054
1117
  }
1055
1118
  return result;
1056
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
+ }
1057
1161
  addToLocalReferenceTypeCache(name, refType) {
1058
1162
  if (inProgressTypes[name]) {
1059
1163
  for (const fn of inProgressTypes[name]) {
@@ -1071,62 +1175,65 @@ class TypeResolver {
1071
1175
  // Handle toJSON methods
1072
1176
  (0, flowUtils_1.throwUnless)(modelType.name, new exceptions_1.GenerateMetadataError("Can't get Symbol from anonymous class", modelType));
1073
1177
  const type = this.current.typeChecker.getTypeAtLocation(modelType.name);
1074
- const toJSON = this.current.typeChecker.getPropertyOfType(type, 'toJSON');
1075
- if (toJSON && toJSON.valueDeclaration && (ts.isMethodDeclaration(toJSON.valueDeclaration) || ts.isMethodSignature(toJSON.valueDeclaration))) {
1076
- 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;
1077
1181
  if (!nodeType) {
1078
- const signature = this.current.typeChecker.getSignatureFromDeclaration(toJSON.valueDeclaration);
1182
+ const signature = this.current.typeChecker.getSignatureFromDeclaration(toJSONDeclaration);
1079
1183
  const implicitType = this.current.typeChecker.getReturnTypeOfSignature(signature);
1080
1184
  nodeType = this.current.typeChecker.typeToTypeNode(implicitType, undefined, ts.NodeBuilderFlags.NoTruncation);
1081
1185
  }
1082
- const type = new TypeResolver(nodeType, this.current).resolve();
1083
- const referenceType = {
1186
+ return this.withDefinedReferenceMetadata({
1084
1187
  refName: refTypeName,
1085
1188
  dataType: 'refAlias',
1086
1189
  description,
1087
- type,
1190
+ type: new TypeResolver(nodeType, this.current).resolve(),
1088
1191
  validators: {},
1089
1192
  deprecated,
1090
- ...(example !== undefined ? { example } : {}),
1091
- ...(title !== undefined ? { title } : {}),
1092
- };
1093
- return referenceType;
1193
+ }, { example, title });
1094
1194
  }
1095
1195
  const properties = new propertyTransformer_1.PropertyTransformer().transform(this, modelType);
1096
1196
  const additionalProperties = this.getModelAdditionalProperties(modelType);
1097
1197
  const inheritedProperties = this.getModelInheritedProperties(modelType) || [];
1098
- const referenceType = {
1198
+ const referenceType = this.withDefinedReferenceMetadata({
1099
1199
  additionalProperties,
1100
1200
  dataType: 'refObject',
1101
1201
  description,
1102
1202
  properties: inheritedProperties,
1103
1203
  refName: refTypeName,
1104
1204
  deprecated,
1105
- ...(example !== undefined ? { example } : {}),
1106
- ...(title !== undefined ? { title } : {}),
1107
- };
1205
+ }, { example, title });
1108
1206
  referenceType.properties = referenceType.properties.concat(properties);
1109
1207
  return referenceType;
1110
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
+ }
1111
1218
  //Generates a name from the original type expression.
1112
1219
  //This function is not invertable, so it's possible, that 2 type expressions have the same refTypeName.
1113
1220
  getRefTypeName(name) {
1114
1221
  let preformattedName = name //Preformatted name handles most cases
1115
1222
  .replaceAll('<', '_')
1116
1223
  .replaceAll('>', '_')
1117
- .replace(/\s+/g, '')
1224
+ .replaceAll(/\s+/g, '')
1118
1225
  .replaceAll(',', '.')
1119
- .replace(/'([^']*)'/g, '$1')
1120
- .replace(/"([^"]*)"/g, '$1')
1226
+ .replaceAll(/'([^']*)'/g, '$1')
1227
+ .replaceAll(/"([^"]*)"/g, '$1')
1121
1228
  .replaceAll('&', '-and-')
1122
1229
  .replaceAll('|', '-or-')
1123
1230
  .replaceAll('[]', '-Array')
1124
1231
  .replaceAll(/[{}]/g, '_'); // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__
1125
1232
  preformattedName = replaceTypeLiteralPropertySeparators(preformattedName); // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_
1126
- preformattedName = preformattedName.replace(/;/g, '--');
1233
+ preformattedName = preformattedName.replaceAll(';', '--');
1127
1234
  preformattedName = replaceIndexedAccessSegments(preformattedName); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_,
1128
1235
  //Safety fixes to replace all characters which are not accepted by swagger ui
1129
- let formattedName = preformattedName.replace(/[^A-Za-z0-9\-._]/g, match => {
1236
+ let formattedName = preformattedName.replaceAll(/[^A-Za-z0-9\-._]/g, match => {
1130
1237
  return `_${match.codePointAt(0) ?? 0}_`;
1131
1238
  });
1132
1239
  formattedName = formattedName.replaceAll('92_r_92_n', '92_n'); //Windows uses \r\n, but linux uses \n.
@@ -1423,66 +1530,123 @@ class TypeResolver {
1423
1530
  }
1424
1531
  static getDefault(node) {
1425
1532
  const defaultStr = (0, jsDocUtils_1.getJSDocComment)(node, 'default');
1426
- if (typeof defaultStr == 'string' && defaultStr !== 'undefined') {
1427
- let textStartCharacter = undefined;
1428
- const inString = () => textStartCharacter !== undefined;
1429
- let formattedStr = '';
1430
- for (let i = 0; i < defaultStr.length; ++i) {
1431
- const actCharacter = defaultStr[i];
1432
- if (inString()) {
1433
- if (actCharacter === textStartCharacter) {
1434
- formattedStr += '"';
1435
- textStartCharacter = undefined;
1436
- }
1437
- else if (actCharacter === '"') {
1438
- formattedStr += '\\"';
1439
- }
1440
- else if (actCharacter === '\\') {
1441
- ++i;
1442
- if (i < defaultStr.length) {
1443
- const nextCharacter = defaultStr[i];
1444
- if (['n', 't', 'r', 'b', 'f', '\\', '"'].includes(nextCharacter)) {
1445
- formattedStr += '\\' + nextCharacter;
1446
- }
1447
- else if (!['v', '0'].includes(nextCharacter)) {
1448
- //\v, \0 characters are not compatible with JSON
1449
- formattedStr += nextCharacter;
1450
- }
1451
- }
1452
- else {
1453
- formattedStr += actCharacter; // this is a bug, but let the JSON parser decide how to handle it
1454
- }
1455
- }
1456
- else {
1457
- formattedStr += actCharacter;
1458
- }
1459
- }
1460
- else {
1461
- if ([`"`, "'", '`'].includes(actCharacter)) {
1462
- textStartCharacter = actCharacter;
1463
- formattedStr += '"';
1464
- }
1465
- else if (actCharacter === '/' && i + 1 < defaultStr.length && defaultStr[i + 1] === '/') {
1466
- i += 2;
1467
- while (i < defaultStr.length && defaultStr[i] !== '\n') {
1468
- ++i;
1469
- }
1470
- }
1471
- else {
1472
- formattedStr += actCharacter;
1473
- }
1474
- }
1475
- }
1476
- try {
1477
- const parsed = JSON.parse(formattedStr);
1478
- return parsed;
1479
- }
1480
- catch (err) {
1481
- const message = err instanceof Error ? err.message : '-';
1482
- throw new exceptions_1.GenerateMetadataError(`JSON could not parse default str: "${defaultStr}", preformatted: "${formattedStr}"\nmessage: "${message}"`);
1483
- }
1533
+ if (typeof defaultStr !== 'string' || defaultStr === 'undefined') {
1534
+ return undefined;
1484
1535
  }
1485
- 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
+ };
1486
1650
  }
1487
1651
  }
1488
1652
  exports.TypeResolver = TypeResolver;