UNPKG

16.1 kBJavaScriptView Raw
1"use strict";
2var _a;
3Object.defineProperty(exports, "__esModule", { value: true });
4exports.ModelClassVisitor = void 0;
5const lodash_1 = require("lodash");
6const ts = require("typescript");
7const typescript_1 = require("typescript");
8const decorators_1 = require("../../decorators");
9const plugin_constants_1 = require("../plugin-constants");
10const ast_utils_1 = require("../utils/ast-utils");
11const plugin_utils_1 = require("../utils/plugin-utils");
12const abstract_visitor_1 = require("./abstract.visitor");
13const [tsVersionMajor, tsVersionMinor] = (_a = ts.versionMajorMinor) === null || _a === void 0 ? void 0 : _a.split('.').map((x) => +x);
14const isInUpdatedAstContext = tsVersionMinor >= 8 || tsVersionMajor > 4;
15class 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}
280exports.ModelClassVisitor = ModelClassVisitor;