@trapi/metadata 0.1.1 → 0.1.5

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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cache/driver.js +1 -2
  3. package/dist/cache/driver.js.map +1 -1
  4. package/dist/cache/utils.d.ts.map +1 -1
  5. package/dist/cache/utils.js +12 -12
  6. package/dist/cache/utils.js.map +1 -1
  7. package/dist/decorator/mapper/index.js +1 -2
  8. package/dist/decorator/mapper/index.js.map +1 -1
  9. package/dist/decorator/representation/index.js +2 -4
  10. package/dist/decorator/representation/index.js.map +1 -1
  11. package/dist/decorator/representation/property/utils.js +4 -3
  12. package/dist/decorator/representation/property/utils.js.map +1 -1
  13. package/dist/generator/controller.js +1 -1
  14. package/dist/generator/controller.js.map +1 -1
  15. package/dist/generator/endpoint.js +0 -3
  16. package/dist/generator/endpoint.js.map +1 -1
  17. package/dist/generator/index.js +3 -9
  18. package/dist/generator/index.js.map +1 -1
  19. package/dist/generator/method.js +0 -2
  20. package/dist/generator/method.js.map +1 -1
  21. package/dist/generator/parameter.js +1 -9
  22. package/dist/generator/parameter.js.map +1 -1
  23. package/dist/resolver/type-node.js +28 -61
  24. package/dist/resolver/type-node.js.map +1 -1
  25. package/dist/resolver/utils/initializer.d.ts.map +1 -1
  26. package/dist/resolver/utils/initializer.js +2 -1
  27. package/dist/resolver/utils/initializer.js.map +1 -1
  28. package/dist/resolver/utils/validator.js +2 -1
  29. package/dist/resolver/utils/validator.js.map +1 -1
  30. package/dist/utils/generator.js +1 -1
  31. package/dist/utils/generator.js.map +1 -1
  32. package/dist/utils/js-doc.js +1 -1
  33. package/dist/utils/js-doc.js.map +1 -1
  34. package/package.json +18 -12
  35. package/src/cache/driver.ts +0 -97
  36. package/src/cache/index.ts +0 -10
  37. package/src/cache/type.ts +0 -43
  38. package/src/cache/utils.ts +0 -51
  39. package/src/config/index.ts +0 -8
  40. package/src/config/path.ts +0 -29
  41. package/src/decorator/functions.ts +0 -142
  42. package/src/decorator/index.ts +0 -13
  43. package/src/decorator/mapper/index.ts +0 -153
  44. package/src/decorator/mapper/maps/decorators-express.ts +0 -117
  45. package/src/decorator/mapper/maps/internal.ts +0 -95
  46. package/src/decorator/mapper/maps/typescript-rest.ts +0 -150
  47. package/src/decorator/mapper/utils.ts +0 -88
  48. package/src/decorator/representation/index.ts +0 -82
  49. package/src/decorator/representation/property/utils.ts +0 -146
  50. package/src/decorator/type.ts +0 -235
  51. package/src/decorator/utils/index.ts +0 -9
  52. package/src/decorator/utils/node.ts +0 -59
  53. package/src/decorator/utils/validator.ts +0 -104
  54. package/src/generator/controller.ts +0 -65
  55. package/src/generator/endpoint.ts +0 -212
  56. package/src/generator/index.ts +0 -219
  57. package/src/generator/method.ts +0 -198
  58. package/src/generator/parameter.ts +0 -392
  59. package/src/index.ts +0 -13
  60. package/src/resolver/error.ts +0 -38
  61. package/src/resolver/index.ts +0 -11
  62. package/src/resolver/type-node.ts +0 -1287
  63. package/src/resolver/type.ts +0 -257
  64. package/src/resolver/utils/index.ts +0 -9
  65. package/src/resolver/utils/initializer.ts +0 -81
  66. package/src/resolver/utils/validator.ts +0 -144
  67. package/src/type.ts +0 -185
  68. package/src/utils/generator.ts +0 -42
  69. package/src/utils/index.ts +0 -10
  70. package/src/utils/js-doc.ts +0 -125
  71. package/src/utils/validator.ts +0 -43
  72. package/test/data/library/@decorators-express/decorators.ts +0 -7
  73. package/test/data/library/self/api.ts +0 -37
  74. package/test/data/library/type.ts +0 -138
  75. package/test/data/library/typescript-rest/api.ts +0 -366
  76. package/test/data/library/typescript-rest/decorators/index.ts +0 -10
  77. package/test/data/library/typescript-rest/decorators/methods.ts +0 -34
  78. package/test/data/library/typescript-rest/decorators/parameters.ts +0 -62
  79. package/test/data/library/typescript-rest/decorators/services.ts +0 -47
  80. package/test/data/library/typescript-rest/return-types.ts +0 -94
  81. package/test/data/library/utils.ts +0 -7
  82. package/test/jest.config.js +0 -49
  83. package/test/unit/cache.spec.ts +0 -48
  84. package/test/unit/decorator/mapper/index.spec.ts +0 -83
  85. package/test/unit/decorator/representation/index.spec.ts +0 -102
  86. package/test/unit/decorator/utils/node.spec.ts +0 -47
  87. package/test/unit/library/typescript-rest.spec.ts +0 -342
  88. package/test/unit/resolver/type.spec.ts +0 -134
  89. package/test/unit/utils/generator.spec.ts +0 -41
  90. package/test/unit/utils/js-doc.spec.ts +0 -95
  91. package/tsconfig.build.json +0 -11
  92. package/tsconfig.json +0 -9
  93. package/writable/.gitignore +0 -3
