@trapi/metadata 0.1.4 → 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.
- package/package.json +11 -8
- package/src/cache/driver.ts +0 -97
- package/src/cache/index.ts +0 -10
- package/src/cache/type.ts +0 -43
- package/src/cache/utils.ts +0 -49
- package/src/config/index.ts +0 -8
- package/src/config/path.ts +0 -29
- package/src/decorator/functions.ts +0 -142
- package/src/decorator/index.ts +0 -13
- package/src/decorator/mapper/index.ts +0 -153
- package/src/decorator/mapper/maps/decorators-express.ts +0 -117
- package/src/decorator/mapper/maps/internal.ts +0 -95
- package/src/decorator/mapper/maps/typescript-rest.ts +0 -150
- package/src/decorator/mapper/utils.ts +0 -88
- package/src/decorator/representation/index.ts +0 -82
- package/src/decorator/representation/property/utils.ts +0 -146
- package/src/decorator/type.ts +0 -235
- package/src/decorator/utils/index.ts +0 -9
- package/src/decorator/utils/node.ts +0 -59
- package/src/decorator/utils/validator.ts +0 -104
- package/src/generator/controller.ts +0 -65
- package/src/generator/endpoint.ts +0 -212
- package/src/generator/index.ts +0 -219
- package/src/generator/method.ts +0 -198
- package/src/generator/parameter.ts +0 -392
- package/src/index.ts +0 -13
- package/src/resolver/error.ts +0 -38
- package/src/resolver/index.ts +0 -11
- package/src/resolver/type-node.ts +0 -1287
- package/src/resolver/type.ts +0 -257
- package/src/resolver/utils/index.ts +0 -9
- package/src/resolver/utils/initializer.ts +0 -84
- package/src/resolver/utils/validator.ts +0 -144
- package/src/type.ts +0 -185
- package/src/utils/generator.ts +0 -42
- package/src/utils/index.ts +0 -10
- package/src/utils/js-doc.ts +0 -125
- package/src/utils/validator.ts +0 -43
- package/test/data/library/@decorators-express/decorators.ts +0 -7
- package/test/data/library/self/api.ts +0 -37
- package/test/data/library/type.ts +0 -138
- package/test/data/library/typescript-rest/api.ts +0 -366
- package/test/data/library/typescript-rest/decorators/index.ts +0 -10
- package/test/data/library/typescript-rest/decorators/methods.ts +0 -34
- package/test/data/library/typescript-rest/decorators/parameters.ts +0 -62
- package/test/data/library/typescript-rest/decorators/services.ts +0 -47
- package/test/data/library/typescript-rest/return-types.ts +0 -94
- package/test/data/library/utils.ts +0 -7
- package/test/jest.config.js +0 -49
- package/test/unit/cache.spec.ts +0 -48
- package/test/unit/decorator/mapper/index.spec.ts +0 -83
- package/test/unit/decorator/representation/index.spec.ts +0 -102
- package/test/unit/decorator/utils/node.spec.ts +0 -47
- package/test/unit/library/typescript-rest.spec.ts +0 -342
- package/test/unit/resolver/type.spec.ts +0 -134
- package/test/unit/utils/generator.spec.ts +0 -41
- package/test/unit/utils/js-doc.spec.ts +0 -104
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -9
- 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
|
-
}
|