@tsoa-next/cli 7.1.0
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/README.MD +3 -0
- package/dist/cli.d.ts +44 -0
- package/dist/cli.js +356 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/metadataGeneration/controllerGenerator.d.ts +30 -0
- package/dist/metadataGeneration/controllerGenerator.js +229 -0
- package/dist/metadataGeneration/controllerGenerator.js.map +1 -0
- package/dist/metadataGeneration/exceptions.d.ts +13 -0
- package/dist/metadataGeneration/exceptions.js +53 -0
- package/dist/metadataGeneration/exceptions.js.map +1 -0
- package/dist/metadataGeneration/extension.d.ts +5 -0
- package/dist/metadataGeneration/extension.js +85 -0
- package/dist/metadataGeneration/extension.js.map +1 -0
- package/dist/metadataGeneration/initializer-value.d.ts +6 -0
- package/dist/metadataGeneration/initializer-value.js +154 -0
- package/dist/metadataGeneration/initializer-value.js.map +1 -0
- package/dist/metadataGeneration/metadataGenerator.d.ts +29 -0
- package/dist/metadataGeneration/metadataGenerator.js +220 -0
- package/dist/metadataGeneration/metadataGenerator.js.map +1 -0
- package/dist/metadataGeneration/methodGenerator.d.ts +45 -0
- package/dist/metadataGeneration/methodGenerator.js +367 -0
- package/dist/metadataGeneration/methodGenerator.js.map +1 -0
- package/dist/metadataGeneration/parameterGenerator.d.ts +33 -0
- package/dist/metadataGeneration/parameterGenerator.js +552 -0
- package/dist/metadataGeneration/parameterGenerator.js.map +1 -0
- package/dist/metadataGeneration/transformer/dateTransformer.d.ts +6 -0
- package/dist/metadataGeneration/transformer/dateTransformer.js +28 -0
- package/dist/metadataGeneration/transformer/dateTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/enumTransformer.d.ts +12 -0
- package/dist/metadataGeneration/transformer/enumTransformer.js +75 -0
- package/dist/metadataGeneration/transformer/enumTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/primitiveTransformer.d.ts +11 -0
- package/dist/metadataGeneration/transformer/primitiveTransformer.js +70 -0
- package/dist/metadataGeneration/transformer/primitiveTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/propertyTransformer.d.ts +12 -0
- package/dist/metadataGeneration/transformer/propertyTransformer.js +101 -0
- package/dist/metadataGeneration/transformer/propertyTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/referenceTransformer.d.ts +10 -0
- package/dist/metadataGeneration/transformer/referenceTransformer.js +81 -0
- package/dist/metadataGeneration/transformer/referenceTransformer.js.map +1 -0
- package/dist/metadataGeneration/transformer/transformer.d.ts +9 -0
- package/dist/metadataGeneration/transformer/transformer.js +39 -0
- package/dist/metadataGeneration/transformer/transformer.js.map +1 -0
- package/dist/metadataGeneration/typeResolver.d.ts +52 -0
- package/dist/metadataGeneration/typeResolver.js +1202 -0
- package/dist/metadataGeneration/typeResolver.js.map +1 -0
- package/dist/module/generate-routes.d.ts +9 -0
- package/dist/module/generate-routes.js +90 -0
- package/dist/module/generate-routes.js.map +1 -0
- package/dist/module/generate-spec.d.ts +9 -0
- package/dist/module/generate-spec.js +79 -0
- package/dist/module/generate-spec.js.map +1 -0
- package/dist/routeGeneration/defaultRouteGenerator.d.ts +12 -0
- package/dist/routeGeneration/defaultRouteGenerator.js +119 -0
- package/dist/routeGeneration/defaultRouteGenerator.js.map +1 -0
- package/dist/routeGeneration/routeGenerator.d.ts +56 -0
- package/dist/routeGeneration/routeGenerator.js +257 -0
- package/dist/routeGeneration/routeGenerator.js.map +1 -0
- package/dist/routeGeneration/templates/express.hbs +221 -0
- package/dist/routeGeneration/templates/hapi.hbs +267 -0
- package/dist/routeGeneration/templates/koa.hbs +218 -0
- package/dist/swagger/specGenerator.d.ts +33 -0
- package/dist/swagger/specGenerator.js +253 -0
- package/dist/swagger/specGenerator.js.map +1 -0
- package/dist/swagger/specGenerator2.d.ts +27 -0
- package/dist/swagger/specGenerator2.js +476 -0
- package/dist/swagger/specGenerator2.js.map +1 -0
- package/dist/swagger/specGenerator3.d.ts +158 -0
- package/dist/swagger/specGenerator3.js +646 -0
- package/dist/swagger/specGenerator3.js.map +1 -0
- package/dist/swagger/specGenerator31.d.ts +24 -0
- package/dist/swagger/specGenerator31.js +75 -0
- package/dist/swagger/specGenerator31.js.map +1 -0
- package/dist/utils/decoratorUtils.d.ts +9 -0
- package/dist/utils/decoratorUtils.js +118 -0
- package/dist/utils/decoratorUtils.js.map +1 -0
- package/dist/utils/flowUtils.d.ts +1 -0
- package/dist/utils/flowUtils.js +8 -0
- package/dist/utils/flowUtils.js.map +1 -0
- package/dist/utils/fs.d.ts +5 -0
- package/dist/utils/fs.js +55 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/genericTypeGuards.d.ts +1 -0
- package/dist/utils/genericTypeGuards.js +8 -0
- package/dist/utils/genericTypeGuards.js.map +1 -0
- package/dist/utils/headerTypeHelpers.d.ts +5 -0
- package/dist/utils/headerTypeHelpers.js +27 -0
- package/dist/utils/headerTypeHelpers.js.map +1 -0
- package/dist/utils/importClassesFromDirectories.d.ts +4 -0
- package/dist/utils/importClassesFromDirectories.js +20 -0
- package/dist/utils/importClassesFromDirectories.js.map +1 -0
- package/dist/utils/internalTypeGuards.d.ts +5 -0
- package/dist/utils/internalTypeGuards.js +66 -0
- package/dist/utils/internalTypeGuards.js.map +1 -0
- package/dist/utils/isVoidType.d.ts +2 -0
- package/dist/utils/isVoidType.js +16 -0
- package/dist/utils/isVoidType.js.map +1 -0
- package/dist/utils/jsDocUtils.d.ts +8 -0
- package/dist/utils/jsDocUtils.js +122 -0
- package/dist/utils/jsDocUtils.js.map +1 -0
- package/dist/utils/jsonUtils.d.ts +1 -0
- package/dist/utils/jsonUtils.js +12 -0
- package/dist/utils/jsonUtils.js.map +1 -0
- package/dist/utils/pathUtils.d.ts +9 -0
- package/dist/utils/pathUtils.js +37 -0
- package/dist/utils/pathUtils.js.map +1 -0
- package/dist/utils/specMerge.d.ts +2 -0
- package/dist/utils/specMerge.js +36 -0
- package/dist/utils/specMerge.js.map +1 -0
- package/dist/utils/swaggerUtils.d.ts +3 -0
- package/dist/utils/swaggerUtils.js +22 -0
- package/dist/utils/swaggerUtils.js.map +1 -0
- package/dist/utils/unspecifiedObject.d.ts +3 -0
- package/dist/utils/unspecifiedObject.js +3 -0
- package/dist/utils/unspecifiedObject.js.map +1 -0
- package/dist/utils/validatorUtils.d.ts +5 -0
- package/dist/utils/validatorUtils.js +241 -0
- package/dist/utils/validatorUtils.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,1202 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.TypeResolver = void 0;
|
|
37
|
+
const runtime_1 = require("@tsoa-next/runtime");
|
|
38
|
+
const ts = __importStar(require("typescript"));
|
|
39
|
+
const jsonUtils_1 = require("../utils/jsonUtils");
|
|
40
|
+
const decoratorUtils_1 = require("./../utils/decoratorUtils");
|
|
41
|
+
const jsDocUtils_1 = require("./../utils/jsDocUtils");
|
|
42
|
+
const validatorUtils_1 = require("./../utils/validatorUtils");
|
|
43
|
+
const flowUtils_1 = require("../utils/flowUtils");
|
|
44
|
+
const exceptions_1 = require("./exceptions");
|
|
45
|
+
const extension_1 = require("./extension");
|
|
46
|
+
const initializer_value_1 = require("./initializer-value");
|
|
47
|
+
const primitiveTransformer_1 = require("./transformer/primitiveTransformer");
|
|
48
|
+
const dateTransformer_1 = require("./transformer/dateTransformer");
|
|
49
|
+
const enumTransformer_1 = require("./transformer/enumTransformer");
|
|
50
|
+
const propertyTransformer_1 = require("./transformer/propertyTransformer");
|
|
51
|
+
const referenceTransformer_1 = require("./transformer/referenceTransformer");
|
|
52
|
+
const localReferenceTypeCache = {};
|
|
53
|
+
const inProgressTypes = {};
|
|
54
|
+
class TypeResolver {
|
|
55
|
+
typeNode;
|
|
56
|
+
current;
|
|
57
|
+
parentNode;
|
|
58
|
+
context;
|
|
59
|
+
referencer;
|
|
60
|
+
constructor(typeNode, current, parentNode, context = {}, referencer) {
|
|
61
|
+
this.typeNode = typeNode;
|
|
62
|
+
this.current = current;
|
|
63
|
+
this.parentNode = parentNode;
|
|
64
|
+
this.context = context;
|
|
65
|
+
this.referencer = referencer;
|
|
66
|
+
}
|
|
67
|
+
static clearCache() {
|
|
68
|
+
Object.keys(localReferenceTypeCache).forEach(key => {
|
|
69
|
+
delete localReferenceTypeCache[key];
|
|
70
|
+
});
|
|
71
|
+
Object.keys(inProgressTypes).forEach(key => {
|
|
72
|
+
delete inProgressTypes[key];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
resolve() {
|
|
76
|
+
const partentJsDocTagNames = this.parentNode ? (0, jsDocUtils_1.getJSDocTagNames)(this.parentNode) : undefined;
|
|
77
|
+
const primitiveType = new primitiveTransformer_1.PrimitiveTransformer().transform(this.current.defaultNumberType, this.typeNode, partentJsDocTagNames);
|
|
78
|
+
if (primitiveType) {
|
|
79
|
+
return primitiveType;
|
|
80
|
+
}
|
|
81
|
+
if (ts.isArrayTypeNode(this.typeNode)) {
|
|
82
|
+
const arrayMetaType = {
|
|
83
|
+
dataType: 'array',
|
|
84
|
+
elementType: new TypeResolver(this.typeNode.elementType, this.current, this.parentNode, this.context).resolve(),
|
|
85
|
+
};
|
|
86
|
+
return arrayMetaType;
|
|
87
|
+
}
|
|
88
|
+
if (ts.isRestTypeNode(this.typeNode)) {
|
|
89
|
+
return new TypeResolver(this.typeNode.type, this.current, this.parentNode, this.context).resolve();
|
|
90
|
+
}
|
|
91
|
+
if (ts.isUnionTypeNode(this.typeNode)) {
|
|
92
|
+
const types = this.typeNode.types.map(type => {
|
|
93
|
+
return new TypeResolver(type, this.current, this.parentNode, this.context).resolve();
|
|
94
|
+
});
|
|
95
|
+
const unionMetaType = {
|
|
96
|
+
dataType: 'union',
|
|
97
|
+
types,
|
|
98
|
+
};
|
|
99
|
+
return unionMetaType;
|
|
100
|
+
}
|
|
101
|
+
if (ts.isIntersectionTypeNode(this.typeNode)) {
|
|
102
|
+
const types = this.typeNode.types.map(type => {
|
|
103
|
+
return new TypeResolver(type, this.current, this.parentNode, this.context).resolve();
|
|
104
|
+
});
|
|
105
|
+
const intersectionMetaType = {
|
|
106
|
+
dataType: 'intersection',
|
|
107
|
+
types,
|
|
108
|
+
};
|
|
109
|
+
return intersectionMetaType;
|
|
110
|
+
}
|
|
111
|
+
if (ts.isTupleTypeNode(this.typeNode)) {
|
|
112
|
+
const elementTypes = [];
|
|
113
|
+
let restType;
|
|
114
|
+
for (const element of this.typeNode.elements) {
|
|
115
|
+
if (ts.isRestTypeNode(element)) {
|
|
116
|
+
const resolvedRest = new TypeResolver(element.type, this.current, element, this.context).resolve();
|
|
117
|
+
if (resolvedRest.dataType === 'array') {
|
|
118
|
+
restType = resolvedRest.elementType;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
restType = resolvedRest;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const typeNode = ts.isNamedTupleMember(element) ? element.type : element;
|
|
126
|
+
const type = new TypeResolver(typeNode, this.current, element, this.context).resolve();
|
|
127
|
+
elementTypes.push(type);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
dataType: 'tuple',
|
|
132
|
+
types: elementTypes,
|
|
133
|
+
...(restType ? { restType } : {}),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
if (this.typeNode.kind === ts.SyntaxKind.AnyKeyword || this.typeNode.kind === ts.SyntaxKind.UnknownKeyword) {
|
|
137
|
+
const literallyAny = {
|
|
138
|
+
dataType: 'any',
|
|
139
|
+
};
|
|
140
|
+
return literallyAny;
|
|
141
|
+
}
|
|
142
|
+
if (ts.isLiteralTypeNode(this.typeNode)) {
|
|
143
|
+
const enumType = {
|
|
144
|
+
dataType: 'enum',
|
|
145
|
+
enums: [this.getLiteralValue(this.typeNode)],
|
|
146
|
+
};
|
|
147
|
+
return enumType;
|
|
148
|
+
}
|
|
149
|
+
if (ts.isTypeLiteralNode(this.typeNode)) {
|
|
150
|
+
const properties = this.typeNode.members.filter(ts.isPropertySignature).reduce((res, propertySignature) => {
|
|
151
|
+
const type = new TypeResolver(propertySignature.type, this.current, propertySignature, this.context).resolve();
|
|
152
|
+
const def = TypeResolver.getDefault(propertySignature);
|
|
153
|
+
const property = {
|
|
154
|
+
example: this.getNodeExample(propertySignature),
|
|
155
|
+
default: def,
|
|
156
|
+
description: this.getNodeDescription(propertySignature),
|
|
157
|
+
format: this.getNodeFormat(propertySignature),
|
|
158
|
+
name: this.getPropertyName(propertySignature),
|
|
159
|
+
required: !propertySignature.questionToken,
|
|
160
|
+
type,
|
|
161
|
+
validators: (0, validatorUtils_1.getPropertyValidators)(propertySignature) || {},
|
|
162
|
+
deprecated: (0, jsDocUtils_1.isExistJSDocTag)(propertySignature, tag => tag.tagName.text === 'deprecated'),
|
|
163
|
+
title: this.getNodeTitle(propertySignature),
|
|
164
|
+
extensions: this.getNodeExtension(propertySignature),
|
|
165
|
+
};
|
|
166
|
+
return [property, ...res];
|
|
167
|
+
}, []);
|
|
168
|
+
const indexMember = this.typeNode.members.find(member => ts.isIndexSignatureDeclaration(member));
|
|
169
|
+
let additionalType;
|
|
170
|
+
if (indexMember) {
|
|
171
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
|
|
172
|
+
const indexSignatureDeclaration = indexMember;
|
|
173
|
+
const indexType = new TypeResolver(indexSignatureDeclaration.parameters[0].type, this.current, this.parentNode, this.context).resolve();
|
|
174
|
+
(0, flowUtils_1.throwUnless)(indexType.dataType === 'string', new exceptions_1.GenerateMetadataError(`Only string indexers are supported.`, this.typeNode));
|
|
175
|
+
additionalType = new TypeResolver(indexSignatureDeclaration.type, this.current, this.parentNode, this.context).resolve();
|
|
176
|
+
}
|
|
177
|
+
const objLiteral = {
|
|
178
|
+
additionalProperties: indexMember && additionalType,
|
|
179
|
+
dataType: 'nestedObjectLiteral',
|
|
180
|
+
properties,
|
|
181
|
+
};
|
|
182
|
+
return objLiteral;
|
|
183
|
+
}
|
|
184
|
+
if (this.typeNode.kind === ts.SyntaxKind.ObjectKeyword) {
|
|
185
|
+
return { dataType: 'object' };
|
|
186
|
+
}
|
|
187
|
+
if (ts.isMappedTypeNode(this.typeNode)) {
|
|
188
|
+
const mappedTypeNode = this.typeNode;
|
|
189
|
+
const getOneOrigDeclaration = (prop) => {
|
|
190
|
+
if (prop.declarations) {
|
|
191
|
+
return prop.declarations[0];
|
|
192
|
+
}
|
|
193
|
+
const syntheticOrigin = prop.links?.syntheticOrigin;
|
|
194
|
+
if (syntheticOrigin && syntheticOrigin.name === prop.name) {
|
|
195
|
+
//Otherwise losts jsDoc like in intellisense
|
|
196
|
+
return syntheticOrigin.declarations?.[0];
|
|
197
|
+
}
|
|
198
|
+
return undefined;
|
|
199
|
+
};
|
|
200
|
+
const isIgnored = (prop) => {
|
|
201
|
+
const declaration = getOneOrigDeclaration(prop);
|
|
202
|
+
return (declaration !== undefined &&
|
|
203
|
+
((0, jsDocUtils_1.getJSDocTagNames)(declaration).some(tag => tag === 'ignore') || (!ts.isPropertyDeclaration(declaration) && !ts.isPropertySignature(declaration) && !ts.isParameter(declaration))));
|
|
204
|
+
};
|
|
205
|
+
const calcMappedType = (type) => {
|
|
206
|
+
if (this.hasFlag(type, ts.TypeFlags.Union)) {
|
|
207
|
+
//Intersections are not interesting somehow...
|
|
208
|
+
const types = type.types;
|
|
209
|
+
const resolvedTypes = types.map(calcMappedType);
|
|
210
|
+
return {
|
|
211
|
+
dataType: 'union',
|
|
212
|
+
types: resolvedTypes,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
else if (this.hasFlag(type, ts.TypeFlags.Undefined)) {
|
|
216
|
+
return {
|
|
217
|
+
dataType: 'undefined',
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
else if (this.hasFlag(type, ts.TypeFlags.Null)) {
|
|
221
|
+
return {
|
|
222
|
+
dataType: 'enum',
|
|
223
|
+
enums: [null],
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
else if (this.hasFlag(type, ts.TypeFlags.Object)) {
|
|
227
|
+
const typeProperties = type.getProperties();
|
|
228
|
+
const properties = typeProperties
|
|
229
|
+
// Ignore methods, getter, setter and @ignored props
|
|
230
|
+
.filter(property => isIgnored(property) === false)
|
|
231
|
+
// Transform to property
|
|
232
|
+
.map(property => {
|
|
233
|
+
const propertyType = this.current.typeChecker.getTypeOfSymbolAtLocation(property, this.typeNode);
|
|
234
|
+
const typeNode = this.current.typeChecker.typeToTypeNode(propertyType, undefined, ts.NodeBuilderFlags.NoTruncation);
|
|
235
|
+
const parent = getOneOrigDeclaration(property); //If there are more declarations, we need to get one of them, from where we want to recognize jsDoc
|
|
236
|
+
const type = new TypeResolver(typeNode, this.current, parent, this.context, propertyType).resolve();
|
|
237
|
+
const required = !this.hasFlag(property, ts.SymbolFlags.Optional);
|
|
238
|
+
const comments = property.getDocumentationComment(this.current.typeChecker);
|
|
239
|
+
const description = comments.length ? ts.displayPartsToString(comments) : undefined;
|
|
240
|
+
const initializer = parent?.initializer;
|
|
241
|
+
const def = initializer ? (0, initializer_value_1.getInitializerValue)(initializer, this.current.typeChecker) : parent ? TypeResolver.getDefault(parent) : undefined;
|
|
242
|
+
// Push property
|
|
243
|
+
return {
|
|
244
|
+
name: property.getName(),
|
|
245
|
+
required,
|
|
246
|
+
deprecated: parent ? (0, jsDocUtils_1.isExistJSDocTag)(parent, tag => tag.tagName.text === 'deprecated') || (0, decoratorUtils_1.isDecorator)(parent, identifier => identifier.text === 'Deprecated') : false,
|
|
247
|
+
type,
|
|
248
|
+
default: def,
|
|
249
|
+
// validators are disjunct via types, so it is now OK.
|
|
250
|
+
// if a type not changes while mapping, we need validators
|
|
251
|
+
// if a type changes, then the validators will be not relevant
|
|
252
|
+
validators: (parent ? (0, validatorUtils_1.getPropertyValidators)(parent) : {}) || {},
|
|
253
|
+
description,
|
|
254
|
+
format: parent ? this.getNodeFormat(parent) : undefined,
|
|
255
|
+
example: parent ? this.getNodeExample(parent) : undefined,
|
|
256
|
+
extensions: parent ? this.getNodeExtension(parent) : undefined,
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
const objectLiteral = {
|
|
260
|
+
dataType: 'nestedObjectLiteral',
|
|
261
|
+
properties,
|
|
262
|
+
};
|
|
263
|
+
const indexInfos = this.current.typeChecker.getIndexInfosOfType(type);
|
|
264
|
+
const indexTypes = indexInfos.flatMap(indexInfo => {
|
|
265
|
+
const typeNode = this.current.typeChecker.typeToTypeNode(indexInfo.type, undefined, ts.NodeBuilderFlags.NoTruncation);
|
|
266
|
+
if (typeNode.kind === ts.SyntaxKind.NeverKeyword) {
|
|
267
|
+
// { [k: string]: never; }
|
|
268
|
+
return [];
|
|
269
|
+
}
|
|
270
|
+
const type = new TypeResolver(typeNode, this.current, mappedTypeNode, this.context, indexInfo.type).resolve();
|
|
271
|
+
return [type];
|
|
272
|
+
});
|
|
273
|
+
if (indexTypes.length) {
|
|
274
|
+
if (indexTypes.length === 1) {
|
|
275
|
+
objectLiteral.additionalProperties = indexTypes[0];
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
// { [k: string]: string; } & { [k: number]: number; }
|
|
279
|
+
// A | B is sometimes A type or B type, sometimes optionally accepts both A & B members.
|
|
280
|
+
// Most people & TSOA thinks that A | B can be only A or only B.
|
|
281
|
+
// So we can accept this merge
|
|
282
|
+
//Every additional property key assumed as string
|
|
283
|
+
objectLiteral.additionalProperties = {
|
|
284
|
+
dataType: 'union',
|
|
285
|
+
types: indexTypes,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return objectLiteral;
|
|
290
|
+
}
|
|
291
|
+
// Known issues & easy to implement: Partial<string>, Partial<never>, ... But I think a programmer not writes types like this
|
|
292
|
+
throw new exceptions_1.GenerateMetadataError(`Unhandled mapped type has found, flags: ${type.flags}`, this.typeNode);
|
|
293
|
+
};
|
|
294
|
+
const referencer = this.getReferencer();
|
|
295
|
+
const result = calcMappedType(referencer);
|
|
296
|
+
return result;
|
|
297
|
+
}
|
|
298
|
+
if (ts.isConditionalTypeNode(this.typeNode)) {
|
|
299
|
+
const referencer = this.getReferencer();
|
|
300
|
+
const resolvedNode = this.current.typeChecker.typeToTypeNode(referencer, undefined, ts.NodeBuilderFlags.NoTruncation);
|
|
301
|
+
return new TypeResolver(resolvedNode, this.current, this.typeNode, this.context, referencer).resolve();
|
|
302
|
+
}
|
|
303
|
+
// keyof & readonly arrays
|
|
304
|
+
if (ts.isTypeOperatorNode(this.typeNode)) {
|
|
305
|
+
return this.resolveTypeOperatorNode(this.typeNode, this.current.typeChecker, this.current, this.context, this.parentNode, this.referencer);
|
|
306
|
+
}
|
|
307
|
+
// Indexed type
|
|
308
|
+
if (ts.isIndexedAccessTypeNode(this.typeNode)) {
|
|
309
|
+
return this.resolveIndexedAccessTypeNode(this.typeNode, this.current.typeChecker, this.current, this.context);
|
|
310
|
+
}
|
|
311
|
+
if (ts.isTemplateLiteralTypeNode(this.typeNode)) {
|
|
312
|
+
const type = this.getReferencer();
|
|
313
|
+
(0, flowUtils_1.throwUnless)(type.isUnion() && type.types.every((unionElementType) => unionElementType.isStringLiteral()), new exceptions_1.GenerateMetadataError(`Could not the type of ${this.current.typeChecker.typeToString(this.current.typeChecker.getTypeFromTypeNode(this.typeNode), this.typeNode)}`, this.typeNode));
|
|
314
|
+
// `a${'c' | 'd'}b`
|
|
315
|
+
const stringLiteralEnum = {
|
|
316
|
+
dataType: 'enum',
|
|
317
|
+
enums: type.types.map((stringLiteralType) => stringLiteralType.value),
|
|
318
|
+
};
|
|
319
|
+
return stringLiteralEnum;
|
|
320
|
+
}
|
|
321
|
+
if (ts.isParenthesizedTypeNode(this.typeNode)) {
|
|
322
|
+
return new TypeResolver(this.typeNode.type, this.current, this.typeNode, this.context, this.referencer).resolve();
|
|
323
|
+
}
|
|
324
|
+
(0, flowUtils_1.throwUnless)(this.typeNode.kind === ts.SyntaxKind.TypeReference, new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[this.typeNode.kind]}`, this.typeNode));
|
|
325
|
+
return this.resolveTypeReferenceNode(this.typeNode, this.current, this.context, this.parentNode);
|
|
326
|
+
}
|
|
327
|
+
resolveTypeOperatorNode(typeNode, typeChecker, current, context, parentNode, referencer) {
|
|
328
|
+
switch (typeNode.operator) {
|
|
329
|
+
case ts.SyntaxKind.KeyOfKeyword: {
|
|
330
|
+
// keyof
|
|
331
|
+
const type = typeChecker.getTypeFromTypeNode(typeNode);
|
|
332
|
+
if (type.isIndexType()) {
|
|
333
|
+
// in case of generic: keyof T. Not handles all possible cases
|
|
334
|
+
const symbol = type.type.getSymbol();
|
|
335
|
+
if (symbol && symbol.getFlags() & ts.TypeFlags.TypeParameter) {
|
|
336
|
+
const typeName = symbol.getEscapedName();
|
|
337
|
+
(0, flowUtils_1.throwUnless)(typeof typeName === 'string', new exceptions_1.GenerateMetadataError(`typeName is not string, but ${typeof typeName}`, typeNode));
|
|
338
|
+
if (context[typeName]) {
|
|
339
|
+
const subResult = new TypeResolver(context[typeName].type, current, parentNode, context).resolve();
|
|
340
|
+
if (subResult.dataType === 'any') {
|
|
341
|
+
return {
|
|
342
|
+
dataType: 'union',
|
|
343
|
+
types: [{ dataType: 'string' }, { dataType: 'double' }],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
const properties = subResult.properties?.map(v => v.name);
|
|
347
|
+
(0, flowUtils_1.throwUnless)(properties, new exceptions_1.GenerateMetadataError(`TypeOperator 'keyof' on node which have no properties`, context[typeName].type));
|
|
348
|
+
return {
|
|
349
|
+
dataType: 'enum',
|
|
350
|
+
enums: properties,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
else if (type.isUnion()) {
|
|
356
|
+
const literals = type.types.filter((t) => t.isLiteral());
|
|
357
|
+
const literalValues = [];
|
|
358
|
+
for (const literal of literals) {
|
|
359
|
+
(0, flowUtils_1.throwUnless)(typeof literal.value == 'number' || typeof literal.value == 'string', new exceptions_1.GenerateMetadataError(`Not handled key Type, maybe ts.PseudoBigInt ${typeChecker.typeToString(literal)}`, typeNode));
|
|
360
|
+
literalValues.push(literal.value);
|
|
361
|
+
}
|
|
362
|
+
if (!literals.length) {
|
|
363
|
+
const length = type.types.length;
|
|
364
|
+
const someStringFlag = type.types.some(t => t.flags === ts.TypeFlags.String);
|
|
365
|
+
const someNumberFlag = type.types.some(t => t.flags === ts.TypeFlags.Number);
|
|
366
|
+
const someSymbolFlag = type.types.some(t => t.flags === ts.TypeFlags.ESSymbol);
|
|
367
|
+
if (someStringFlag && someNumberFlag) {
|
|
368
|
+
if (length === 2 || (length === 3 && someSymbolFlag)) {
|
|
369
|
+
return {
|
|
370
|
+
dataType: 'union',
|
|
371
|
+
types: [{ dataType: 'string' }, { dataType: 'double' }],
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Warn on nonsense (`number`, `typeof Symbol.iterator`)
|
|
377
|
+
if (type.types.find(t => !t.isLiteral()) !== undefined) {
|
|
378
|
+
const problems = type.types.filter(t => !t.isLiteral()).map(t => typeChecker.typeToString(t));
|
|
379
|
+
console.warn(new exceptions_1.GenerateMetaDataWarning(`Skipped non-literal type(s) ${problems.join(', ')}`, typeNode).toString());
|
|
380
|
+
}
|
|
381
|
+
const stringMembers = literalValues.filter(v => typeof v == 'string');
|
|
382
|
+
const numberMembers = literalValues.filter(v => typeof v == 'number');
|
|
383
|
+
if (stringMembers.length && numberMembers.length) {
|
|
384
|
+
return {
|
|
385
|
+
dataType: 'union',
|
|
386
|
+
types: [
|
|
387
|
+
{ dataType: 'enum', enums: stringMembers },
|
|
388
|
+
{ dataType: 'enum', enums: numberMembers },
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
return {
|
|
393
|
+
dataType: 'enum',
|
|
394
|
+
enums: literalValues,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
else if (type.isLiteral()) {
|
|
398
|
+
(0, flowUtils_1.throwUnless)(typeof type.value == 'number' || typeof type.value == 'string', new exceptions_1.GenerateMetadataError(`Not handled indexType, maybe ts.PseudoBigInt ${typeChecker.typeToString(type)}`, typeNode));
|
|
399
|
+
return {
|
|
400
|
+
dataType: 'enum',
|
|
401
|
+
enums: [type.value],
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
else if (this.hasFlag(type, ts.TypeFlags.Never)) {
|
|
405
|
+
throw new exceptions_1.GenerateMetadataError(`TypeOperator 'keyof' on node produced a never type`, typeNode);
|
|
406
|
+
}
|
|
407
|
+
else if (this.hasFlag(type, ts.TypeFlags.TemplateLiteral)) {
|
|
408
|
+
//Now assumes template literals as string
|
|
409
|
+
console.warn(new exceptions_1.GenerateMetaDataWarning(`Template literals are assumed as strings`, typeNode).toString());
|
|
410
|
+
return {
|
|
411
|
+
dataType: 'string',
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
else if (this.hasFlag(type, ts.TypeFlags.Number)) {
|
|
415
|
+
return {
|
|
416
|
+
dataType: 'double',
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const indexedTypeName = typeChecker.typeToString(typeChecker.getTypeFromTypeNode(typeNode.type));
|
|
420
|
+
throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${indexedTypeName}`, typeNode);
|
|
421
|
+
}
|
|
422
|
+
case ts.SyntaxKind.ReadonlyKeyword:
|
|
423
|
+
// Handle `readonly` arrays
|
|
424
|
+
return new TypeResolver(typeNode.type, current, typeNode, context, referencer).resolve();
|
|
425
|
+
default:
|
|
426
|
+
throw new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[typeNode.kind]}`, typeNode);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
resolveIndexedAccessTypeNode(typeNode, typeChecker, current, context) {
|
|
430
|
+
const { indexType, objectType } = typeNode;
|
|
431
|
+
if ([ts.SyntaxKind.NumberKeyword, ts.SyntaxKind.StringKeyword].includes(indexType.kind)) {
|
|
432
|
+
// Indexed by keyword
|
|
433
|
+
const isNumberIndexType = indexType.kind === ts.SyntaxKind.NumberKeyword;
|
|
434
|
+
const typeOfObjectType = typeChecker.getTypeFromTypeNode(objectType);
|
|
435
|
+
const type = isNumberIndexType ? typeOfObjectType.getNumberIndexType() : typeOfObjectType.getStringIndexType();
|
|
436
|
+
(0, flowUtils_1.throwUnless)(type, new exceptions_1.GenerateMetadataError(`Could not determine ${isNumberIndexType ? 'number' : 'string'} index on ${typeChecker.typeToString(typeOfObjectType)}`, typeNode));
|
|
437
|
+
return new TypeResolver(typeChecker.typeToTypeNode(type, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
|
|
438
|
+
}
|
|
439
|
+
else if (ts.isLiteralTypeNode(indexType) && (ts.isStringLiteral(indexType.literal) || ts.isNumericLiteral(indexType.literal))) {
|
|
440
|
+
// Indexed by literal
|
|
441
|
+
const hasType = (node) => node !== undefined && Object.prototype.hasOwnProperty.call(node, 'type');
|
|
442
|
+
const symbol = typeChecker.getPropertyOfType(typeChecker.getTypeFromTypeNode(objectType), indexType.literal.text);
|
|
443
|
+
(0, flowUtils_1.throwUnless)(symbol, new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeChecker.typeToString(typeChecker.getTypeFromTypeNode(objectType))}`, typeNode));
|
|
444
|
+
if (hasType(symbol.valueDeclaration) && symbol.valueDeclaration.type) {
|
|
445
|
+
return new TypeResolver(symbol.valueDeclaration.type, current, typeNode, context).resolve();
|
|
446
|
+
}
|
|
447
|
+
const declaration = typeChecker.getTypeOfSymbolAtLocation(symbol, objectType);
|
|
448
|
+
try {
|
|
449
|
+
return new TypeResolver(typeChecker.typeToTypeNode(declaration, objectType, ts.NodeBuilderFlags.NoTruncation), current, typeNode, context).resolve();
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
throw new exceptions_1.GenerateMetadataError(`Could not determine the keys on ${typeChecker.typeToString(typeChecker.getTypeFromTypeNode(typeChecker.typeToTypeNode(declaration, undefined, ts.NodeBuilderFlags.NoTruncation)))}`, typeNode);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else if (ts.isTypeOperatorNode(indexType) && indexType.operator === ts.SyntaxKind.KeyOfKeyword) {
|
|
456
|
+
// Indexed by keyof typeof value
|
|
457
|
+
const typeOfObjectType = ts.isParenthesizedTypeNode(objectType) ? objectType.type : objectType;
|
|
458
|
+
const { type: typeOfIndexType } = indexType;
|
|
459
|
+
const isSameTypeQuery = ts.isTypeQueryNode(typeOfObjectType) && ts.isTypeQueryNode(typeOfIndexType) && typeOfObjectType.exprName.getText() === typeOfIndexType.exprName.getText();
|
|
460
|
+
const isSameTypeReference = ts.isTypeReferenceNode(typeOfObjectType) && ts.isTypeReferenceNode(typeOfIndexType) && typeOfObjectType.typeName.getText() === typeOfIndexType.typeName.getText();
|
|
461
|
+
if (isSameTypeQuery || isSameTypeReference) {
|
|
462
|
+
const type = this.getReferencer();
|
|
463
|
+
const node = typeChecker.typeToTypeNode(type, undefined, ts.NodeBuilderFlags.InTypeAlias | ts.NodeBuilderFlags.NoTruncation);
|
|
464
|
+
return new TypeResolver(node, current, typeNode, context, this.referencer).resolve();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
throw new exceptions_1.GenerateMetadataError(`Unknown type: ${ts.SyntaxKind[typeNode.kind]}`, typeNode);
|
|
468
|
+
}
|
|
469
|
+
resolveTypeReferenceNode(typeNode, current, context, parentNode) {
|
|
470
|
+
const { typeName, typeArguments } = typeNode;
|
|
471
|
+
if (typeName.kind !== ts.SyntaxKind.Identifier) {
|
|
472
|
+
return this.getReferenceType(typeNode);
|
|
473
|
+
}
|
|
474
|
+
switch (typeName.text) {
|
|
475
|
+
case 'Date':
|
|
476
|
+
return new dateTransformer_1.DateTransformer().transform(parentNode);
|
|
477
|
+
case 'Buffer':
|
|
478
|
+
case 'Readable':
|
|
479
|
+
return { dataType: 'buffer' };
|
|
480
|
+
case 'Array':
|
|
481
|
+
if (typeArguments && typeArguments.length === 1) {
|
|
482
|
+
return {
|
|
483
|
+
dataType: 'array',
|
|
484
|
+
elementType: new TypeResolver(typeArguments[0], current, parentNode, context).resolve(),
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
break;
|
|
488
|
+
case 'Promise':
|
|
489
|
+
if (typeArguments && typeArguments.length === 1) {
|
|
490
|
+
return new TypeResolver(typeArguments[0], current, parentNode, context).resolve();
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
case 'String':
|
|
494
|
+
return { dataType: 'string' };
|
|
495
|
+
default:
|
|
496
|
+
if (context[typeName.text]) {
|
|
497
|
+
return new TypeResolver(context[typeName.text].type, current, parentNode, context).resolve();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
return this.getReferenceType(typeNode);
|
|
501
|
+
}
|
|
502
|
+
getLiteralValue(typeNode) {
|
|
503
|
+
switch (typeNode.literal.kind) {
|
|
504
|
+
case ts.SyntaxKind.TrueKeyword:
|
|
505
|
+
return true;
|
|
506
|
+
case ts.SyntaxKind.FalseKeyword:
|
|
507
|
+
return false;
|
|
508
|
+
case ts.SyntaxKind.StringLiteral:
|
|
509
|
+
return typeNode.literal.text;
|
|
510
|
+
case ts.SyntaxKind.NumericLiteral:
|
|
511
|
+
return parseFloat(typeNode.literal.text);
|
|
512
|
+
case ts.SyntaxKind.PrefixUnaryExpression:
|
|
513
|
+
// make sure to only handle the MinusToken here
|
|
514
|
+
(0, flowUtils_1.throwUnless)(typeNode.literal.operator === ts.SyntaxKind.MinusToken, new exceptions_1.GenerateMetadataError(`Couldn't resolve literal node: ${typeNode.literal.getText()}`));
|
|
515
|
+
return parseFloat(typeNode.literal.getText());
|
|
516
|
+
case ts.SyntaxKind.NullKeyword:
|
|
517
|
+
return null;
|
|
518
|
+
default:
|
|
519
|
+
(0, flowUtils_1.throwUnless)(Object.prototype.hasOwnProperty.call(typeNode.literal, 'text'), new exceptions_1.GenerateMetadataError(`Couldn't resolve literal node: ${typeNode.literal.getText()}`));
|
|
520
|
+
return typeNode.literal.text;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
getDesignatedModels(nodes, typeName) {
|
|
524
|
+
/**
|
|
525
|
+
* Model is marked with '@tsoaModel', indicating that it should be the 'canonical' model used
|
|
526
|
+
*/
|
|
527
|
+
const designatedNodes = nodes.filter(enumNode => {
|
|
528
|
+
return (0, jsDocUtils_1.isExistJSDocTag)(enumNode, tag => tag.tagName.text === 'tsoaModel');
|
|
529
|
+
});
|
|
530
|
+
if (designatedNodes.length === 0) {
|
|
531
|
+
return nodes;
|
|
532
|
+
}
|
|
533
|
+
(0, flowUtils_1.throwUnless)(designatedNodes.length === 1, new exceptions_1.GenerateMetadataError(`Multiple models for ${typeName} marked with '@tsoaModel'; '@tsoaModel' should only be applied to one model.`));
|
|
534
|
+
return designatedNodes;
|
|
535
|
+
}
|
|
536
|
+
hasFlag(type, flag) {
|
|
537
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison
|
|
538
|
+
return (type.flags & flag) === flag;
|
|
539
|
+
}
|
|
540
|
+
getReferencer() {
|
|
541
|
+
if (this.referencer) {
|
|
542
|
+
return this.referencer;
|
|
543
|
+
}
|
|
544
|
+
if (this.typeNode.pos !== -1) {
|
|
545
|
+
return this.current.typeChecker.getTypeFromTypeNode(this.typeNode);
|
|
546
|
+
}
|
|
547
|
+
throw new exceptions_1.GenerateMetadataError(`Can not succeeded to calculate referencer type.`, this.typeNode);
|
|
548
|
+
}
|
|
549
|
+
static typeReferenceToEntityName(node) {
|
|
550
|
+
if (ts.isTypeReferenceNode(node)) {
|
|
551
|
+
return node.typeName;
|
|
552
|
+
}
|
|
553
|
+
else if (ts.isExpressionWithTypeArguments(node)) {
|
|
554
|
+
return node.expression;
|
|
555
|
+
}
|
|
556
|
+
throw new exceptions_1.GenerateMetadataError(`Can't resolve Reference type.`);
|
|
557
|
+
}
|
|
558
|
+
//Generates type name for type references
|
|
559
|
+
calcRefTypeName(type) {
|
|
560
|
+
const getEntityName = (type) => {
|
|
561
|
+
if (ts.isIdentifier(type)) {
|
|
562
|
+
return type.text;
|
|
563
|
+
}
|
|
564
|
+
return `${getEntityName(type.left)}.${type.right.text}`;
|
|
565
|
+
};
|
|
566
|
+
let name = getEntityName(type);
|
|
567
|
+
if (this.context[name]) {
|
|
568
|
+
//resolve name only interesting if entity is not qualifiedName
|
|
569
|
+
name = this.context[name].name; //Not needed to check unicity, because generic parameters are checked previously
|
|
570
|
+
}
|
|
571
|
+
else {
|
|
572
|
+
const declarations = this.getModelTypeDeclarations(type);
|
|
573
|
+
// Handle cases where declarations is empty (e.g., inline object types in generics)
|
|
574
|
+
if (!declarations || declarations.length === 0) {
|
|
575
|
+
// Check if this is a simple identifier (like Date, String, etc.)
|
|
576
|
+
if (ts.isIdentifier(type)) {
|
|
577
|
+
// For simple identifiers, just return the name
|
|
578
|
+
return type.text;
|
|
579
|
+
}
|
|
580
|
+
// For inline object types, we should use the resolve method to get the proper type
|
|
581
|
+
// This will handle TypeLiteralNode, UnionTypeNode, etc. properly
|
|
582
|
+
// Note: We need to cast to TypeNode since EntityName can be Identifier or QualifiedName
|
|
583
|
+
const typeNode = type;
|
|
584
|
+
const resolvedType = new TypeResolver(typeNode, this.current, this.parentNode, this.context).resolve();
|
|
585
|
+
// Generate a deterministic name for this inline type based on its structure
|
|
586
|
+
const typeName = this.calcTypeName(typeNode);
|
|
587
|
+
const sanitizedName = typeName
|
|
588
|
+
.replace(/[^A-Za-z0-9]/g, '_')
|
|
589
|
+
.replace(/_+/g, '_')
|
|
590
|
+
.replace(/^_|_$/g, '');
|
|
591
|
+
const uniqueName = `Inline_${sanitizedName}`;
|
|
592
|
+
// Add to reference types so it can be properly serialized
|
|
593
|
+
// We need to create a proper ReferenceType object
|
|
594
|
+
const referenceType = {
|
|
595
|
+
dataType: 'refAlias',
|
|
596
|
+
refName: uniqueName,
|
|
597
|
+
type: resolvedType,
|
|
598
|
+
validators: {},
|
|
599
|
+
deprecated: false,
|
|
600
|
+
};
|
|
601
|
+
this.current.AddReferenceType(referenceType);
|
|
602
|
+
return uniqueName;
|
|
603
|
+
}
|
|
604
|
+
//Two possible solutions for recognizing different types:
|
|
605
|
+
// - Add declaration positions into type names (In an order).
|
|
606
|
+
// - It accepts multiple types with same name, if the code compiles, there would be no conflicts in the type names
|
|
607
|
+
// - Clear namespaces from type names.
|
|
608
|
+
// - Horrible changes can be in the routes.ts in case of teamwork,
|
|
609
|
+
// because source files have paths in the computer where data generation runs.
|
|
610
|
+
// - Use fully namespaced names
|
|
611
|
+
// - Conflicts can be recognized because of the declarations
|
|
612
|
+
//
|
|
613
|
+
// The second was implemented, it not changes the usual type name formats.
|
|
614
|
+
const oneDeclaration = declarations[0]; //Every declarations should be in the same namespace hierarchy
|
|
615
|
+
const identifiers = name.split('.');
|
|
616
|
+
if (oneDeclaration && ts.isEnumMember(oneDeclaration)) {
|
|
617
|
+
name = identifiers.slice(identifiers.length - 2).join('.');
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
name = identifiers.slice(identifiers.length - 1).join('.');
|
|
621
|
+
}
|
|
622
|
+
let actNode = oneDeclaration.parent;
|
|
623
|
+
let isFirst = true;
|
|
624
|
+
const isGlobalDeclaration = (mod) => mod.name.kind === ts.SyntaxKind.Identifier && mod.name.text === 'global';
|
|
625
|
+
while (!ts.isSourceFile(actNode)) {
|
|
626
|
+
if (ts.isBlock(actNode)) {
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
if (!(isFirst && ts.isEnumDeclaration(actNode)) && !ts.isModuleBlock(actNode)) {
|
|
630
|
+
(0, flowUtils_1.throwUnless)(ts.isModuleDeclaration(actNode), new exceptions_1.GenerateMetadataError(`This node kind is unknown: ${actNode.kind}`, type));
|
|
631
|
+
if (!isGlobalDeclaration(actNode)) {
|
|
632
|
+
const moduleName = actNode.name.text;
|
|
633
|
+
name = `${moduleName}.${name}`;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
isFirst = false;
|
|
637
|
+
actNode = actNode.parent;
|
|
638
|
+
}
|
|
639
|
+
const declarationPositions = declarations.map(declaration => ({
|
|
640
|
+
fileName: declaration.getSourceFile().fileName,
|
|
641
|
+
pos: declaration.pos,
|
|
642
|
+
}));
|
|
643
|
+
this.current.CheckModelUnicity(name, declarationPositions);
|
|
644
|
+
}
|
|
645
|
+
return name;
|
|
646
|
+
}
|
|
647
|
+
calcMemberJsDocProperties(arg) {
|
|
648
|
+
const def = TypeResolver.getDefault(arg);
|
|
649
|
+
const isDeprecated = (0, jsDocUtils_1.isExistJSDocTag)(arg, tag => tag.tagName.text === 'deprecated') || (0, decoratorUtils_1.isDecorator)(arg, identifier => identifier.text === 'Deprecated');
|
|
650
|
+
const symbol = this.getSymbolAtLocation(arg.name);
|
|
651
|
+
const comments = symbol ? symbol.getDocumentationComment(this.current.typeChecker) : [];
|
|
652
|
+
const description = comments.length ? ts.displayPartsToString(comments) : undefined;
|
|
653
|
+
const validators = (0, validatorUtils_1.getPropertyValidators)(arg);
|
|
654
|
+
const format = this.getNodeFormat(arg);
|
|
655
|
+
const example = this.getNodeExample(arg);
|
|
656
|
+
const extensions = this.getNodeExtension(arg);
|
|
657
|
+
const isIgnored = (0, jsDocUtils_1.getJSDocTagNames)(arg).some(tag => tag === 'ignore');
|
|
658
|
+
const jsonObj = {
|
|
659
|
+
default: def,
|
|
660
|
+
description,
|
|
661
|
+
validators: validators && Object.keys(validators).length ? validators : undefined,
|
|
662
|
+
format,
|
|
663
|
+
example: example !== undefined ? example : undefined,
|
|
664
|
+
extensions: extensions.length ? extensions : undefined,
|
|
665
|
+
deprecated: isDeprecated ? true : undefined,
|
|
666
|
+
ignored: isIgnored ? true : undefined,
|
|
667
|
+
};
|
|
668
|
+
const keys = Object.keys(jsonObj);
|
|
669
|
+
for (const key of keys) {
|
|
670
|
+
if (jsonObj[key] === undefined) {
|
|
671
|
+
delete jsonObj[key];
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (Object.keys(jsonObj).length) {
|
|
675
|
+
return JSON.stringify(jsonObj);
|
|
676
|
+
}
|
|
677
|
+
return '';
|
|
678
|
+
}
|
|
679
|
+
//Generates type name for type references
|
|
680
|
+
calcTypeName(arg) {
|
|
681
|
+
if (ts.isLiteralTypeNode(arg)) {
|
|
682
|
+
const literalValue = this.getLiteralValue(arg);
|
|
683
|
+
if (typeof literalValue == 'string') {
|
|
684
|
+
return `'${literalValue}'`;
|
|
685
|
+
}
|
|
686
|
+
if (literalValue === null) {
|
|
687
|
+
return 'null';
|
|
688
|
+
}
|
|
689
|
+
if (typeof literalValue === 'boolean') {
|
|
690
|
+
return literalValue === true ? 'true' : 'false';
|
|
691
|
+
}
|
|
692
|
+
return `${literalValue}`;
|
|
693
|
+
}
|
|
694
|
+
const resolvedType = primitiveTransformer_1.PrimitiveTransformer.resolveKindToPrimitive(arg.kind);
|
|
695
|
+
if (resolvedType) {
|
|
696
|
+
return resolvedType;
|
|
697
|
+
}
|
|
698
|
+
if (ts.isTypeReferenceNode(arg) || ts.isExpressionWithTypeArguments(arg)) {
|
|
699
|
+
return this.calcTypeReferenceTypeName(arg)[1];
|
|
700
|
+
}
|
|
701
|
+
else if (ts.isTypeLiteralNode(arg)) {
|
|
702
|
+
const members = arg.members.map(member => {
|
|
703
|
+
if (ts.isPropertySignature(member)) {
|
|
704
|
+
const name = member.name.text;
|
|
705
|
+
const typeText = this.calcTypeName(member.type);
|
|
706
|
+
return `"${name}"${member.questionToken ? '?' : ''}${this.calcMemberJsDocProperties(member)}: ${typeText}`;
|
|
707
|
+
}
|
|
708
|
+
else if (ts.isIndexSignatureDeclaration(member)) {
|
|
709
|
+
(0, flowUtils_1.throwUnless)(member.parameters.length === 1, new exceptions_1.GenerateMetadataError(`Index signature parameters length != 1`, member));
|
|
710
|
+
const indexType = member.parameters[0];
|
|
711
|
+
(0, flowUtils_1.throwUnless)(
|
|
712
|
+
// now we can't reach this part of code
|
|
713
|
+
ts.isParameter(indexType), new exceptions_1.GenerateMetadataError(`indexSignature declaration parameter kind is not SyntaxKind.Parameter`, indexType));
|
|
714
|
+
(0, flowUtils_1.throwUnless)(!indexType.questionToken, new exceptions_1.GenerateMetadataError(`Question token has found for an indexSignature declaration`, indexType));
|
|
715
|
+
const typeText = this.calcTypeName(member.type);
|
|
716
|
+
const indexName = indexType.name.text;
|
|
717
|
+
const indexTypeText = this.calcTypeName(indexType.type);
|
|
718
|
+
return `["${indexName}": ${indexTypeText}]: ${typeText}`;
|
|
719
|
+
}
|
|
720
|
+
throw new exceptions_1.GenerateMetadataError(`Unhandled member kind has found: ${member.kind}`, member);
|
|
721
|
+
});
|
|
722
|
+
return `{${members.join('; ')}}`;
|
|
723
|
+
}
|
|
724
|
+
else if (ts.isArrayTypeNode(arg)) {
|
|
725
|
+
const typeName = this.calcTypeName(arg.elementType);
|
|
726
|
+
return `${typeName}[]`;
|
|
727
|
+
}
|
|
728
|
+
else if (ts.isIntersectionTypeNode(arg)) {
|
|
729
|
+
const memberTypeNames = arg.types.map(type => this.calcTypeName(type));
|
|
730
|
+
return memberTypeNames.join(' & ');
|
|
731
|
+
}
|
|
732
|
+
else if (ts.isUnionTypeNode(arg)) {
|
|
733
|
+
const memberTypeNames = arg.types.map(type => this.calcTypeName(type));
|
|
734
|
+
return memberTypeNames.join(' | ');
|
|
735
|
+
}
|
|
736
|
+
else if (ts.isTypeOperatorNode(arg)) {
|
|
737
|
+
const subTypeName = this.calcTypeName(arg.type);
|
|
738
|
+
if (arg.operator === ts.SyntaxKind.KeyOfKeyword) {
|
|
739
|
+
return `keyof ${subTypeName}`;
|
|
740
|
+
}
|
|
741
|
+
else if (arg.operator === ts.SyntaxKind.ReadonlyKeyword) {
|
|
742
|
+
return `readonly ${subTypeName}`;
|
|
743
|
+
}
|
|
744
|
+
throw new exceptions_1.GenerateMetadataError(`Unknown keyword has found: ${arg.operator}`, arg);
|
|
745
|
+
}
|
|
746
|
+
else if (ts.isTypeQueryNode(arg)) {
|
|
747
|
+
const subTypeName = this.calcRefTypeName(arg.exprName);
|
|
748
|
+
return `typeof ${subTypeName}`;
|
|
749
|
+
}
|
|
750
|
+
else if (ts.isIndexedAccessTypeNode(arg)) {
|
|
751
|
+
const objectTypeName = this.calcTypeName(arg.objectType);
|
|
752
|
+
const indexTypeName = this.calcTypeName(arg.indexType);
|
|
753
|
+
return `${objectTypeName}[${indexTypeName}]`;
|
|
754
|
+
}
|
|
755
|
+
else if (arg.kind === ts.SyntaxKind.UnknownKeyword) {
|
|
756
|
+
return 'unknown';
|
|
757
|
+
}
|
|
758
|
+
else if (arg.kind === ts.SyntaxKind.AnyKeyword) {
|
|
759
|
+
return 'any';
|
|
760
|
+
}
|
|
761
|
+
else if (arg.kind === ts.SyntaxKind.NeverKeyword) {
|
|
762
|
+
return 'never';
|
|
763
|
+
}
|
|
764
|
+
else if (ts.isConditionalTypeNode(arg)) {
|
|
765
|
+
const checkTypeName = this.calcTypeName(arg.checkType);
|
|
766
|
+
const extendsTypeName = this.calcTypeName(arg.extendsType);
|
|
767
|
+
const trueTypeName = this.calcTypeName(arg.trueType);
|
|
768
|
+
const falseTypeName = this.calcTypeName(arg.falseType);
|
|
769
|
+
return `${checkTypeName} extends ${extendsTypeName} ? ${trueTypeName} : ${falseTypeName}`;
|
|
770
|
+
}
|
|
771
|
+
else if (ts.isParenthesizedTypeNode(arg)) {
|
|
772
|
+
const internalTypeName = this.calcTypeName(arg.type);
|
|
773
|
+
return `(${internalTypeName})`; //Parentheses are not really interesting. The type name generation adds parentheses for the clarity
|
|
774
|
+
}
|
|
775
|
+
const warning = new exceptions_1.GenerateMetaDataWarning(`This kind (${arg.kind}) is unhandled, so the type will be any, and no type conflict checks will made`, arg);
|
|
776
|
+
console.warn(warning.toString());
|
|
777
|
+
return 'any';
|
|
778
|
+
}
|
|
779
|
+
//Generates type name for type references
|
|
780
|
+
calcTypeReferenceTypeName(node) {
|
|
781
|
+
const type = TypeResolver.typeReferenceToEntityName(node);
|
|
782
|
+
const refTypeName = this.calcRefTypeName(type);
|
|
783
|
+
if (Array.isArray(node.typeArguments)) {
|
|
784
|
+
// Add typeArguments for Synthetic nodes (e.g. Record<> in TestClassModel.indexedResponse)
|
|
785
|
+
const argumentsString = node.typeArguments.map(type => this.calcTypeName(type));
|
|
786
|
+
return [type, `${refTypeName}<${argumentsString.join(', ')}>`];
|
|
787
|
+
}
|
|
788
|
+
return [type, refTypeName];
|
|
789
|
+
}
|
|
790
|
+
getReferenceType(node, addToRefTypeMap = true) {
|
|
791
|
+
const [type, name] = this.calcTypeReferenceTypeName(node);
|
|
792
|
+
const refTypeName = this.getRefTypeName(name);
|
|
793
|
+
this.current.CheckExpressionUnicity(refTypeName, name);
|
|
794
|
+
this.context = this.typeArgumentsToContext(node, type);
|
|
795
|
+
const calcReferenceType = () => {
|
|
796
|
+
try {
|
|
797
|
+
const existingType = localReferenceTypeCache[name];
|
|
798
|
+
if (existingType) {
|
|
799
|
+
return existingType;
|
|
800
|
+
}
|
|
801
|
+
if (inProgressTypes[name]) {
|
|
802
|
+
return this.createCircularDependencyResolver(name, refTypeName);
|
|
803
|
+
}
|
|
804
|
+
inProgressTypes[name] = [];
|
|
805
|
+
const declarations = this.getModelTypeDeclarations(type);
|
|
806
|
+
const referenceTypes = [];
|
|
807
|
+
// If no declarations found, this might be a built-in type or a type that can't be resolved
|
|
808
|
+
if (declarations.length === 0) {
|
|
809
|
+
// For complex generic types with object literal type arguments,
|
|
810
|
+
// we need to handle them differently
|
|
811
|
+
throw new exceptions_1.GenerateMetadataError(`Could not find declarations for type '${name}'. This might be a complex generic type that needs special handling.`);
|
|
812
|
+
}
|
|
813
|
+
for (const declaration of declarations) {
|
|
814
|
+
if (ts.isTypeAliasDeclaration(declaration)) {
|
|
815
|
+
const referencer = node.pos !== -1 ? this.current.typeChecker.getTypeFromTypeNode(node) : undefined;
|
|
816
|
+
referenceTypes.push(new referenceTransformer_1.ReferenceTransformer().transform(declaration, refTypeName, this, referencer));
|
|
817
|
+
}
|
|
818
|
+
else if (enumTransformer_1.EnumTransformer.transformable(declaration)) {
|
|
819
|
+
referenceTypes.push(new enumTransformer_1.EnumTransformer().transform(this, declaration, refTypeName));
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
referenceTypes.push(this.getModelReference(declaration, refTypeName));
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
const referenceType = referenceTransformer_1.ReferenceTransformer.merge(referenceTypes);
|
|
826
|
+
this.addToLocalReferenceTypeCache(name, referenceType);
|
|
827
|
+
return referenceType;
|
|
828
|
+
}
|
|
829
|
+
catch (err) {
|
|
830
|
+
// eslint-disable-next-line no-console
|
|
831
|
+
console.error(`There was a problem resolving type of '${name}'.`);
|
|
832
|
+
throw err;
|
|
833
|
+
}
|
|
834
|
+
};
|
|
835
|
+
const result = calcReferenceType();
|
|
836
|
+
if (addToRefTypeMap) {
|
|
837
|
+
this.current.AddReferenceType(result);
|
|
838
|
+
}
|
|
839
|
+
return result;
|
|
840
|
+
}
|
|
841
|
+
addToLocalReferenceTypeCache(name, refType) {
|
|
842
|
+
if (inProgressTypes[name]) {
|
|
843
|
+
for (const fn of inProgressTypes[name]) {
|
|
844
|
+
fn(refType);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
localReferenceTypeCache[name] = refType;
|
|
848
|
+
delete inProgressTypes[name];
|
|
849
|
+
}
|
|
850
|
+
getModelReference(modelType, refTypeName) {
|
|
851
|
+
const example = this.getNodeExample(modelType);
|
|
852
|
+
const description = this.getNodeDescription(modelType);
|
|
853
|
+
const deprecated = (0, jsDocUtils_1.isExistJSDocTag)(modelType, tag => tag.tagName.text === 'deprecated') || (0, decoratorUtils_1.isDecorator)(modelType, identifier => identifier.text === 'Deprecated');
|
|
854
|
+
const title = this.getNodeTitle(modelType);
|
|
855
|
+
// Handle toJSON methods
|
|
856
|
+
(0, flowUtils_1.throwUnless)(modelType.name, new exceptions_1.GenerateMetadataError("Can't get Symbol from anonymous class", modelType));
|
|
857
|
+
const type = this.current.typeChecker.getTypeAtLocation(modelType.name);
|
|
858
|
+
const toJSON = this.current.typeChecker.getPropertyOfType(type, 'toJSON');
|
|
859
|
+
if (toJSON && toJSON.valueDeclaration && (ts.isMethodDeclaration(toJSON.valueDeclaration) || ts.isMethodSignature(toJSON.valueDeclaration))) {
|
|
860
|
+
let nodeType = toJSON.valueDeclaration.type;
|
|
861
|
+
if (!nodeType) {
|
|
862
|
+
const signature = this.current.typeChecker.getSignatureFromDeclaration(toJSON.valueDeclaration);
|
|
863
|
+
const implicitType = this.current.typeChecker.getReturnTypeOfSignature(signature);
|
|
864
|
+
nodeType = this.current.typeChecker.typeToTypeNode(implicitType, undefined, ts.NodeBuilderFlags.NoTruncation);
|
|
865
|
+
}
|
|
866
|
+
const type = new TypeResolver(nodeType, this.current).resolve();
|
|
867
|
+
const referenceType = {
|
|
868
|
+
refName: refTypeName,
|
|
869
|
+
dataType: 'refAlias',
|
|
870
|
+
description,
|
|
871
|
+
type,
|
|
872
|
+
validators: {},
|
|
873
|
+
deprecated,
|
|
874
|
+
...(example && { example }),
|
|
875
|
+
...(title && { title }),
|
|
876
|
+
};
|
|
877
|
+
return referenceType;
|
|
878
|
+
}
|
|
879
|
+
const properties = new propertyTransformer_1.PropertyTransformer().transform(this, modelType);
|
|
880
|
+
const additionalProperties = this.getModelAdditionalProperties(modelType);
|
|
881
|
+
const inheritedProperties = this.getModelInheritedProperties(modelType) || [];
|
|
882
|
+
const referenceType = {
|
|
883
|
+
additionalProperties,
|
|
884
|
+
dataType: 'refObject',
|
|
885
|
+
description,
|
|
886
|
+
properties: inheritedProperties,
|
|
887
|
+
refName: refTypeName,
|
|
888
|
+
deprecated,
|
|
889
|
+
...(example && { example }),
|
|
890
|
+
...(title && { title }),
|
|
891
|
+
};
|
|
892
|
+
referenceType.properties = referenceType.properties.concat(properties);
|
|
893
|
+
return referenceType;
|
|
894
|
+
}
|
|
895
|
+
//Generates a name from the original type expression.
|
|
896
|
+
//This function is not invertable, so it's possible, that 2 type expressions have the same refTypeName.
|
|
897
|
+
getRefTypeName(name) {
|
|
898
|
+
const preformattedName = name //Preformatted name handles most cases
|
|
899
|
+
.replace(/<|>/g, '_')
|
|
900
|
+
.replace(/\s+/g, '')
|
|
901
|
+
.replace(/,/g, '.')
|
|
902
|
+
.replace(/'([^']*)'/g, '$1')
|
|
903
|
+
.replace(/"([^"]*)"/g, '$1')
|
|
904
|
+
.replace(/&/g, '-and-')
|
|
905
|
+
.replace(/\|/g, '-or-')
|
|
906
|
+
.replace(/\[\]/g, '-Array')
|
|
907
|
+
.replace(/{|}/g, '_') // SuccessResponse_{indexesCreated-number}_ -> SuccessResponse__indexesCreated-number__
|
|
908
|
+
.replace(/([a-z_0-9]+\??):([a-z]+)/gi, '$1-$2') // SuccessResponse_indexesCreated:number_ -> SuccessResponse_indexesCreated-number_
|
|
909
|
+
.replace(/;/g, '--')
|
|
910
|
+
.replace(/([a-z})\]])\[([a-z]+)\]/gi, '$1-at-$2'); // Partial_SerializedDatasourceWithVersion[format]_ -> Partial_SerializedDatasourceWithVersion~format~_,
|
|
911
|
+
//Safety fixes to replace all characters which are not accepted by swagger ui
|
|
912
|
+
let formattedName = preformattedName.replace(/[^A-Za-z0-9\-._]/g, match => {
|
|
913
|
+
return `_${match.charCodeAt(0)}_`;
|
|
914
|
+
});
|
|
915
|
+
formattedName = formattedName.replace(/92_r_92_n/g, '92_n'); //Windows uses \r\n, but linux uses \n.
|
|
916
|
+
return formattedName;
|
|
917
|
+
}
|
|
918
|
+
createCircularDependencyResolver(refName, refTypeName) {
|
|
919
|
+
const referenceType = {
|
|
920
|
+
dataType: 'refObject',
|
|
921
|
+
refName: refTypeName,
|
|
922
|
+
};
|
|
923
|
+
inProgressTypes[refName].push(realReferenceType => {
|
|
924
|
+
for (const key of Object.keys(realReferenceType)) {
|
|
925
|
+
;
|
|
926
|
+
referenceType[key] = realReferenceType[key];
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
return referenceType;
|
|
930
|
+
}
|
|
931
|
+
nodeIsUsable(node) {
|
|
932
|
+
switch (node.kind) {
|
|
933
|
+
case ts.SyntaxKind.InterfaceDeclaration:
|
|
934
|
+
case ts.SyntaxKind.ClassDeclaration:
|
|
935
|
+
case ts.SyntaxKind.TypeAliasDeclaration:
|
|
936
|
+
case ts.SyntaxKind.EnumDeclaration:
|
|
937
|
+
case ts.SyntaxKind.EnumMember:
|
|
938
|
+
return true;
|
|
939
|
+
default:
|
|
940
|
+
return false;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
getModelTypeDeclarations(type) {
|
|
944
|
+
let typeName = type.kind === ts.SyntaxKind.Identifier ? type.text : type.right.text;
|
|
945
|
+
let symbol = this.getSymbolAtLocation(type);
|
|
946
|
+
if (!symbol && type.kind === ts.SyntaxKind.QualifiedName) {
|
|
947
|
+
const fullEnumSymbol = this.getSymbolAtLocation(type.left);
|
|
948
|
+
symbol = fullEnumSymbol.exports?.get(typeName);
|
|
949
|
+
}
|
|
950
|
+
// Handle built-in types that don't have declarations in user code
|
|
951
|
+
if (!symbol || !symbol.getDeclarations) {
|
|
952
|
+
return [];
|
|
953
|
+
}
|
|
954
|
+
const declarations = symbol.getDeclarations();
|
|
955
|
+
if (!declarations || declarations.length === 0) {
|
|
956
|
+
return [];
|
|
957
|
+
}
|
|
958
|
+
if (symbol.escapedName !== typeName && symbol.escapedName !== 'default') {
|
|
959
|
+
typeName = symbol.escapedName;
|
|
960
|
+
}
|
|
961
|
+
let modelTypes = declarations.filter((node) => {
|
|
962
|
+
return this.nodeIsUsable(node) && node.name?.getText() === typeName;
|
|
963
|
+
});
|
|
964
|
+
// If no usable model types found, return empty array instead of throwing
|
|
965
|
+
if (modelTypes.length === 0) {
|
|
966
|
+
return [];
|
|
967
|
+
}
|
|
968
|
+
if (modelTypes.length > 1) {
|
|
969
|
+
// remove types that are from typescript e.g. 'Account'
|
|
970
|
+
modelTypes = modelTypes.filter(modelType => {
|
|
971
|
+
return modelType.getSourceFile().fileName.replace(/\\/g, '/').toLowerCase().indexOf('node_modules/typescript') <= -1;
|
|
972
|
+
});
|
|
973
|
+
modelTypes = this.getDesignatedModels(modelTypes, typeName);
|
|
974
|
+
}
|
|
975
|
+
return modelTypes;
|
|
976
|
+
}
|
|
977
|
+
getSymbolAtLocation(type) {
|
|
978
|
+
const symbol = this.current.typeChecker.getSymbolAtLocation(type) || type.symbol;
|
|
979
|
+
// resolve alias if it is an alias, otherwise take symbol directly
|
|
980
|
+
return (symbol && this.hasFlag(symbol, ts.SymbolFlags.Alias) && this.current.typeChecker.getAliasedSymbol(symbol)) || symbol;
|
|
981
|
+
}
|
|
982
|
+
getModelAdditionalProperties(node) {
|
|
983
|
+
if (node.kind === ts.SyntaxKind.InterfaceDeclaration) {
|
|
984
|
+
const interfaceDeclaration = node;
|
|
985
|
+
const indexMember = interfaceDeclaration.members.find(member => member.kind === ts.SyntaxKind.IndexSignature);
|
|
986
|
+
if (!indexMember) {
|
|
987
|
+
return undefined;
|
|
988
|
+
}
|
|
989
|
+
const indexSignatureDeclaration = indexMember;
|
|
990
|
+
const indexType = new TypeResolver(indexSignatureDeclaration.parameters[0].type, this.current, this.parentNode, this.context).resolve();
|
|
991
|
+
(0, flowUtils_1.throwUnless)(indexType.dataType === 'string', new exceptions_1.GenerateMetadataError(`Only string indexers are supported.`, this.typeNode));
|
|
992
|
+
return new TypeResolver(indexSignatureDeclaration.type, this.current, this.parentNode, this.context).resolve();
|
|
993
|
+
}
|
|
994
|
+
return undefined;
|
|
995
|
+
}
|
|
996
|
+
typeArgumentsToContext(type, targetEntity) {
|
|
997
|
+
let newContext = {};
|
|
998
|
+
// Handle cases where targetEntity might be an inline object type
|
|
999
|
+
// Inline object types don't have declarations, so we need to handle them differently
|
|
1000
|
+
let declarations;
|
|
1001
|
+
try {
|
|
1002
|
+
declarations = this.getModelTypeDeclarations(targetEntity);
|
|
1003
|
+
}
|
|
1004
|
+
catch (error) {
|
|
1005
|
+
// If we can't get declarations (e.g., inline object type),
|
|
1006
|
+
// we can't process type parameters, so return empty context
|
|
1007
|
+
return newContext;
|
|
1008
|
+
}
|
|
1009
|
+
const typeParameters = declarations[0] && 'typeParameters' in declarations[0] ? declarations[0].typeParameters : undefined;
|
|
1010
|
+
if (typeParameters) {
|
|
1011
|
+
for (let index = 0; index < typeParameters.length; index++) {
|
|
1012
|
+
const typeParameter = typeParameters[index];
|
|
1013
|
+
const typeArg = type.typeArguments && type.typeArguments[index];
|
|
1014
|
+
let resolvedType;
|
|
1015
|
+
let name;
|
|
1016
|
+
// Argument may be a forward reference from context
|
|
1017
|
+
if (typeArg && ts.isTypeReferenceNode(typeArg) && ts.isIdentifier(typeArg.typeName) && this.context[typeArg.typeName.text]) {
|
|
1018
|
+
resolvedType = this.context[typeArg.typeName.text].type;
|
|
1019
|
+
name = this.context[typeArg.typeName.text].name;
|
|
1020
|
+
}
|
|
1021
|
+
else if (typeArg) {
|
|
1022
|
+
resolvedType = typeArg;
|
|
1023
|
+
}
|
|
1024
|
+
else if (typeParameter.default) {
|
|
1025
|
+
resolvedType = typeParameter.default;
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
throw new exceptions_1.GenerateMetadataError(`Could not find a value for type parameter ${typeParameter.name.text}`, type);
|
|
1029
|
+
}
|
|
1030
|
+
newContext = {
|
|
1031
|
+
...newContext,
|
|
1032
|
+
[typeParameter.name.text]: {
|
|
1033
|
+
type: resolvedType,
|
|
1034
|
+
name: name || this.calcTypeName(resolvedType),
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
return newContext;
|
|
1040
|
+
}
|
|
1041
|
+
getModelInheritedProperties(modelTypeDeclaration) {
|
|
1042
|
+
let properties = [];
|
|
1043
|
+
const heritageClauses = modelTypeDeclaration.heritageClauses;
|
|
1044
|
+
if (!heritageClauses) {
|
|
1045
|
+
return properties;
|
|
1046
|
+
}
|
|
1047
|
+
for (const clause of heritageClauses) {
|
|
1048
|
+
if (!clause.types) {
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
for (const t of clause.types) {
|
|
1052
|
+
const baseEntityName = t.expression;
|
|
1053
|
+
// create subContext
|
|
1054
|
+
const resetCtx = this.context;
|
|
1055
|
+
this.context = this.typeArgumentsToContext(t, baseEntityName);
|
|
1056
|
+
const referenceType = this.getReferenceType(t, false);
|
|
1057
|
+
if (referenceType) {
|
|
1058
|
+
if (referenceType.dataType === 'refEnum') {
|
|
1059
|
+
// since it doesn't have properties to iterate over, then we don't do anything with it
|
|
1060
|
+
}
|
|
1061
|
+
else if (referenceType.dataType === 'refAlias') {
|
|
1062
|
+
let type = referenceType;
|
|
1063
|
+
while (type.dataType === 'refAlias') {
|
|
1064
|
+
type = type.type;
|
|
1065
|
+
}
|
|
1066
|
+
if (type.dataType === 'refObject') {
|
|
1067
|
+
properties = [...properties, ...type.properties];
|
|
1068
|
+
}
|
|
1069
|
+
else if (type.dataType === 'nestedObjectLiteral') {
|
|
1070
|
+
properties = [...properties, ...type.properties];
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
else if (referenceType.dataType === 'refObject') {
|
|
1074
|
+
;
|
|
1075
|
+
(referenceType.properties || []).forEach(property => properties.push(property));
|
|
1076
|
+
}
|
|
1077
|
+
else {
|
|
1078
|
+
(0, runtime_1.assertNever)(referenceType);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
// reset subContext
|
|
1082
|
+
this.context = resetCtx;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
return properties;
|
|
1086
|
+
}
|
|
1087
|
+
getNodeDescription(node) {
|
|
1088
|
+
const symbol = this.getSymbolAtLocation(node.name);
|
|
1089
|
+
if (!symbol) {
|
|
1090
|
+
return undefined;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* TODO: Workaround for what seems like a bug in the compiler
|
|
1094
|
+
* Warrants more investigation and possibly a PR against typescript
|
|
1095
|
+
*/
|
|
1096
|
+
if (node.kind === ts.SyntaxKind.Parameter) {
|
|
1097
|
+
// TypeScript won't parse jsdoc if the flag is 4, i.e. 'Property'
|
|
1098
|
+
symbol.flags = 0;
|
|
1099
|
+
}
|
|
1100
|
+
const comments = symbol.getDocumentationComment(this.current.typeChecker);
|
|
1101
|
+
if (comments.length) {
|
|
1102
|
+
return ts.displayPartsToString(comments);
|
|
1103
|
+
}
|
|
1104
|
+
return undefined;
|
|
1105
|
+
}
|
|
1106
|
+
getNodeFormat(node) {
|
|
1107
|
+
return (0, jsDocUtils_1.getJSDocComment)(node, 'format');
|
|
1108
|
+
}
|
|
1109
|
+
getNodeTitle(node) {
|
|
1110
|
+
return (0, jsDocUtils_1.getJSDocComment)(node, 'title');
|
|
1111
|
+
}
|
|
1112
|
+
getPropertyName(prop) {
|
|
1113
|
+
if (ts.isComputedPropertyName(prop.name) && ts.isPropertyAccessExpression(prop.name.expression)) {
|
|
1114
|
+
const initializerValue = (0, initializer_value_1.getInitializerValue)(prop.name.expression, this.current.typeChecker);
|
|
1115
|
+
if (initializerValue) {
|
|
1116
|
+
return initializerValue?.toString();
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
return prop.name.text;
|
|
1120
|
+
}
|
|
1121
|
+
getNodeExample(node) {
|
|
1122
|
+
const exampleJSDoc = (0, jsDocUtils_1.getJSDocComment)(node, 'example');
|
|
1123
|
+
if (exampleJSDoc) {
|
|
1124
|
+
return (0, jsonUtils_1.safeFromJson)(exampleJSDoc);
|
|
1125
|
+
}
|
|
1126
|
+
return (0, decoratorUtils_1.getNodeFirstDecoratorValue)(node, this.current.typeChecker, dec => dec.text === 'Example');
|
|
1127
|
+
}
|
|
1128
|
+
getNodeExtension(node) {
|
|
1129
|
+
const decorators = this.getDecoratorsByIdentifier(node, 'Extension');
|
|
1130
|
+
const extensionDecorator = (0, extension_1.getExtensions)(decorators, this.current);
|
|
1131
|
+
const extensionComments = (0, jsDocUtils_1.getJSDocComments)(node, 'extension');
|
|
1132
|
+
const extensionJSDoc = extensionComments ? (0, extension_1.getExtensionsFromJSDocComments)(extensionComments) : [];
|
|
1133
|
+
return extensionDecorator.concat(extensionJSDoc);
|
|
1134
|
+
}
|
|
1135
|
+
getDecoratorsByIdentifier(node, id) {
|
|
1136
|
+
return (0, decoratorUtils_1.getDecorators)(node, identifier => identifier.text === id);
|
|
1137
|
+
}
|
|
1138
|
+
static getDefault(node) {
|
|
1139
|
+
const defaultStr = (0, jsDocUtils_1.getJSDocComment)(node, 'default');
|
|
1140
|
+
if (typeof defaultStr == 'string' && defaultStr !== 'undefined') {
|
|
1141
|
+
let textStartCharacter = undefined;
|
|
1142
|
+
const inString = () => textStartCharacter !== undefined;
|
|
1143
|
+
let formattedStr = '';
|
|
1144
|
+
for (let i = 0; i < defaultStr.length; ++i) {
|
|
1145
|
+
const actCharacter = defaultStr[i];
|
|
1146
|
+
if (inString()) {
|
|
1147
|
+
if (actCharacter === textStartCharacter) {
|
|
1148
|
+
formattedStr += '"';
|
|
1149
|
+
textStartCharacter = undefined;
|
|
1150
|
+
}
|
|
1151
|
+
else if (actCharacter === '"') {
|
|
1152
|
+
formattedStr += '\\"';
|
|
1153
|
+
}
|
|
1154
|
+
else if (actCharacter === '\\') {
|
|
1155
|
+
++i;
|
|
1156
|
+
if (i < defaultStr.length) {
|
|
1157
|
+
const nextCharacter = defaultStr[i];
|
|
1158
|
+
if (['n', 't', 'r', 'b', 'f', '\\', '"'].includes(nextCharacter)) {
|
|
1159
|
+
formattedStr += '\\' + nextCharacter;
|
|
1160
|
+
}
|
|
1161
|
+
else if (!['v', '0'].includes(nextCharacter)) {
|
|
1162
|
+
//\v, \0 characters are not compatible with JSON
|
|
1163
|
+
formattedStr += nextCharacter;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
else {
|
|
1167
|
+
formattedStr += actCharacter; // this is a bug, but let the JSON parser decide how to handle it
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
else {
|
|
1171
|
+
formattedStr += actCharacter;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
else {
|
|
1175
|
+
if ([`"`, "'", '`'].includes(actCharacter)) {
|
|
1176
|
+
textStartCharacter = actCharacter;
|
|
1177
|
+
formattedStr += '"';
|
|
1178
|
+
}
|
|
1179
|
+
else if (actCharacter === '/' && i + 1 < defaultStr.length && defaultStr[i + 1] === '/') {
|
|
1180
|
+
i += 2;
|
|
1181
|
+
while (i < defaultStr.length && defaultStr[i] !== '\n') {
|
|
1182
|
+
++i;
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
else {
|
|
1186
|
+
formattedStr += actCharacter;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
try {
|
|
1191
|
+
const parsed = JSON.parse(formattedStr);
|
|
1192
|
+
return parsed;
|
|
1193
|
+
}
|
|
1194
|
+
catch (err) {
|
|
1195
|
+
throw new exceptions_1.GenerateMetadataError(`JSON could not parse default str: "${defaultStr}", preformatted: "${formattedStr}"\nmessage: "${err?.message || '-'}"`);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
return undefined;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
exports.TypeResolver = TypeResolver;
|
|
1202
|
+
//# sourceMappingURL=typeResolver.js.map
|