@@ -1,1287 +0,0 @@
1
- /*
2
- * Copyright (c) 2021.
3
- * Author Peter Placzek (tada5hi)
4
- * For the full copyright and license information,
5
- * view the LICENSE file that was distributed with this source code.
6
- */
7
-
8
- import * as ts from 'typescript';
9
- import {hasOwnProperty} from "@trapi/metadata-utils";
10
- import {Decorator} from "../decorator";
11
-
12
- import {MetadataGenerator} from '../generator';
13
- import {
14
- getJSDocTagComment,
15
- getJSDocTagNames,
16
- isExistJSDocTag
17
- } from '../utils';
18
- import {ResolverError} from "./error";
19
- import {
20
- Resolver
21
- } from "./type";
22
- import {getInitializerValue} from "./utils";
23
- import {Property} from "../type";
24
- import {getNodeDecorators} from "../decorator";
25
-
26
- const localReferenceTypeCache: { [typeName: string]: Resolver.ReferenceType } = {};
27
- const inProgressTypes: { [typeName: string]: boolean } = {};
28
-
29
- type OverrideToken = ts.Token<ts.SyntaxKind.QuestionToken> | ts.Token<ts.SyntaxKind.PlusToken> | ts.Token<ts.SyntaxKind.MinusToken> | undefined;
30
- type UsableDeclaration = ts.InterfaceDeclaration | ts.ClassDeclaration | ts.PropertySignature | ts.TypeAliasDeclaration | ts.EnumMember;
31
-
32
- interface TypeNodeResolverContext {
33
- [name: string]: ts.TypeReferenceNode | ts.TypeNode;
34
- }
35
-
36
- function getPropertyValidators(property: ts.PropertyDeclaration | ts.TypeAliasDeclaration | ts.PropertySignature | ts.ParameterDeclaration) {
37
- return {};
38
- }
39
-
40
- type UtilityType = 'NonNullable' | 'Omit' | 'Partial' | 'Readonly' | 'Record' | 'Required' | 'Pick';
41
- interface UtilityOptions {
42
- keys: Array<string | number | boolean>;
43
- }
44
-
45
- export class TypeNodeResolver {
46
- constructor(
47
- private readonly typeNode: ts.TypeNode,
48
- private readonly current: MetadataGenerator,
49
- private readonly parentNode?: ts.Node,
50
- private context: TypeNodeResolverContext = {},
51
- private readonly referencer?: ts.TypeNode,
52
- ) {}
53
-
54
- public static clearCache() {
55
- Object.keys(localReferenceTypeCache).forEach(key => {
56
- delete localReferenceTypeCache[key];
57
- });
58
-
59
- Object.keys(inProgressTypes).forEach(key => {
60
- delete inProgressTypes[key];
61
- });
62
- }
63
-
64
- public resolve(): Resolver.Type {
65
- const primitiveType = this.getPrimitiveType(this.typeNode, this.parentNode);
66
- if (primitiveType) {
67
- return primitiveType;
68
- }
69
-
70
- if (this.typeNode.kind === ts.SyntaxKind.NullKeyword) {
71
- return {
72
- typeName: 'enum',
73
- members: [null],
74
- } as Resolver.EnumType;
75
- }
76
-
77
- if (this.typeNode.kind === ts.SyntaxKind.ArrayType) {
78
- return {
79
- typeName: 'array',
80
- elementType: new TypeNodeResolver((this.typeNode as ts.ArrayTypeNode).elementType, this.current, this.parentNode, this.context).resolve(),
81
- } as Resolver.ArrayType;
82
- }
83
-
84
- if (ts.isUnionTypeNode(this.typeNode)) {
85
- const types = this.typeNode.types.map(type => {
86
- return new TypeNodeResolver(type, this.current, this.parentNode, this.context).resolve();
87
- });
88
-
89
- return {
90
- typeName: 'union',
91
- members: types,
92
- } as Resolver.UnionType;
93
- }
94
-
95
- if (ts.isIntersectionTypeNode(this.typeNode)) {
96
- const types = this.typeNode.types.map(type => {
97
- return new TypeNodeResolver(type, this.current, this.parentNode, this.context).resolve();
98
- });
99
-
100
- return {
101
- typeName: 'intersection',
102
- members: types,
103
- } as Resolver.IntersectionType;
104
- }
105
-
106
- if (this.typeNode.kind === ts.SyntaxKind.AnyKeyword || this.typeNode.kind === ts.SyntaxKind.UnknownKeyword || this.typeNode.kind === ts.SyntaxKind.UndefinedKeyword) {
107
- return {
108
- typeName: 'any',
109
- } as Resolver.AnyType;
110
- }
111
-
112
- if (ts.isLiteralTypeNode(this.typeNode)) {
113
- return {
114
- typeName: 'enum',
115
- members: [TypeNodeResolver.getLiteralValue(this.typeNode)],
116
- } as Resolver.EnumType;
117
- }
118
-
119
- if (ts.isTypeLiteralNode(this.typeNode)) {
120
- const properties : Property[] = this.typeNode.members
121
- .filter(member => ts.isPropertySignature(member))
122
- .reduce((res, propertySignature: ts.PropertySignature) => {
123
- const type = new TypeNodeResolver(propertySignature.type as ts.TypeNode, this.current, propertySignature, this.context).resolve();
124
- const property: Property = {
125
- deprecated: isExistJSDocTag(propertySignature, tag => tag.tagName.text === 'deprecated'),
126
- example: TypeNodeResolver.getNodeExample(propertySignature),
127
- default: getJSDocTagComment(propertySignature, 'default'),
128
- description: this.getNodeDescription(propertySignature),
129
- format: TypeNodeResolver.getNodeFormat(propertySignature),
130
- name: (propertySignature.name as ts.Identifier).text,
131
- required: !propertySignature.questionToken,
132
- type: type,
133
- validators: getPropertyValidators(propertySignature) || {}
134
- };
135
-
136
- return [property, ...res];
137
- }, []);
138
-
139
- const indexMember = this.typeNode.members.find(member => ts.isIndexSignatureDeclaration(member));
140
- let additionalType: Resolver.Type | undefined;
141
-
142
- if (indexMember) {
143
- const indexSignatureDeclaration = indexMember as ts.IndexSignatureDeclaration;
144
- const indexType = new TypeNodeResolver(indexSignatureDeclaration.parameters[0].type as ts.TypeNode, this.current, this.parentNode, this.context).resolve();
145
- if (indexType.typeName !== 'string') {
146
- throw new ResolverError(`Only string indexes are supported.`, this.typeNode);
147
- }
148
-
149
- additionalType = new TypeNodeResolver(indexSignatureDeclaration.type, this.current, this.parentNode, this.context).resolve();
150
- }
151
-
152
- return {
153
- additionalProperties: indexMember && additionalType,
154
- typeName: 'nestedObjectLiteral',
155
- properties: properties,
156
- } as Resolver.NestedObjectLiteralType;
157
- }
158
-
159
- if (this.typeNode.kind === ts.SyntaxKind.ObjectKeyword || ts.isFunctionTypeNode(this.typeNode)) {
160
- return { typeName: 'object' };
161
- }
162
-
163
- if (ts.isMappedTypeNode(this.typeNode) && this.referencer) {
164
- const type = this.current.typeChecker.getTypeFromTypeNode(this.referencer);
165
- const mappedTypeNode = this.typeNode;
166
- const typeChecker = this.current.typeChecker;
167
- const getDeclaration = (prop: ts.Symbol) => prop.declarations && (prop.declarations[0] as ts.Declaration | undefined);
168
- const isIgnored = (prop: ts.Symbol) => {
169
- const declaration = getDeclaration(prop);
170
- return (
171
- prop.getJsDocTags().find(tag => tag.name === 'ignore') !== undefined ||
172
- (declaration !== undefined && !ts.isPropertyDeclaration(declaration) && !ts.isPropertySignature(declaration) && !ts.isParameter(declaration))
173
- );
174
- };
175
- const properties: Property[] = type
176
- .getProperties()
177
- // Ignore methods, getter, setter and @ignored props
178
- .filter(property => isIgnored(property) === false)
179
- // Transform to property
180
- .map(property => {
181
- const propertyType = typeChecker.getTypeOfSymbolAtLocation(property, this.typeNode);
182
- const declaration = getDeclaration(property) as ts.PropertySignature | ts.PropertyDeclaration | ts.ParameterDeclaration | undefined;
183
-
184
- if (declaration && ts.isPropertySignature(declaration)) {
185
- return { ...this.propertyFromSignature(declaration, mappedTypeNode.questionToken), name: property.getName() };
186
- } else if (declaration && (ts.isPropertyDeclaration(declaration) || ts.isParameter(declaration))) {
187
- return { ...this.propertyFromDeclaration(declaration, mappedTypeNode.questionToken), name: property.getName() };
188
- }
189
-
190
- // Resolve default value, required and typeNode
191
- let required = false;
192
- const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation)!;
193
- if (mappedTypeNode.questionToken && mappedTypeNode.questionToken.kind === ts.SyntaxKind.MinusToken) {
194
- required = true;
195
- } else if (mappedTypeNode.questionToken && mappedTypeNode.questionToken.kind === ts.SyntaxKind.QuestionToken) {
196
- required = false;
197
- }
198
-
199
- // Push property
200
- return {
201
- name: property.getName(),
202
- required: required,
203
- deprecated: false,
204
- type: new TypeNodeResolver(typeNode, this.current, this.typeNode, this.context, this.referencer).resolve(),
205
- validators: {},
206
- };
207
- });
208
-
209
- return {
210
- typeName: 'nestedObjectLiteral',
211
- properties: properties,
212
- } as Resolver.NestedObjectLiteralType;
213
- }
214
-
215
- if (ts.isConditionalTypeNode(this.typeNode) && this.referencer && ts.isTypeReferenceNode(this.referencer)) {
216
- const type = this.current.typeChecker.getTypeFromTypeNode(this.referencer);
217
-
218
- if (type.aliasSymbol) {
219
- let declaration = type.aliasSymbol.declarations[0] as ts.TypeAliasDeclaration | ts.EnumDeclaration | ts.DeclarationStatement;
220
- if (declaration.name) {
221
- declaration = this.getModelTypeDeclaration(declaration.name as ts.EntityName) as ts.TypeAliasDeclaration | ts.EnumDeclaration | ts.DeclarationStatement;
222
- }
223
-
224
- const name = TypeNodeResolver.getRefTypeName(this.referencer.getText());
225
- return this.handleCachingAndCircularReferences(name, () => {
226
- if (ts.isTypeAliasDeclaration(declaration)) {
227
- // Note: I don't understand why typescript lose type for `this.referencer` (from above with isTypeReferenceNode())
228
- return this.getTypeAliasReference(declaration, this.current.typeChecker.typeToString(type), this.referencer as ts.TypeReferenceNode);
229
- } else if (ts.isEnumDeclaration(declaration)) {
230
- return this.getEnumerateType(declaration.name) as Resolver.RefEnumType;
231
- } else {
232
- throw new ResolverError(
233
- `Couldn't resolve Conditional to TypeNode. If you think this should be resolvable, please file an Issue. We found an aliasSymbol and it's declaration was of kind ${declaration.kind}`,
234
- this.typeNode,
235
- );
236
- }
237
- });
238
- } else if (type.isClassOrInterface()) {
239
- let declaration = type.symbol.declarations[0] as ts.InterfaceDeclaration | ts.ClassDeclaration;
240
- if (declaration.name) {
241
- declaration = this.getModelTypeDeclaration(declaration.name) as ts.InterfaceDeclaration | ts.ClassDeclaration;
242
- }
243
- const name = TypeNodeResolver.getRefTypeName(this.referencer.getText());
244
- return this.handleCachingAndCircularReferences(name, () => this.getModelReference(declaration, this.current.typeChecker.typeToString(type)));
245
- } else {
246
- try {
247
- return new TypeNodeResolver(this.current.typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation)!, this.current, this.typeNode, this.context, this.referencer).resolve();
248
- } catch {
249
- throw new ResolverError(
250
- `Couldn't resolve Conditional to TypeNode. If you think this should be resolvable, please file an Issue. The flags on the result of the ConditionalType was ${type.flags}`,
251
- this.typeNode,
252
- );
253
- }
254
- }
255
- }
256
-
257
- if (ts.isTypeOperatorNode(this.typeNode) && this.typeNode.operator === ts.SyntaxKind.KeyOfKeyword) {
258
- const type = this.current.typeChecker.getTypeFromTypeNode(this.typeNode);
259
- try {
260
- return new TypeNodeResolver(this.current.typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.NoTruncation)!, this.current, this.typeNode, this.context, this.referencer).resolve();
261
- } catch (err) {
262
- const indexedTypeName = this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode.type));
263
- throw new ResolverError(`Could not determine the keys on ${indexedTypeName}`, this.typeNode);
264
- }
265
- }
266
-
267
- if (ts.isIndexedAccessTypeNode(this.typeNode) && (this.typeNode.indexType.kind === ts.SyntaxKind.NumberKeyword || this.typeNode.indexType.kind === ts.SyntaxKind.StringKeyword)) {
268
- const numberIndexType = this.typeNode.indexType.kind === ts.SyntaxKind.NumberKeyword;
269
- const objectType = this.current.typeChecker.getTypeFromTypeNode(this.typeNode.objectType);
270
- const type = numberIndexType ? objectType.getNumberIndexType() : objectType.getStringIndexType();
271
- if (type === undefined) {
272
- throw new ResolverError(`Could not determine ${numberIndexType ? 'number' : 'string'} index on ${this.current.typeChecker.typeToString(objectType)}`, this.typeNode);
273
- }
274
- return new TypeNodeResolver(this.current.typeChecker.typeToTypeNode(type, undefined, undefined)!, this.current, this.typeNode, this.context, this.referencer).resolve();
275
- }
276
-
277
- if (
278
- ts.isIndexedAccessTypeNode(this.typeNode) &&
279
- ts.isLiteralTypeNode(this.typeNode.indexType) &&
280
- (ts.isStringLiteral(this.typeNode.indexType.literal) || ts.isNumericLiteral(this.typeNode.indexType.literal))
281
- ) {
282
- const hasType = (node: ts.Node | undefined): node is ts.HasType => node !== undefined && node.hasOwnProperty('type');
283
- const symbol = this.current.typeChecker.getPropertyOfType(this.current.typeChecker.getTypeFromTypeNode(this.typeNode.objectType), this.typeNode.indexType.literal.text);
284
- if (symbol === undefined) {
285
- throw new ResolverError(
286
- `Could not determine the keys on ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode.objectType))}`,
287
- this.typeNode,
288
- );
289
- }
290
- if (hasType(symbol.valueDeclaration) && symbol.valueDeclaration.type) {
291
- return new TypeNodeResolver(symbol.valueDeclaration.type, this.current, this.typeNode, this.context, this.referencer).resolve();
292
- }
293
- const declaration = this.current.typeChecker.getTypeOfSymbolAtLocation(symbol, this.typeNode.objectType);
294
- try {
295
- return new TypeNodeResolver(this.current.typeChecker.typeToTypeNode(declaration, undefined, undefined)!, this.current, this.typeNode, this.context, this.referencer).resolve();
296
- } catch {
297
- throw new ResolverError(
298
- `Could not determine the keys on ${this.current.typeChecker.typeToString(
299
- this.current.typeChecker.getTypeFromTypeNode(this.current.typeChecker.typeToTypeNode(declaration, undefined, undefined)!),
300
- )}`,
301
- this.typeNode,
302
- );
303
- }
304
- }
305
-
306
- // @ts-ignore
307
- if (this.typeNode.kind === ts.SyntaxKind.TemplateLiteralType) {
308
- const type = this.current.typeChecker.getTypeFromTypeNode(this.referencer || this.typeNode);
309
- if (type.isUnion() && type.types.every(unionElementType => unionElementType.isStringLiteral())) {
310
- return {
311
- typeName: 'enum',
312
- members: type.types.map((stringLiteralType: ts.StringLiteralType) => stringLiteralType.value),
313
- } as Resolver.EnumType;
314
- } else {
315
- throw new ResolverError(`Could not the type of ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode), this.typeNode)}`, this.typeNode);
316
- }
317
- }
318
-
319
- if (ts.isParenthesizedTypeNode(this.typeNode)) {
320
- return new TypeNodeResolver(this.typeNode.type, this.current, this.typeNode, this.context, this.referencer).resolve();
321
- }
322
-
323
- if (this.typeNode.kind !== ts.SyntaxKind.TypeReference) {
324
- throw new ResolverError(`Unknown type: ${ts.SyntaxKind[this.typeNode.kind]}`, this.typeNode);
325
- }
326
-
327
- const typeReference = this.typeNode as ts.TypeReferenceNode;
328
-
329
- if (typeReference.typeName.kind === ts.SyntaxKind.Identifier) {
330
- // Special Utility Type
331
- if(typeReference.typeName.text === 'Record') {
332
- return {
333
- additionalProperties: new TypeNodeResolver(typeReference.typeArguments[1], this.current, this.parentNode, this.context).resolve(),
334
- typeName: 'nestedObjectLiteral',
335
- properties: [],
336
- } as Resolver.NestedObjectLiteralType;
337
- }
338
-
339
- const specialReference = TypeNodeResolver.resolveSpecialReference(typeReference.typeName);
340
- if(typeof specialReference !== 'undefined') {
341
- return specialReference;
342
- }
343
-
344
- if (typeReference.typeName.text === 'Date') {
345
- return this.getDateType(this.parentNode);
346
- }
347
-
348
- if (typeReference.typeName.text === 'Buffer' || typeReference.typeName.text === 'Readable') {
349
- return { typeName: 'buffer' } as Resolver.BufferType;
350
- }
351
-
352
- if (typeReference.typeName.text === 'Array' && typeReference.typeArguments && typeReference.typeArguments.length >= 1) {
353
- return {
354
- typeName: 'array',
355
- elementType: new TypeNodeResolver(typeReference.typeArguments[0], this.current, this.parentNode, this.context).resolve(),
356
- } as Resolver.ArrayType;
357
- }
358
-
359
- if (typeReference.typeName.text === 'Promise' && typeReference.typeArguments && typeReference.typeArguments.length === 1) {
360
- return new TypeNodeResolver(typeReference.typeArguments[0], this.current, this.parentNode, this.context).resolve();
361
- }
362
-
363
- if (typeReference.typeName.text === 'String') {
364
- return {typeName: 'string'} as Resolver.StringType;
365
- }
366
-
367
- if (this.context[typeReference.typeName.text]) {
368
- return new TypeNodeResolver(this.context[typeReference.typeName.text], this.current, this.parentNode, this.context).resolve();
369
- }
370
- }
371
-
372
- const referenceType = this.getReferenceType(typeReference);
373
-
374
- this.current.addReferenceType(referenceType);
375
- return referenceType;
376
- }
377
-
378
- // ------------------------------------------------------------------------
379
- // Utility Type(s)
380
- // ------------------------------------------------------------------------
381
- private static isSupportedUtilityType(typeName: string | ts.Identifier | undefined) : typeName is UtilityType {
382
- if(typeof typeName === 'undefined') { return false; }
383
- return ['NonNullable', 'Pick', 'Omit', 'Partial', 'Readonly', 'Record', 'Required'].indexOf(typeof typeName !== 'string' ? typeName.text : typeName) !== -1;
384
- }
385
-
386
- private static getUtilityTypeOptions(typeArguments: ts.NodeArray<ts.TypeNode>) {
387
- const utilityOptions : UtilityOptions = {
388
- keys: []
389
- };
390
-
391
- if(typeArguments.length >= 2) {
392
- if (ts.isUnionTypeNode(typeArguments[1])) {
393
- const args : ts.NodeArray<ts.TypeNode> = (typeArguments[1] as ts.UnionTypeNode).types;
394
- for(let i=0; i<args.length; i++) {
395
- if (ts.isLiteralTypeNode(args[i])) {
396
- utilityOptions['keys'].push(TypeNodeResolver.getLiteralValue(args[i] as ts.LiteralTypeNode));
397
- }
398
- }
399
- }
400
-
401
- if (ts.isLiteralTypeNode(typeArguments[1])) {
402
- utilityOptions['keys'].push(TypeNodeResolver.getLiteralValue(typeArguments[1] as ts.LiteralTypeNode));
403
- }
404
- }
405
-
406
- return utilityOptions;
407
- }
408
-
409
- private filterUtilityProperties<T extends Record<'name' | string, any>>(properties: T[], utilityType?: UtilityType, utilityOptions?: UtilityOptions) : T[] {
410
- if(typeof utilityType === 'undefined' || typeof utilityOptions === 'undefined') {
411
- return properties;
412
- }
413
-
414
- return properties
415
- .filter(property => {
416
- const name : string = typeof property.name !== 'string' ? (property.name as ts.Identifier).text : property.name;
417
-
418
- switch (utilityType) {
419
- case 'Pick':
420
- return utilityOptions.keys.indexOf(name) !== -1;
421
- case 'Omit':
422
- return utilityOptions.keys.indexOf(name) === -1;
423
- }
424
-
425
- return true;
426
- })
427
- .map(property => {
428
- if(hasOwnProperty(property, 'required')) {
429
- switch (utilityType) {
430
- case 'Partial':
431
- property.required = false;
432
- break;
433
- case 'Required':
434
- case 'NonNullable':
435
- property.required = true;
436
- break;
437
- }
438
- }
439
-
440
- return property;
441
- });
442
- }
443
-
444
- private static resolveSpecialReference(node: ts.Identifier) : Resolver.Type | undefined {
445
- switch (node.text) {
446
- case 'Buffer':
447
- case 'DownloadBinaryData':
448
- case 'DownloadResource':
449
- return { typeName: 'buffer' } as Resolver.BufferType;
450
- default:
451
- return undefined;
452
- }
453
- }
454
-
455
- private static getLiteralValue(typeNode: ts.LiteralTypeNode): string | number | boolean | null {
456
- let value: boolean | number | string | null;
457
- switch (typeNode.literal.kind) {
458
- case ts.SyntaxKind.TrueKeyword:
459
- value = true;
460
- break;
461
- case ts.SyntaxKind.FalseKeyword:
462
- value = false;
463
- break;
464
- case ts.SyntaxKind.StringLiteral:
465
- value = typeNode.literal.text;
466
- break;
467
- case ts.SyntaxKind.NumericLiteral:
468
- value = parseFloat(typeNode.literal.text);
469
- break;
470
- case ts.SyntaxKind.NullKeyword:
471
- value = null;
472
- break;
473
- default:
474
- if (typeNode.literal.hasOwnProperty('text')) {
475
- value = (typeNode.literal as ts.LiteralExpression).text;
476
- } else {
477
- throw new ResolverError(`Couldn't resolve literal node: ${typeNode.literal.getText()}`);
478
- }
479
- }
480
- return value;
481
- }
482
-
483
- private getPrimitiveType(typeNode: ts.TypeNode, parentNode?: ts.Node): Resolver.PrimitiveType | undefined {
484
- if(!typeNode) {
485
- return {
486
- typeName: 'void',
487
- };
488
- }
489
-
490
- const resolvedType = this.attemptToResolveKindToPrimitive(typeNode.kind);
491
- if (typeof resolvedType === 'undefined') {
492
- return undefined;
493
- }
494
-
495
- if (resolvedType === 'number') {
496
- if (!parentNode) {
497
- return { typeName: 'double' };
498
- }
499
-
500
- const tags = getJSDocTagNames(parentNode)
501
- .filter(name => {
502
- return [
503
- 'isInt',
504
- 'isLong',
505
- 'isFloat',
506
- 'isDouble'
507
- ].some(m => m.toLowerCase() === name.toLowerCase());
508
- })
509
- .map(name => name.toLowerCase());
510
-
511
- const data : Decorator.Data[] = getNodeDecorators(parentNode, identifier => [
512
- 'isInt',
513
- 'isLong',
514
- 'isFloat',
515
- 'isDouble'
516
- ].some(m => m.toLowerCase() === identifier.text.toLowerCase()));
517
-
518
- let decorator : string | undefined = data.length > 0 ? data[0].text : undefined;
519
-
520
- if(typeof decorator !== 'undefined') {
521
- decorator = decorator.toLowerCase();
522
- }
523
-
524
- if (!decorator && tags.length === 0) {
525
- return { typeName: 'double' };
526
- }
527
-
528
- switch (decorator || tags[0]) {
529
- case 'isint':
530
- return { typeName: 'integer' };
531
- case 'islong':
532
- return { typeName: 'long' };
533
- case 'isfloat':
534
- return { typeName: 'float' };
535
- case 'isdouble':
536
- return { typeName: 'double' };
537
- default:
538
- return { typeName: 'double' };
539
- }
540
- } else if (resolvedType === 'string') {
541
- return {
542
- typeName: 'string',
543
- };
544
- } else if (resolvedType === 'boolean') {
545
- return {
546
- typeName: 'boolean',
547
- };
548
- } else if (resolvedType === 'void') {
549
- return {
550
- typeName: 'void',
551
- };
552
- } else {
553
- // todo: should not occur
554
- return resolvedType;
555
- }
556
- }
557
-
558
- private getDateType(parentNode?: ts.Node): Resolver.DateType | Resolver.DateTimeType {
559
- if (!parentNode) {
560
- return { typeName: 'datetime' };
561
- }
562
- const tags = getJSDocTagNames(parentNode).filter(name => {
563
- return ['isDate', 'isDateTime'].some(m => m === name);
564
- });
565
-
566
- if (tags.length === 0) {
567
- return { typeName: 'datetime' };
568
- }
569
- switch (tags[0]) {
570
- case 'isDate':
571
- return { typeName: 'date' };
572
- case 'isDateTime':
573
- return { typeName: 'datetime' };
574
- default:
575
- return { typeName: 'datetime' };
576
- }
577
- }
578
-
579
- private static getDesignatedModels(nodes: ts.Node[], typeName: string): ts.Node[] {
580
- return nodes;
581
- }
582
-
583
- private getEnumerateType(typeName: ts.EntityName): Resolver.RefEnumType | undefined {
584
- const enumName = (typeName as ts.Identifier).text;
585
- let enumNodes = this.current.nodes.filter(node => node.kind === ts.SyntaxKind.EnumDeclaration).filter(node => (node as any).name.text === enumName);
586
-
587
- if (!enumNodes.length) {
588
- return undefined;
589
- }
590
-
591
- enumNodes = TypeNodeResolver.getDesignatedModels(enumNodes, enumName);
592
-
593
- if (enumNodes.length > 1) {
594
- throw new ResolverError(`Multiple matching enum found for enum ${enumName}; please make enum names unique.`);
595
- }
596
-
597
- const enumDeclaration = enumNodes[0] as ts.EnumDeclaration;
598
-
599
- const isNotUndefined = <T>(item: T): item is Exclude<T, undefined> => {
600
- return item !== undefined;
601
- };
602
-
603
- const enums = enumDeclaration.members.map(this.current.typeChecker.getConstantValue.bind(this.current.typeChecker)).filter(isNotUndefined);
604
- const enumNames = enumDeclaration.members.map(e => e.name.getText()).filter(isNotUndefined);
605
-
606
- return {
607
- typeName: 'refEnum',
608
- description: this.getNodeDescription(enumDeclaration),
609
- members: enums as string[],
610
- memberNames: enumNames,
611
- refName: enumName,
612
- deprecated: isExistJSDocTag(enumDeclaration, tag => tag.tagName.text === 'deprecated')
613
- };
614
- }
615
-
616
- private getReferenceType(node: ts.TypeReferenceType): Resolver.ReferenceType {
617
- let type: ts.EntityName;
618
- if (ts.isTypeReferenceNode(node)) {
619
- type = node.typeName;
620
- } else if (ts.isExpressionWithTypeArguments(node)) {
621
- type = node.expression as ts.EntityName;
622
- } else {
623
- throw new ResolverError(`Can't resolve Reference type.`);
624
- }
625
-
626
- // Can't invoke getText on Synthetic Nodes
627
- let resolvableName = node.pos !== -1 ? node.getText() : (type as ts.Identifier).text;
628
- if (node.pos === -1 && 'typeArguments' in node && Array.isArray(node.typeArguments)) {
629
- // Add typeArguments for Synthetic nodes (e.g. Record<> in TestClassModel.indexedResponse)
630
- const argumentsString = node.typeArguments
631
- .map(arg => {
632
- if (ts.isLiteralTypeNode(arg)) {
633
- return `'${String(TypeNodeResolver.getLiteralValue(arg))}'`;
634
- }
635
- const resolvedType = this.attemptToResolveKindToPrimitive(arg.kind);
636
- if (typeof resolvedType === 'undefined') { return 'any'; }
637
- return resolvedType;
638
- });
639
-
640
- resolvableName += `<${argumentsString.join(', ')}>`;
641
- }
642
-
643
- const name = this.contextualizedName(resolvableName);
644
-
645
- // Handle Utility Types
646
- const identifierName = (type as ts.Identifier).text;
647
- const utilityTypeSupported : boolean = TypeNodeResolver.isSupportedUtilityType(identifierName);
648
- const utilityType : UtilityType | undefined = utilityTypeSupported ? identifierName as UtilityType : undefined;
649
- let utilityOptions : UtilityOptions = {
650
- keys: []
651
- };
652
-
653
- if(utilityTypeSupported) {
654
- const typeArguments : ts.NodeArray<ts.TypeNode> = (type.parent as ts.TypeReferenceNode).typeArguments;
655
- if (ts.isTypeReferenceNode(typeArguments[0])) {
656
- type = (typeArguments[0] as ts.TypeReferenceNode).typeName;
657
- } else if (ts.isExpressionWithTypeArguments(typeArguments[0])) {
658
- type = (typeArguments[0] as ts.ExpressionWithTypeArguments).expression as ts.EntityName;
659
- } else {
660
- throw new ResolverError(`Can't resolve Reference type.`);
661
- }
662
-
663
- utilityOptions = TypeNodeResolver.getUtilityTypeOptions(typeArguments);
664
- } else {
665
- this.typeArgumentsToContext(node, type, this.context);
666
- }
667
-
668
- try {
669
- const existingType = localReferenceTypeCache[name];
670
- if (existingType) {
671
- return existingType;
672
- }
673
-
674
- const refEnumType = this.getEnumerateType(type);
675
- if (refEnumType) {
676
- localReferenceTypeCache[name] = refEnumType;
677
- return refEnumType;
678
- }
679
-
680
- if (inProgressTypes[name]) {
681
- return this.createCircularDependencyResolver(name);
682
- }
683
-
684
- inProgressTypes[name] = true;
685
-
686
- const declaration : UsableDeclaration = this.getModelTypeDeclaration(type);
687
-
688
- let referenceType: Resolver.ReferenceType;
689
- if (ts.isTypeAliasDeclaration(declaration)) {
690
- referenceType = this.getTypeAliasReference(declaration, name, node);
691
- } else if (ts.isEnumMember(declaration)) {
692
- referenceType = {
693
- typeName: 'refEnum',
694
- refName: TypeNodeResolver.getRefTypeName(name, utilityType),
695
- members: [this.current.typeChecker.getConstantValue(declaration)!],
696
- // @ts-ignore
697
- memberNames: [declaration.name.getText()],
698
- deprecated: isExistJSDocTag(declaration, tag => tag.tagName.text === 'deprecated')
699
- };
700
- } else {
701
- referenceType = this.getModelReference(declaration, name, utilityType, utilityOptions);
702
- }
703
-
704
- localReferenceTypeCache[name] = referenceType;
705
-
706
- return referenceType;
707
- } catch (err) {
708
- // eslint-disable-next-line no-console
709
- console.error(`There was a problem resolving type of '${name}'.`);
710
- throw err;
711
- }
712
- }
713
-
714
- private getTypeAliasReference(declaration: ts.TypeAliasDeclaration, name: string, referencer: ts.TypeReferenceType): Resolver.ReferenceType {
715
- const refName = TypeNodeResolver.getRefTypeName(name);
716
-
717
- let type : Resolver.Type | undefined;
718
-
719
- if(declaration.type.kind === ts.SyntaxKind.TypeReference) {
720
- const referenceType = this.getReferenceType(declaration.type as ts.TypeReferenceNode);
721
- if(referenceType.refName === refName) {
722
- return referenceType;
723
- }
724
- }
725
-
726
- if(typeof type === 'undefined') {
727
- type = new TypeNodeResolver(declaration.type, this.current, declaration, this.context, this.referencer || referencer).resolve();
728
- }
729
-
730
- const example = TypeNodeResolver.getNodeExample(declaration);
731
-
732
- return {
733
- typeName: 'refAlias',
734
- default: getJSDocTagComment(declaration, 'default'),
735
- description: this.getNodeDescription(declaration),
736
- refName: refName,
737
- format: TypeNodeResolver.getNodeFormat(declaration),
738
- type: type,
739
- validators: getPropertyValidators(declaration) || {},
740
- deprecated: isExistJSDocTag(declaration, tag => tag.tagName.text === 'deprecated'),
741
- ...(example && { example: example }),
742
- };
743
- }
744
-
745
- private getModelReference(modelType: ts.InterfaceDeclaration | ts.ClassDeclaration, name: string, utilityType?: UtilityType, utilityOptions?: UtilityOptions) : Resolver.ReferenceType {
746
- const example = TypeNodeResolver.getNodeExample(modelType);
747
- const description : string = this.getNodeDescription(modelType);
748
- const deprecated : boolean = isExistJSDocTag(modelType, tag => tag.tagName.text === 'deprecated') || typeof this.current.decoratorMapper.match('DEPRECATED', modelType) !== 'undefined';
749
-
750
- // Handle toJSON methods
751
- if (!modelType.name) {
752
- throw new ResolverError("Can't get Symbol from anonymous class", modelType);
753
- }
754
- const type = this.current.typeChecker.getTypeAtLocation(modelType.name);
755
- const toJSON = this.current.typeChecker.getPropertyOfType(type, 'toJSON');
756
- if (toJSON && toJSON.valueDeclaration && (ts.isMethodDeclaration(toJSON.valueDeclaration) || ts.isMethodSignature(toJSON.valueDeclaration))) {
757
- let nodeType = toJSON.valueDeclaration.type;
758
- if (!nodeType) {
759
- const signature = this.current.typeChecker.getSignatureFromDeclaration(toJSON.valueDeclaration);
760
- const implicitType = this.current.typeChecker.getReturnTypeOfSignature(signature!);
761
- nodeType = this.current.typeChecker.typeToTypeNode(implicitType, undefined, ts.NodeBuilderFlags.NoTruncation) as ts.TypeNode;
762
- }
763
-
764
- return {
765
- refName: TypeNodeResolver.getRefTypeName(name, utilityType)+'Alias',
766
- typeName: 'refAlias',
767
- description: description,
768
- type: new TypeNodeResolver(nodeType, this.current).resolve(),
769
- deprecated: deprecated,
770
- validators: {},
771
- ...(example && { example: example }),
772
- };
773
- }
774
-
775
- const properties = this.getModelProperties(modelType, undefined, utilityType, utilityOptions);
776
- const additionalProperties = this.getModelAdditionalProperties(modelType);
777
- const inheritedProperties = this.getModelInheritedProperties(modelType) || [];
778
-
779
- const referenceType: Resolver.ReferenceType & { properties: Property[] } = {
780
- additionalProperties: additionalProperties,
781
- typeName: 'refObject',
782
- description: description,
783
- properties: this.filterUtilityProperties(inheritedProperties, utilityType, utilityOptions),
784
- refName: TypeNodeResolver.getRefTypeName(name, utilityType),
785
- deprecated: deprecated,
786
- ...(example && { example: example }),
787
- };
788
-
789
- referenceType.properties = referenceType.properties.concat(properties);
790
-
791
- return referenceType;
792
- }
793
-
794
- private static getRefTypeName(name: string, utilityType?: UtilityType): string {
795
- return encodeURIComponent(
796
- name
797
- .replace(/<|>/g, '_')
798
- .replace(/\s+/g, '')
799
- .replace(/,/g, '.')
800
- .replace(/\'([^']*)\'/g, '$1')
801
- .replace(/\"([^"]*)\"/g, '$1')
802
- .replace(/&/g, typeof utilityType !== 'undefined' ? '--' : '-and-')
803
- .replace(/\|/g, typeof utilityType !== 'undefined' ? '--' : '-or-')
804
- .replace(/\[\]/g, '-array')
805
- .replace(/{|}/g, '_') // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__
806
- .replace(/([a-z]+):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_
807
- .replace(/;/g, '--')
808
- .replace(/([a-z]+)\[([a-z]+)\]/gi, '$1-at-$2') // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_,
809
-
810
- .replace(/_/g, '')
811
- .replace(/-/g, '')
812
- );
813
- }
814
-
815
- private attemptToResolveKindToPrimitive = (syntaxKind: ts.SyntaxKind) : 'number' | 'string' | 'boolean' | 'void' | undefined => {
816
- if (syntaxKind === ts.SyntaxKind.NumberKeyword) {
817
- return 'number';
818
- } else if (syntaxKind === ts.SyntaxKind.StringKeyword) {
819
- return 'string';
820
- } else if (syntaxKind === ts.SyntaxKind.BooleanKeyword) {
821
- return 'boolean';
822
- } else if (syntaxKind === ts.SyntaxKind.VoidKeyword) {
823
- return 'void';
824
- } else {
825
- return undefined;
826
- }
827
- }
828
-
829
- private contextualizedName(name: string): string {
830
- return Object.entries(this.context).reduce((acc, [key, entry]) => {
831
- return acc
832
- .replace(new RegExp(`<\\s*([^>]*\\s)*\\s*(${key})(\\s[^>]*)*\\s*>`, 'g'), `<$1${entry.getText()}$3>`)
833
- .replace(new RegExp(`<\\s*([^,]*\\s)*\\s*(${key})(\\s[^,]*)*\\s*,`, 'g'), `<$1${entry.getText()}$3,`)
834
- .replace(new RegExp(`,\\s*([^>]*\\s)*\\s*(${key})(\\s[^>]*)*\\s*>`, 'g'), `,$1${entry.getText()}$3>`)
835
- .replace(new RegExp(`<\\s*([^<]*\\s)*\\s*(${key})(\\s[^<]*)*\\s*<`, 'g'), `<$1${entry.getText()}$3<`);
836
- }, name);
837
- }
838
-
839
- private handleCachingAndCircularReferences(name: string, declarationResolver: () => Resolver.ReferenceType): Resolver.ReferenceType {
840
- try {
841
- const existingType = localReferenceTypeCache[name];
842
- if (existingType) {
843
- return existingType;
844
- }
845
-
846
- if (inProgressTypes[name]) {
847
- return this.createCircularDependencyResolver(name);
848
- }
849
-
850
- inProgressTypes[name] = true;
851
-
852
- const reference = declarationResolver();
853
-
854
- localReferenceTypeCache[name] = reference;
855
-
856
- this.current.addReferenceType(reference);
857
-
858
- return reference;
859
- } catch (err) {
860
- // eslint-disable-next-line no-console
861
- console.error(`There was a problem resolving type of '${name}'.`);
862
- throw err;
863
- }
864
- }
865
-
866
- private createCircularDependencyResolver(refName: string) {
867
- const referenceType = {
868
- typeName: 'refObject',
869
- refName: refName,
870
- } as Resolver.ReferenceType;
871
-
872
- this.current.registerDependencyResolver(referenceTypes => {
873
- const realReferenceType : Resolver.ReferenceType | undefined = referenceTypes[refName];
874
- if (!realReferenceType) {
875
- return;
876
- }
877
-
878
- referenceType.description = realReferenceType.description;
879
- if (realReferenceType.typeName === 'refObject' && referenceType.typeName === 'refObject') {
880
- referenceType.properties = realReferenceType.properties;
881
- }
882
- referenceType.typeName = realReferenceType.typeName;
883
- referenceType.refName = realReferenceType.refName;
884
- });
885
-
886
- return referenceType;
887
- }
888
-
889
- private static nodeIsUsable(node: ts.Node) {
890
- switch (node.kind) {
891
- case ts.SyntaxKind.InterfaceDeclaration:
892
- case ts.SyntaxKind.ClassDeclaration:
893
- case ts.SyntaxKind.TypeAliasDeclaration:
894
- case ts.SyntaxKind.EnumDeclaration:
895
- case ts.SyntaxKind.EnumMember:
896
- return true;
897
- default:
898
- return false;
899
- }
900
- }
901
-
902
- // @ts-ignore
903
- private static resolveLeftmostIdentifier(type: ts.EntityName): ts.Identifier {
904
- while (type.kind !== ts.SyntaxKind.Identifier) {
905
- type = type.left;
906
- }
907
- return type;
908
- }
909
-
910
- // @ts-ignore
911
- private static resolveRightMostIdentifier(type: ts.EntityName) : ts.Identifier {
912
- while(type.kind !== ts.SyntaxKind.Identifier) {
913
- type = type.right;
914
- }
915
-
916
- return type;
917
- }
918
-
919
- private findMatchingModuleOrEnumDeclaration(statements: ts.Node[], name: string) : Array<ts.ModuleDeclaration | ts.EnumDeclaration> {
920
- return statements.filter((node: ts.Node) => {
921
- if ((node.kind !== ts.SyntaxKind.ModuleDeclaration || !this.current.isExportedNode(node)) && !ts.isEnumDeclaration(node)) {
922
- return false;
923
- }
924
-
925
- const moduleDeclaration = node as ts.ModuleDeclaration | ts.EnumDeclaration;
926
-
927
- return (moduleDeclaration.name as ts.Identifier).text.toLowerCase() === name.toLowerCase();
928
- }) as Array<ts.ModuleDeclaration | ts.EnumDeclaration>;
929
- }
930
-
931
- private resolveModelTypeScope(
932
- leftmost: ts.EntityName,
933
- statements: ts.Node[]
934
- ) : Array<ts.ModuleDeclaration | ts.EnumDeclaration | ts.Node> {
935
- while (leftmost.parent && leftmost.parent.kind === ts.SyntaxKind.QualifiedName) {
936
- const leftmostName : string = leftmost.kind === ts.SyntaxKind.Identifier ? leftmost.text : leftmost.right.text;
937
- const moduleDeclarations : Array<ts.ModuleDeclaration | ts.EnumDeclaration> = this.findMatchingModuleOrEnumDeclaration(statements, leftmostName);
938
-
939
- if (moduleDeclarations.length > 0) {
940
- statements = Array.prototype.concat(
941
- ...moduleDeclarations.map(declaration => {
942
- if (ts.isEnumDeclaration(declaration)) {
943
- return declaration.members;
944
- } else {
945
- if (!declaration.body || !ts.isModuleBlock(declaration.body)) {
946
- throw new ResolverError(`Module declaration found for ${leftmostName} has no body.`);
947
- }
948
- return declaration.body.statements;
949
- }
950
- }),
951
- );
952
- }
953
-
954
- leftmost = leftmost.parent as ts.EntityName;
955
- }
956
-
957
- return statements;
958
- }
959
-
960
- private getModelTypeDeclaration(type: ts.EntityName) {
961
- type UsableDeclarationWithoutPropertySignature = Exclude<UsableDeclaration, ts.PropertySignature>;
962
-
963
- const leftmostIdentifier = TypeNodeResolver.resolveLeftmostIdentifier(type);
964
- const statements : ts.Node[] = this.resolveModelTypeScope(leftmostIdentifier, this.current.nodes);
965
-
966
- const typeName = type.kind === ts.SyntaxKind.Identifier ? type.text : type.right.text;
967
-
968
- let modelTypes = statements.filter(node => {
969
- if (!TypeNodeResolver.nodeIsUsable(node) || !this.current.isExportedNode(node)) {
970
- return false;
971
- }
972
-
973
- const modelTypeDeclaration = node as UsableDeclaration;
974
- return (modelTypeDeclaration.name as ts.Identifier)?.text === typeName;
975
- }) as UsableDeclarationWithoutPropertySignature[];
976
-
977
- if (!modelTypes.length) {
978
- throw new ResolverError(
979
- `No matching model found for referenced type ${typeName}. If ${typeName} comes from a dependency, please create an interface in your own code that has the same structure. The compiler can not utilize interfaces from external dependencies.`,
980
- );
981
- }
982
-
983
- if (modelTypes.length > 1) {
984
- // remove types that are from typescript e.g. 'Account'
985
- modelTypes = modelTypes.filter(modelType => {
986
- return modelType.getSourceFile().fileName.replace(/\\/g, '/').toLowerCase().indexOf('node_modules') <= -1;
987
- });
988
-
989
- modelTypes = TypeNodeResolver.getDesignatedModels(modelTypes, typeName) as UsableDeclarationWithoutPropertySignature[];
990
- }
991
- if (modelTypes.length > 1) {
992
- const conflicts = modelTypes.map(modelType => modelType.getSourceFile().fileName).join('"; "');
993
- throw new ResolverError(`Multiple matching models found for referenced type ${typeName}; please make model names unique. Conflicts found: "${conflicts}".`);
994
- }
995
-
996
- return modelTypes[0];
997
- }
998
-
999
- private getModelProperties(
1000
- node: ts.InterfaceDeclaration | ts.ClassDeclaration,
1001
- overrideToken?: OverrideToken,
1002
- utilityType?: UtilityType,
1003
- utilityOptions?: UtilityOptions
1004
- ) : Property[] {
1005
- const isIgnored = (e: ts.TypeElement | ts.ClassElement) => {
1006
- return isExistJSDocTag(e, tag => tag.tagName.text === 'ignore');
1007
- };
1008
-
1009
- // Interface model
1010
- if (ts.isInterfaceDeclaration(node)) {
1011
- return node.members.filter(member => !isIgnored(member) && ts.isPropertySignature(member)).map((member: ts.PropertySignature) => this.propertyFromSignature(member, overrideToken));
1012
- }
1013
-
1014
- // Class model
1015
- let properties = node.members
1016
- .filter(member => !isIgnored(member))
1017
- .filter(member => member.kind === ts.SyntaxKind.PropertyDeclaration)
1018
- .filter(member => !this.hasStaticModifier(member))
1019
- .filter(member => this.hasPublicModifier(member)) as Array<ts.PropertyDeclaration | ts.ParameterDeclaration>;
1020
-
1021
- const classConstructor = node.members.find(member => ts.isConstructorDeclaration(member)) as ts.ConstructorDeclaration;
1022
-
1023
- if (classConstructor && classConstructor.parameters) {
1024
- const constructorProperties = classConstructor.parameters.filter(parameter => this.isAccessibleParameter(parameter));
1025
-
1026
- properties.push(...constructorProperties);
1027
- }
1028
-
1029
- properties = this.filterUtilityProperties(properties, utilityType, utilityOptions);
1030
-
1031
- return properties.map(property => this.propertyFromDeclaration(property, overrideToken, utilityType));
1032
- }
1033
-
1034
- private propertyFromSignature(propertySignature: ts.PropertySignature, overrideToken?: OverrideToken) {
1035
- const identifier = propertySignature.name as ts.Identifier;
1036
-
1037
- if (!propertySignature.type) {
1038
- throw new ResolverError(`No valid type found for property declaration.`);
1039
- }
1040
-
1041
- let required = !propertySignature.questionToken;
1042
- if (overrideToken && overrideToken.kind === ts.SyntaxKind.MinusToken) {
1043
- required = true;
1044
- } else if (overrideToken && overrideToken.kind === ts.SyntaxKind.QuestionToken) {
1045
- required = false;
1046
- }
1047
-
1048
- const property: Property = {
1049
- deprecated: isExistJSDocTag(propertySignature, tag => tag.tagName.text === 'deprecated'),
1050
- default: getJSDocTagComment(propertySignature, 'default'),
1051
- description: this.getNodeDescription(propertySignature),
1052
- example: TypeNodeResolver.getNodeExample(propertySignature),
1053
- format: TypeNodeResolver.getNodeFormat(propertySignature),
1054
- name: identifier.text,
1055
- required: required,
1056
- type: new TypeNodeResolver(propertySignature.type, this.current, propertySignature.type.parent, this.context, propertySignature.type).resolve(),
1057
- validators: getPropertyValidators(propertySignature) || {}
1058
- };
1059
- return property;
1060
- }
1061
-
1062
- private propertyFromDeclaration(propertyDeclaration: ts.PropertyDeclaration | ts.ParameterDeclaration, overrideToken?: OverrideToken, utilityType?: string) {
1063
- const identifier = propertyDeclaration.name as ts.Identifier;
1064
- let typeNode = propertyDeclaration.type;
1065
-
1066
- if (!typeNode) {
1067
- const tsType = this.current.typeChecker.getTypeAtLocation(propertyDeclaration);
1068
- typeNode = this.current.typeChecker.typeToTypeNode(tsType, undefined, ts.NodeBuilderFlags.NoTruncation);
1069
- }
1070
-
1071
- if (!typeNode) {
1072
- throw new ResolverError(`No valid type found for property declaration.`);
1073
- }
1074
-
1075
- const type = new TypeNodeResolver(typeNode, this.current, propertyDeclaration, this.context, typeNode).resolve();
1076
-
1077
- let required = !propertyDeclaration.questionToken && !propertyDeclaration.initializer;
1078
- if (overrideToken && overrideToken.kind === ts.SyntaxKind.MinusToken) {
1079
- required = true;
1080
- } else if (overrideToken && overrideToken.kind === ts.SyntaxKind.QuestionToken) {
1081
- required = false;
1082
- }
1083
-
1084
- if(typeof utilityType !== 'undefined') {
1085
- if(utilityType === 'Partial') {
1086
- required = false;
1087
- }
1088
-
1089
- if(utilityType === 'Required') {
1090
- required = true;
1091
- }
1092
- }
1093
-
1094
- const property: Property = {
1095
- deprecated: isExistJSDocTag(propertyDeclaration, tag => tag.tagName.text === 'deprecated'),
1096
- default: getInitializerValue(propertyDeclaration.initializer, this.current.typeChecker),
1097
- description: this.getNodeDescription(propertyDeclaration),
1098
- example: TypeNodeResolver.getNodeExample(propertyDeclaration),
1099
- format: TypeNodeResolver.getNodeFormat(propertyDeclaration),
1100
- name: identifier.text,
1101
- required: required,
1102
- type: type,
1103
- validators: getPropertyValidators(propertyDeclaration) || {},
1104
- };
1105
- return property;
1106
- }
1107
-
1108
- private getModelAdditionalProperties(node: UsableDeclaration) {
1109
- if (node.kind === ts.SyntaxKind.InterfaceDeclaration) {
1110
- const indexMember = node.members.find(member => member.kind === ts.SyntaxKind.IndexSignature);
1111
- if (!indexMember) {
1112
- return undefined;
1113
- }
1114
-
1115
- const indexSignatureDeclaration = indexMember as ts.IndexSignatureDeclaration;
1116
- const indexType = new TypeNodeResolver(indexSignatureDeclaration.parameters[0].type as ts.TypeNode, this.current, this.parentNode, this.context).resolve();
1117
- if (indexType.typeName !== 'string') {
1118
- throw new ResolverError(`Only string indexers are supported.`, this.typeNode);
1119
- }
1120
-
1121
- return new TypeNodeResolver(indexSignatureDeclaration.type, this.current, this.parentNode, this.context).resolve();
1122
- }
1123
-
1124
- return undefined;
1125
- }
1126
-
1127
- private typeArgumentsToContext(type: ts.TypeReferenceNode | ts.ExpressionWithTypeArguments, targetEntity: ts.EntityName, context: TypeNodeResolverContext): TypeNodeResolverContext {
1128
- // this.context = {};
1129
-
1130
- const declaration = this.getModelTypeDeclaration(targetEntity);
1131
- const typeParameters = 'typeParameters' in declaration ? declaration.typeParameters : undefined;
1132
-
1133
- if (typeParameters) {
1134
- for (let index = 0; index < typeParameters.length; index++) {
1135
- const typeParameter = typeParameters[index];
1136
- const typeArg = type.typeArguments && type.typeArguments[index];
1137
- let resolvedType: ts.TypeNode;
1138
-
1139
- // Argument may be a forward reference from context
1140
- if (typeArg && ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName) && context[typeArg.typeName.text]) {
1141
- resolvedType = context[typeArg.typeName.text];
1142
- } else if (typeArg) {
1143
- resolvedType = typeArg;
1144
- } else if (typeParameter.default) {
1145
- resolvedType = typeParameter.default;
1146
- } else {
1147
- throw new ResolverError(`Could not find a value for type parameter ${typeParameter.name.text}`, type);
1148
- }
1149
-
1150
- this.context = {
1151
- ...this.context,
1152
- [typeParameter.name.text]: resolvedType,
1153
- };
1154
- }
1155
- }
1156
- return context;
1157
- }
1158
-
1159
- private getModelInheritedProperties(
1160
- modelTypeDeclaration: Exclude<UsableDeclaration, ts.PropertySignature | ts.TypeAliasDeclaration | ts.EnumMember>
1161
- ): Property[] {
1162
- let properties: Property[] = [];
1163
-
1164
- const heritageClauses = modelTypeDeclaration.heritageClauses;
1165
- if (!heritageClauses) {
1166
- return properties;
1167
- }
1168
-
1169
- heritageClauses.forEach(clause => {
1170
- if (!clause.types) {
1171
- return;
1172
- }
1173
-
1174
- clause.types.forEach(t => {
1175
- const baseEntityName = t.expression as ts.EntityName;
1176
-
1177
- // create subContext
1178
- const resetCtx = this.typeArgumentsToContext(t, baseEntityName, this.context);
1179
-
1180
- const referenceType = this.getReferenceType(t);
1181
- if (referenceType) {
1182
- if (referenceType.typeName === 'refEnum') {
1183
- // since it doesn't have properties to iterate over, then we don't do anything with it
1184
- } else if (referenceType.typeName === 'refAlias') {
1185
- let type: Resolver.Type = referenceType;
1186
- while (type.typeName === 'refAlias') {
1187
- type = type.type;
1188
- }
1189
-
1190
- if (type.typeName === 'refObject') {
1191
- properties = [...properties, ...type.properties];
1192
- } else if (type.typeName === 'nestedObjectLiteral') {
1193
- properties = [...properties, ...type.properties];
1194
- }
1195
- } else if (referenceType.typeName === 'refObject') {
1196
- referenceType.properties.forEach(property => properties.push(property));
1197
- } else {
1198
- // todo: should never assert
1199
- }
1200
- }
1201
-
1202
- // reset subContext
1203
- this.context = resetCtx;
1204
- });
1205
- });
1206
-
1207
- return properties;
1208
- }
1209
-
1210
- private hasPublicModifier(node: ts.Node) {
1211
- return (
1212
- !node.modifiers ||
1213
- node.modifiers.every(modifier => {
1214
- return modifier.kind !== ts.SyntaxKind.ProtectedKeyword && modifier.kind !== ts.SyntaxKind.PrivateKeyword;
1215
- })
1216
- );
1217
- }
1218
-
1219
- private hasStaticModifier(node: ts.Node) {
1220
- return (
1221
- node.modifiers &&
1222
- node.modifiers.some(modifier => {
1223
- return modifier.kind === ts.SyntaxKind.StaticKeyword;
1224
- })
1225
- );
1226
- }
1227
-
1228
- private isAccessibleParameter(node: ts.Node) {
1229
- // No modifiers
1230
- if (!node.modifiers) {
1231
- return false;
1232
- }
1233
-
1234
- // public || public readonly
1235
- if (node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.PublicKeyword)) {
1236
- return true;
1237
- }
1238
-
1239
- // readonly, not private readonly, not public readonly
1240
- const isReadonly = node.modifiers.some(modifier => modifier.kind === ts.SyntaxKind.ReadonlyKeyword);
1241
- const isProtectedOrPrivate = node.modifiers.some(modifier => {
1242
- return modifier.kind === ts.SyntaxKind.ProtectedKeyword || modifier.kind === ts.SyntaxKind.PrivateKeyword;
1243
- });
1244
- return isReadonly && !isProtectedOrPrivate;
1245
- }
1246
-
1247
- private getNodeDescription(node: UsableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.EnumDeclaration) {
1248
- const symbol = this.current.typeChecker.getSymbolAtLocation(node.name as ts.Node);
1249
- if (!symbol) {
1250
- return undefined;
1251
- }
1252
-
1253
- /**
1254
- * TODO: Workaround for what seems like a bug in the compiler
1255
- * Warrants more investigation and possibly a PR against typescript
1256
- */
1257
- if (node.kind === ts.SyntaxKind.Parameter) {
1258
- // TypeScript won't parse jsdoc if the flag is 4, i.e. 'Property'
1259
- symbol.flags = 0;
1260
- }
1261
-
1262
- const comments = symbol.getDocumentationComment(this.current.typeChecker);
1263
- if (comments.length) {
1264
- return ts.displayPartsToString(comments);
1265
- }
1266
-
1267
- return undefined;
1268
- }
1269
-
1270
- private static getNodeFormat(node: UsableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.EnumDeclaration) {
1271
- return getJSDocTagComment(node, 'format');
1272
- }
1273
-
1274
- private static getNodeExample(node: UsableDeclaration | ts.PropertyDeclaration | ts.ParameterDeclaration | ts.EnumDeclaration) {
1275
- const example = getJSDocTagComment(node, 'example');
1276
-
1277
- if (example) {
1278
- try {
1279
- return JSON.parse(example);
1280
- } catch {
1281
- return undefined;
1282
- }
1283
- } else {
1284
- return undefined;
1285
- }
1286
- }
1287
- }