1 | "use strict";
|
2 | var _a;
|
3 | Object.defineProperty(exports, "__esModule", { value: true });
|
4 | exports.ModelClassVisitor = void 0;
|
5 | const lodash_1 = require("lodash");
|
6 | const ts = require("typescript");
|
7 | const typescript_1 = require("typescript");
|
8 | const decorators_1 = require("../../decorators");
|
9 | const plugin_constants_1 = require("../plugin-constants");
|
10 | const ast_utils_1 = require("../utils/ast-utils");
|
11 | const plugin_utils_1 = require("../utils/plugin-utils");
|
12 | const abstract_visitor_1 = require("./abstract.visitor");
|
13 | const [tsVersionMajor, tsVersionMinor] = (_a = ts.versionMajorMinor) === null || _a === void 0 ? void 0 : _a.split('.').map((x) => +x);
|
14 | const isInUpdatedAstContext = tsVersionMinor >= 8 || tsVersionMajor > 4;
|
15 | class ModelClassVisitor extends abstract_visitor_1.AbstractFileVisitor {
|
16 | visit(sourceFile, ctx, program, options) {
|
17 | const typeChecker = program.getTypeChecker();
|
18 | sourceFile = this.updateImports(sourceFile, ctx.factory, program);
|
19 | const propertyNodeVisitorFactory = (metadata) => (node) => {
|
20 | if (ts.isPropertyDeclaration(node)) {
|
21 | const decorators = ts.canHaveDecorators
|
22 | ? ts.getDecorators(node)
|
23 | : node.decorators;
|
24 | const hidePropertyDecorator = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)([decorators_1.ApiHideProperty.name], decorators, typescript_1.factory);
|
25 | if (hidePropertyDecorator) {
|
26 | return node;
|
27 | }
|
28 | const isPropertyStatic = (node.modifiers || []).some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword);
|
29 | if (isPropertyStatic) {
|
30 | return node;
|
31 | }
|
32 | try {
|
33 | this.inspectPropertyDeclaration(ctx.factory, node, typeChecker, options, sourceFile.fileName, sourceFile, metadata);
|
34 | }
|
35 | catch (err) {
|
36 | return node;
|
37 | }
|
38 | }
|
39 | return node;
|
40 | };
|
41 | const visitClassNode = (node) => {
|
42 | if (ts.isClassDeclaration(node)) {
|
43 | const metadata = {};
|
44 | node = ts.visitEachChild(node, propertyNodeVisitorFactory(metadata), ctx);
|
45 | return this.addMetadataFactory(ctx.factory, node, metadata);
|
46 | }
|
47 | return ts.visitEachChild(node, visitClassNode, ctx);
|
48 | };
|
49 | return ts.visitNode(sourceFile, visitClassNode);
|
50 | }
|
51 | addMetadataFactory(factory, node, classMetadata) {
|
52 | const returnValue = factory.createObjectLiteralExpression(Object.keys(classMetadata).map((key) => factory.createPropertyAssignment(factory.createIdentifier(key), classMetadata[key])));
|
53 | const method = isInUpdatedAstContext
|
54 | ? factory.createMethodDeclaration([factory.createModifier(ts.SyntaxKind.StaticKeyword)], undefined, factory.createIdentifier(plugin_constants_1.METADATA_FACTORY_NAME), undefined, undefined, [], undefined, factory.createBlock([factory.createReturnStatement(returnValue)], true))
|
55 | : factory.createMethodDeclaration(undefined, [factory.createModifier(ts.SyntaxKind.StaticKeyword)], undefined, factory.createIdentifier(plugin_constants_1.METADATA_FACTORY_NAME), undefined, undefined, [], undefined, factory.createBlock([factory.createReturnStatement(returnValue)], true));
|
56 | return isInUpdatedAstContext
|
57 | ? factory.updateClassDeclaration(node, node.modifiers, node.name, node.typeParameters, node.heritageClauses, [...node.members, method])
|
58 | : factory.updateClassDeclaration(node, node.decorators, node.modifiers, node.name, node.typeParameters, node.heritageClauses, [...node.members, method]);
|
59 | }
|
60 | inspectPropertyDeclaration(factory, compilerNode, typeChecker, options, hostFilename, sourceFile, metadata) {
|
61 | const objectLiteralExpr = this.createDecoratorObjectLiteralExpr(factory, compilerNode, typeChecker, factory.createNodeArray(), options, hostFilename, sourceFile);
|
62 | this.addClassMetadata(compilerNode, objectLiteralExpr, sourceFile, metadata);
|
63 | }
|
64 | createDecoratorObjectLiteralExpr(factory, node, typeChecker, existingProperties = factory.createNodeArray(), options = {}, hostFilename = '', sourceFile) {
|
65 | const isRequired = !node.questionToken;
|
66 | let properties = [
|
67 | ...existingProperties,
|
68 | !(0, plugin_utils_1.hasPropertyKey)('required', existingProperties) &&
|
69 | factory.createPropertyAssignment('required', (0, ast_utils_1.createBooleanLiteral)(factory, isRequired)),
|
70 | ...this.createTypePropertyAssignments(factory, node.type, typeChecker, existingProperties, hostFilename),
|
71 | ...this.createDescriptionAndTsDocTagPropertyAssigments(factory, node, typeChecker, existingProperties, options, sourceFile),
|
72 | this.createDefaultPropertyAssignment(factory, node, existingProperties),
|
73 | this.createEnumPropertyAssignment(factory, node, typeChecker, existingProperties, hostFilename)
|
74 | ];
|
75 | if (options.classValidatorShim) {
|
76 | properties = properties.concat(this.createValidationPropertyAssignments(factory, node));
|
77 | }
|
78 | return factory.createObjectLiteralExpression((0, lodash_1.compact)((0, lodash_1.flatten)(properties)));
|
79 | }
|
80 | createTypePropertyAssignments(factory, node, typeChecker, existingProperties, hostFilename) {
|
81 | const key = 'type';
|
82 | if ((0, plugin_utils_1.hasPropertyKey)(key, existingProperties)) {
|
83 | return [];
|
84 | }
|
85 | if (node) {
|
86 | if (ts.isTypeLiteralNode(node)) {
|
87 | const propertyAssignments = Array.from(node.members || []).map((member) => {
|
88 | const literalExpr = this.createDecoratorObjectLiteralExpr(factory, member, typeChecker, existingProperties, {}, hostFilename);
|
89 | return factory.createPropertyAssignment(factory.createIdentifier(member.name.getText()), literalExpr);
|
90 | });
|
91 | return [
|
92 | factory.createPropertyAssignment(key, factory.createArrowFunction(undefined, undefined, [], undefined, undefined, factory.createParenthesizedExpression(factory.createObjectLiteralExpression(propertyAssignments))))
|
93 | ];
|
94 | }
|
95 | else if (ts.isUnionTypeNode(node)) {
|
96 | const nullableType = node.types.find((type) => type.kind === ts.SyntaxKind.NullKeyword ||
|
97 | (ts.SyntaxKind.LiteralType && type.getText() === 'null'));
|
98 | const isNullable = !!nullableType;
|
99 | const remainingTypes = node.types.filter((item) => item !== nullableType);
|
100 | if (remainingTypes.length === 1) {
|
101 | const remainingTypesProperties = this.createTypePropertyAssignments(factory, remainingTypes[0], typeChecker, existingProperties, hostFilename);
|
102 | const resultArray = new Array(...remainingTypesProperties);
|
103 | if (isNullable) {
|
104 | const nullablePropertyAssignment = factory.createPropertyAssignment('nullable', (0, ast_utils_1.createBooleanLiteral)(factory, true));
|
105 | resultArray.push(nullablePropertyAssignment);
|
106 | }
|
107 | return resultArray;
|
108 | }
|
109 | }
|
110 | }
|
111 | const type = typeChecker.getTypeAtLocation(node);
|
112 | if (!type) {
|
113 | return [];
|
114 | }
|
115 | let typeReference = (0, plugin_utils_1.getTypeReferenceAsString)(type, typeChecker);
|
116 | if (!typeReference) {
|
117 | return [];
|
118 | }
|
119 | typeReference = (0, plugin_utils_1.replaceImportPath)(typeReference, hostFilename);
|
120 | return [
|
121 | factory.createPropertyAssignment(key, factory.createArrowFunction(undefined, undefined, [], undefined, undefined, factory.createIdentifier(typeReference)))
|
122 | ];
|
123 | }
|
124 | createEnumPropertyAssignment(factory, node, typeChecker, existingProperties, hostFilename) {
|
125 | const key = 'enum';
|
126 | if ((0, plugin_utils_1.hasPropertyKey)(key, existingProperties)) {
|
127 | return undefined;
|
128 | }
|
129 | let type = typeChecker.getTypeAtLocation(node);
|
130 | if (!type) {
|
131 | return undefined;
|
132 | }
|
133 | if ((0, plugin_utils_1.isAutoGeneratedTypeUnion)(type)) {
|
134 | const types = type.types;
|
135 | type = types[types.length - 1];
|
136 | }
|
137 | const typeIsArrayTuple = (0, plugin_utils_1.extractTypeArgumentIfArray)(type);
|
138 | if (!typeIsArrayTuple) {
|
139 | return undefined;
|
140 | }
|
141 | let isArrayType = typeIsArrayTuple.isArray;
|
142 | type = typeIsArrayTuple.type;
|
143 | const isEnumMember = type.symbol && type.symbol.flags === ts.SymbolFlags.EnumMember;
|
144 | if (!(0, ast_utils_1.isEnum)(type) || isEnumMember) {
|
145 | if (!isEnumMember) {
|
146 | type = (0, plugin_utils_1.isAutoGeneratedEnumUnion)(type, typeChecker);
|
147 | }
|
148 | if (!type) {
|
149 | return undefined;
|
150 | }
|
151 | const typeIsArrayTuple = (0, plugin_utils_1.extractTypeArgumentIfArray)(type);
|
152 | if (!typeIsArrayTuple) {
|
153 | return undefined;
|
154 | }
|
155 | isArrayType = typeIsArrayTuple.isArray;
|
156 | type = typeIsArrayTuple.type;
|
157 | }
|
158 | const enumRef = (0, plugin_utils_1.replaceImportPath)((0, ast_utils_1.getText)(type, typeChecker), hostFilename);
|
159 | const enumProperty = factory.createPropertyAssignment(key, factory.createIdentifier(enumRef));
|
160 | if (isArrayType) {
|
161 | const isArrayKey = 'isArray';
|
162 | const isArrayProperty = factory.createPropertyAssignment(isArrayKey, factory.createIdentifier('true'));
|
163 | return [enumProperty, isArrayProperty];
|
164 | }
|
165 | return enumProperty;
|
166 | }
|
167 | createDefaultPropertyAssignment(factory, node, existingProperties) {
|
168 | const key = 'default';
|
169 | if ((0, plugin_utils_1.hasPropertyKey)(key, existingProperties)) {
|
170 | return undefined;
|
171 | }
|
172 | let initializer = node.initializer;
|
173 | if (!initializer) {
|
174 | return undefined;
|
175 | }
|
176 | if (ts.isAsExpression(initializer)) {
|
177 | initializer = initializer.expression;
|
178 | }
|
179 | return factory.createPropertyAssignment(key, initializer);
|
180 | }
|
181 | createValidationPropertyAssignments(factory, node) {
|
182 | const assignments = [];
|
183 | const decorators = ts.canHaveDecorators
|
184 | ? ts.getDecorators(node)
|
185 | : node.decorators;
|
186 | this.addPropertyByValidationDecorator(factory, 'IsIn', 'enum', decorators, assignments);
|
187 | this.addPropertyByValidationDecorator(factory, 'Min', 'minimum', decorators, assignments);
|
188 | this.addPropertyByValidationDecorator(factory, 'Max', 'maximum', decorators, assignments);
|
189 | this.addPropertyByValidationDecorator(factory, 'MinLength', 'minLength', decorators, assignments);
|
190 | this.addPropertyByValidationDecorator(factory, 'MaxLength', 'maxLength', decorators, assignments);
|
191 | this.addPropertiesByValidationDecorator(factory, 'IsPositive', decorators, assignments, () => {
|
192 | return [
|
193 | factory.createPropertyAssignment('minimum', (0, ast_utils_1.createPrimitiveLiteral)(factory, 1))
|
194 | ];
|
195 | });
|
196 | this.addPropertiesByValidationDecorator(factory, 'IsNegative', decorators, assignments, () => {
|
197 | return [
|
198 | factory.createPropertyAssignment('maximum', (0, ast_utils_1.createPrimitiveLiteral)(factory, -1))
|
199 | ];
|
200 | });
|
201 | this.addPropertiesByValidationDecorator(factory, 'Length', decorators, assignments, (decoratorRef) => {
|
202 | const decoratorArguments = (0, ast_utils_1.getDecoratorArguments)(decoratorRef);
|
203 | const result = [];
|
204 | result.push(factory.createPropertyAssignment('minLength', (0, lodash_1.head)(decoratorArguments)));
|
205 | if (decoratorArguments.length > 1) {
|
206 | result.push(factory.createPropertyAssignment('maxLength', decoratorArguments[1]));
|
207 | }
|
208 | return result;
|
209 | });
|
210 | this.addPropertiesByValidationDecorator(factory, 'Matches', decorators, assignments, (decoratorRef) => {
|
211 | const decoratorArguments = (0, ast_utils_1.getDecoratorArguments)(decoratorRef);
|
212 | return [
|
213 | factory.createPropertyAssignment('pattern', (0, ast_utils_1.createPrimitiveLiteral)(factory, (0, lodash_1.head)(decoratorArguments).text))
|
214 | ];
|
215 | });
|
216 | return assignments;
|
217 | }
|
218 | addPropertyByValidationDecorator(factory, decoratorName, propertyKey, decorators, assignments) {
|
219 | this.addPropertiesByValidationDecorator(factory, decoratorName, decorators, assignments, (decoratorRef) => {
|
220 | const argument = (0, lodash_1.head)((0, ast_utils_1.getDecoratorArguments)(decoratorRef));
|
221 | if (argument) {
|
222 | return [factory.createPropertyAssignment(propertyKey, argument)];
|
223 | }
|
224 | return [];
|
225 | });
|
226 | }
|
227 | addPropertiesByValidationDecorator(factory, decoratorName, decorators, assignments, addPropertyAssignments) {
|
228 | const decoratorRef = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)([decoratorName], decorators, factory);
|
229 | if (!decoratorRef) {
|
230 | return;
|
231 | }
|
232 | assignments.push(...addPropertyAssignments(decoratorRef));
|
233 | }
|
234 | addClassMetadata(node, objectLiteral, sourceFile, metadata) {
|
235 | const hostClass = node.parent;
|
236 | const className = hostClass.name && hostClass.name.getText();
|
237 | if (!className) {
|
238 | return;
|
239 | }
|
240 | const propertyName = node.name && node.name.getText(sourceFile);
|
241 | if (!propertyName ||
|
242 | (node.name && node.name.kind === ts.SyntaxKind.ComputedPropertyName)) {
|
243 | return;
|
244 | }
|
245 | metadata[propertyName] = objectLiteral;
|
246 | }
|
247 | createDescriptionAndTsDocTagPropertyAssigments(factory, node, typeChecker, existingProperties = factory.createNodeArray(), options = {}, sourceFile) {
|
248 | var _a;
|
249 | if (!options.introspectComments || !sourceFile) {
|
250 | return [];
|
251 | }
|
252 | const propertyAssignments = [];
|
253 | const comments = (0, ast_utils_1.getMainCommentOfNode)(node, sourceFile);
|
254 | const tags = (0, ast_utils_1.getTsDocTagsOfNode)(node, sourceFile, typeChecker);
|
255 | const keyOfComment = options.dtoKeyOfComment;
|
256 | if (!(0, plugin_utils_1.hasPropertyKey)(keyOfComment, existingProperties) && comments) {
|
257 | const descriptionPropertyAssignment = factory.createPropertyAssignment(keyOfComment, factory.createStringLiteral(comments));
|
258 | propertyAssignments.push(descriptionPropertyAssignment);
|
259 | }
|
260 | const hasExampleOrExamplesKey = (0, plugin_utils_1.hasPropertyKey)('example', existingProperties) ||
|
261 | (0, plugin_utils_1.hasPropertyKey)('examples', existingProperties);
|
262 | if (!hasExampleOrExamplesKey && ((_a = tags.example) === null || _a === void 0 ? void 0 : _a.length)) {
|
263 | if (tags.example.length === 1) {
|
264 | const examplePropertyAssignment = factory.createPropertyAssignment('example', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.example[0]));
|
265 | propertyAssignments.push(examplePropertyAssignment);
|
266 | }
|
267 | else {
|
268 | const examplesPropertyAssignment = factory.createPropertyAssignment('examples', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.example));
|
269 | propertyAssignments.push(examplesPropertyAssignment);
|
270 | }
|
271 | }
|
272 | const hasDeprecatedKey = (0, plugin_utils_1.hasPropertyKey)('deprecated', existingProperties);
|
273 | if (!hasDeprecatedKey && tags.deprecated) {
|
274 | const deprecatedPropertyAssignment = factory.createPropertyAssignment('deprecated', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.deprecated));
|
275 | propertyAssignments.push(deprecatedPropertyAssignment);
|
276 | }
|
277 | return propertyAssignments;
|
278 | }
|
279 | }
|
280 | exports.ModelClassVisitor = ModelClassVisitor;
|