@nestjs/swagger
Version: 
Nest - modern, fast, powerful node.js web framework (@swagger)
156 lines (155 loc) • 7.55 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
const ts = require("typescript");
const decorators_1 = require("../../decorators");
const plugin_constants_1 = require("../plugin-constants");
const ast_utils_1 = require("../utils/ast-utils");
const plugin_utils_1 = require("../utils/plugin-utils");
const abstract_visitor_1 = require("./abstract.visitor");
class ModelClassVisitor extends abstract_visitor_1.AbstractFileVisitor {
    visit(sourceFile, ctx, program, options) {
        const typeChecker = program.getTypeChecker();
        sourceFile = this.updateImports(sourceFile);
        const visitNode = (node) => {
            if (ts.isPropertyDeclaration(node)) {
                const decorators = node.decorators;
                const hidePropertyDecorator = plugin_utils_1.getDecoratorOrUndefinedByNames([decorators_1.ApiHideProperty.name], decorators);
                if (hidePropertyDecorator) {
                    return node;
                }
                const propertyDecorator = plugin_utils_1.getDecoratorOrUndefinedByNames([decorators_1.ApiProperty.name, decorators_1.ApiPropertyOptional.name], decorators);
                if (!propertyDecorator) {
                    return this.addDecoratorToNode(node, typeChecker, options, sourceFile.fileName);
                }
                return this.addPropertiesToExisitingDecorator(propertyDecorator, node, typeChecker, options, sourceFile.fileName);
            }
            return ts.visitEachChild(node, visitNode, ctx);
        };
        return ts.visitNode(sourceFile, visitNode);
    }
    addDecoratorToNode(compilerNode, typeChecker, options, hostFilename) {
        const { pos, end } = compilerNode.decorators || ts.createNodeArray();
        compilerNode.decorators = Object.assign([
            ...(compilerNode.decorators || ts.createNodeArray()),
            ts.createDecorator(ts.createCall(ts.createIdentifier(`${plugin_constants_1.OPENAPI_NAMESPACE}.${decorators_1.ApiProperty.name}`), undefined, [
                this.createDecoratorObjectLiteralExpr(compilerNode, typeChecker, [], options, hostFilename)
            ]))
        ], { pos, end });
        return compilerNode;
    }
    addPropertiesToExisitingDecorator(compilerNode, originalNode, typeChecker, options, hostFilename) {
        const callExpr = compilerNode.expression;
        if (!callExpr) {
            return originalNode;
        }
        const callArgs = callExpr.arguments;
        if (!callArgs) {
            return originalNode;
        }
        const { pos, end } = callArgs;
        const decoratorArgument = lodash_1.head(callArgs);
        if (!decoratorArgument) {
            callExpr.arguments = Object.assign([
                this.createDecoratorObjectLiteralExpr(originalNode, typeChecker, [], options, hostFilename)
            ], { pos, end });
        }
        const decoratorProperties = (decoratorArgument && decoratorArgument.properties) || [];
        callExpr.arguments = Object.assign([
            this.createDecoratorObjectLiteralExpr(originalNode, typeChecker, decoratorProperties, options, hostFilename)
        ], {
            pos,
            end
        });
        return originalNode;
    }
    createDecoratorObjectLiteralExpr(node, typeChecker, existingProperties = [], options = {}, hostFilename = '') {
        const isRequired = !node.questionToken;
        let properties = [
            ...existingProperties,
            !plugin_utils_1.hasPropertyKey('required', existingProperties) &&
                ts.createPropertyAssignment('required', ts.createLiteral(isRequired)),
            this.createTypePropertyAssignment(node, typeChecker, existingProperties, hostFilename),
            this.createDefaultPropertyAssignment(node, existingProperties),
            this.createEnumPropertyAssignment(node, typeChecker, existingProperties, hostFilename)
        ];
        if (options.classValidatorShim) {
            properties = properties.concat(this.createValidationPropertyAssignments(node));
        }
        return ts.createObjectLiteral(lodash_1.compact(lodash_1.flatten(properties)));
    }
    createTypePropertyAssignment(node, typeChecker, existingProperties, hostFilename) {
        const key = 'type';
        if (plugin_utils_1.hasPropertyKey(key, existingProperties)) {
            return undefined;
        }
        const type = typeChecker.getTypeAtLocation(node);
        if (!type) {
            return undefined;
        }
        let typeReference = plugin_utils_1.getTypeReferenceAsString(type, typeChecker);
        if (!typeReference) {
            return undefined;
        }
        typeReference = plugin_utils_1.replaceImportPath(typeReference, hostFilename);
        return ts.createPropertyAssignment(key, ts.createArrowFunction(undefined, undefined, [], undefined, undefined, ts.createIdentifier(typeReference)));
    }
    createEnumPropertyAssignment(node, typeChecker, existingProperties, hostFilename) {
        const key = 'enum';
        if (plugin_utils_1.hasPropertyKey(key, existingProperties)) {
            return undefined;
        }
        let type = typeChecker.getTypeAtLocation(node);
        if (!type) {
            return undefined;
        }
        let isArrayType = false;
        if (ast_utils_1.isArray(type)) {
            type = ast_utils_1.getTypeArguments(type)[0];
            isArrayType = true;
            if (!type) {
                return undefined;
            }
        }
        if (!ast_utils_1.isEnum(type)) {
            return undefined;
        }
        const enumRef = plugin_utils_1.replaceImportPath(ast_utils_1.getText(type, typeChecker), hostFilename);
        const enumProperty = ts.createPropertyAssignment(key, ts.createIdentifier(enumRef));
        if (isArrayType) {
            const isArrayKey = 'isArray';
            const isArrayProperty = ts.createPropertyAssignment(isArrayKey, ts.createIdentifier('true'));
            return [enumProperty, isArrayProperty];
        }
        return enumProperty;
    }
    createDefaultPropertyAssignment(node, existingProperties) {
        const key = 'default';
        if (plugin_utils_1.hasPropertyKey(key, existingProperties)) {
            return undefined;
        }
        const initializer = node.initializer;
        if (!initializer) {
            return undefined;
        }
        return ts.createPropertyAssignment(key, ts.createIdentifier(initializer.getText()));
    }
    createValidationPropertyAssignments(node) {
        const assignments = [];
        const decorators = node.decorators;
        this.addPropertyByValidationDecorator('Min', 'minimum', decorators, assignments);
        this.addPropertyByValidationDecorator('Max', 'maximum', decorators, assignments);
        this.addPropertyByValidationDecorator('MinLength', 'minLength', decorators, assignments);
        this.addPropertyByValidationDecorator('MaxLength', 'maxLength', decorators, assignments);
        return assignments;
    }
    addPropertyByValidationDecorator(decoratorName, propertyKey, decorators, assignments) {
        const decoratorRef = plugin_utils_1.getDecoratorOrUndefinedByNames([decoratorName], decorators);
        if (!decoratorRef) {
            return;
        }
        const argument = lodash_1.head(ast_utils_1.getDecoratorArguments(decoratorRef));
        assignments.push(ts.createPropertyAssignment(propertyKey, ts.createIdentifier(argument && argument.getText())));
    }
}
exports.ModelClassVisitor = ModelClassVisitor;