@nestjs/swagger
Version:
Nest - modern, fast, powerful node.js web framework (@swagger)
266 lines (265 loc) • 14.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ControllerClassVisitor = void 0;
const lodash_1 = require("lodash");
const path_1 = require("path");
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 type_reference_to_identifier_util_1 = require("../utils/type-reference-to-identifier.util");
const abstract_visitor_1 = require("./abstract.visitor");
class ControllerClassVisitor extends abstract_visitor_1.AbstractFileVisitor {
constructor() {
super(...arguments);
this._collectedMetadata = {};
this._typeImports = {};
}
get typeImports() {
return this._typeImports;
}
collectedMetadata(options) {
const metadataWithImports = [];
Object.keys(this._collectedMetadata).forEach((filePath) => {
const metadata = this._collectedMetadata[filePath];
const fileExt = options.esmCompatible ? (0, plugin_utils_1.getOutputExtension)(filePath) : '';
const path = filePath.replace(/\.[jt]s$/, fileExt);
const importExpr = ts.factory.createCallExpression(ts.factory.createToken(ts.SyntaxKind.ImportKeyword), undefined, [ts.factory.createStringLiteral(path)]);
metadataWithImports.push([importExpr, metadata]);
});
return metadataWithImports;
}
visit(sourceFile, ctx, program, options) {
const typeChecker = program.getTypeChecker();
if (!options.readonly) {
sourceFile = this.updateImports(sourceFile, ctx.factory, program);
}
const visitNode = (node) => {
var _a;
if (ts.isMethodDeclaration(node)) {
try {
const metadata = {};
const updatedNode = this.addDecoratorToNode(ctx.factory, node, typeChecker, options, sourceFile, metadata);
if (!options.readonly) {
return updatedNode;
}
else {
const filePath = this.normalizeImportPath(options.pathToSource, sourceFile.fileName);
if (!this._collectedMetadata[filePath]) {
this._collectedMetadata[filePath] = {};
}
const parent = node.parent;
const clsName = (_a = parent.name) === null || _a === void 0 ? void 0 : _a.getText();
if (clsName) {
if (!this._collectedMetadata[filePath][clsName]) {
this._collectedMetadata[filePath][clsName] = {};
}
Object.assign(this._collectedMetadata[filePath][clsName], metadata);
}
}
}
catch (_b) {
if (!options.readonly) {
return node;
}
}
}
if (options.readonly) {
ts.forEachChild(node, visitNode);
}
else {
return ts.visitEachChild(node, visitNode, ctx);
}
};
return ts.visitNode(sourceFile, visitNode);
}
addDecoratorToNode(factory, compilerNode, typeChecker, options, sourceFile, metadata) {
var _a;
const hostFilename = sourceFile.fileName;
const decorators = ts.canHaveDecorators(compilerNode) && ts.getDecorators(compilerNode);
if (!decorators) {
return compilerNode;
}
const apiOperationDecoratorsArray = this.createApiOperationDecorator(factory, compilerNode, decorators, options, sourceFile, typeChecker, metadata);
const apiResponseDecoratorsArray = this.createApiResponseDecorator(factory, compilerNode, options, metadata);
const removeExistingApiOperationDecorator = apiOperationDecoratorsArray.length > 0;
const existingDecorators = removeExistingApiOperationDecorator
? decorators.filter((item) => (0, ast_utils_1.getDecoratorName)(item) !== decorators_1.ApiOperation.name)
: decorators;
const modifiers = (_a = ts.getModifiers(compilerNode)) !== null && _a !== void 0 ? _a : [];
const objectLiteralExpr = this.createDecoratorObjectLiteralExpr(factory, compilerNode, typeChecker, factory.createNodeArray(), hostFilename, metadata, options);
const updatedDecorators = [
...apiOperationDecoratorsArray,
...apiResponseDecoratorsArray,
...existingDecorators,
factory.createDecorator(factory.createCallExpression(factory.createIdentifier(`${plugin_constants_1.OPENAPI_NAMESPACE}.${decorators_1.ApiResponse.name}`), undefined, [factory.createObjectLiteralExpression(objectLiteralExpr.properties)]))
];
if (!options.readonly) {
return factory.updateMethodDeclaration(compilerNode, [...updatedDecorators, ...modifiers], compilerNode.asteriskToken, compilerNode.name, compilerNode.questionToken, compilerNode.typeParameters, compilerNode.parameters, compilerNode.type, compilerNode.body);
}
else {
return compilerNode;
}
}
createApiOperationDecorator(factory, node, decorators, options, sourceFile, typeChecker, metadata) {
if (!options.introspectComments) {
return [];
}
const apiOperationDecorator = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)([decorators_1.ApiOperation.name], decorators, factory);
let apiOperationExistingProps = undefined;
if (apiOperationDecorator && !options.readonly) {
const apiOperationExpr = (0, lodash_1.head)((0, ast_utils_1.getDecoratorArguments)(apiOperationDecorator));
if (apiOperationExpr) {
apiOperationExistingProps =
apiOperationExpr.properties;
}
}
const extractedComments = (0, ast_utils_1.getMainCommentOfNode)(node);
if (!extractedComments) {
return [];
}
const properties = [
...(apiOperationExistingProps !== null && apiOperationExistingProps !== void 0 ? apiOperationExistingProps : factory.createNodeArray())
];
const tags = (0, ast_utils_1.getTsDocTagsOfNode)(node, typeChecker);
const hasRemarksKey = (0, plugin_utils_1.hasPropertyKey)('description', factory.createNodeArray(apiOperationExistingProps));
if (!hasRemarksKey && tags.remarks) {
const remarksPropertyAssignment = factory.createPropertyAssignment('description', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.remarks));
properties.push(remarksPropertyAssignment);
if (options.controllerKeyOfComment === 'description') {
properties.unshift(factory.createPropertyAssignment('summary', factory.createStringLiteral(extractedComments)));
}
else {
const keyToGenerate = options.controllerKeyOfComment;
properties.unshift(factory.createPropertyAssignment(keyToGenerate, factory.createStringLiteral(extractedComments)));
}
}
else {
const keyToGenerate = options.controllerKeyOfComment;
properties.unshift(factory.createPropertyAssignment(keyToGenerate, factory.createStringLiteral(extractedComments)));
}
const hasDeprecatedKey = (0, plugin_utils_1.hasPropertyKey)('deprecated', factory.createNodeArray(apiOperationExistingProps));
if (!hasDeprecatedKey && tags.deprecated) {
const deprecatedPropertyAssignment = factory.createPropertyAssignment('deprecated', (0, ast_utils_1.createLiteralFromAnyValue)(factory, tags.deprecated));
properties.push(deprecatedPropertyAssignment);
}
const objectLiteralExpr = factory.createObjectLiteralExpression((0, lodash_1.compact)(properties));
const apiOperationDecoratorArguments = factory.createNodeArray([objectLiteralExpr]);
const methodKey = node.name.getText();
if (metadata[methodKey]) {
const existingObjectLiteralExpr = metadata[methodKey];
const existingProperties = existingObjectLiteralExpr.properties;
const updatedProperties = factory.createNodeArray([
...existingProperties,
...(0, lodash_1.compact)(properties)
]);
const updatedObjectLiteralExpr = factory.createObjectLiteralExpression(updatedProperties);
metadata[methodKey] = updatedObjectLiteralExpr;
}
else {
metadata[methodKey] = objectLiteralExpr;
}
if (apiOperationDecorator) {
const expr = apiOperationDecorator.expression;
const updatedCallExpr = factory.updateCallExpression(expr, expr.expression, undefined, apiOperationDecoratorArguments);
return [factory.updateDecorator(apiOperationDecorator, updatedCallExpr)];
}
else {
return [
factory.createDecorator(factory.createCallExpression(factory.createIdentifier(`${plugin_constants_1.OPENAPI_NAMESPACE}.${decorators_1.ApiOperation.name}`), undefined, apiOperationDecoratorArguments))
];
}
}
createApiResponseDecorator(factory, node, options, metadata) {
if (!options.introspectComments) {
return [];
}
const tags = (0, ast_utils_1.getTsDocErrorsOfNode)(node);
if (!tags.length) {
return [];
}
return tags.map((tag) => {
const properties = [];
properties.push(factory.createPropertyAssignment('status', factory.createNumericLiteral(tag.status)));
properties.push(factory.createPropertyAssignment('description', factory.createNumericLiteral(tag.description)));
const objectLiteralExpr = factory.createObjectLiteralExpression((0, lodash_1.compact)(properties));
const methodKey = node.name.getText();
metadata[methodKey] = objectLiteralExpr;
const apiResponseDecoratorArguments = factory.createNodeArray([objectLiteralExpr]);
return factory.createDecorator(factory.createCallExpression(factory.createIdentifier(`${plugin_constants_1.OPENAPI_NAMESPACE}.${decorators_1.ApiResponse.name}`), undefined, apiResponseDecoratorArguments));
});
}
createDecoratorObjectLiteralExpr(factory, node, typeChecker, existingProperties = factory.createNodeArray(), hostFilename, metadata, options) {
let properties = [];
if (!options.readonly && !options.skipAutoHttpCode) {
properties = properties.concat(existingProperties, this.createStatusPropertyAssignment(factory, node, existingProperties));
}
properties = properties.concat([
this.createTypePropertyAssignment(factory, node, typeChecker, existingProperties, hostFilename, options)
]);
const objectLiteralExpr = factory.createObjectLiteralExpression((0, lodash_1.compact)(properties));
const methodKey = node.name.getText();
const existingExprOrUndefined = metadata[methodKey];
if (existingExprOrUndefined) {
const existingProperties = existingExprOrUndefined.properties;
const updatedProperties = factory.createNodeArray([
...existingProperties,
...(0, lodash_1.compact)(properties)
]);
const updatedObjectLiteralExpr = factory.createObjectLiteralExpression(updatedProperties);
metadata[methodKey] = updatedObjectLiteralExpr;
}
else {
metadata[methodKey] = objectLiteralExpr;
}
return objectLiteralExpr;
}
createTypePropertyAssignment(factory, node, typeChecker, existingProperties, hostFilename, options) {
if ((0, plugin_utils_1.hasPropertyKey)('type', existingProperties)) {
return undefined;
}
const signature = typeChecker.getSignatureFromDeclaration(node);
const type = typeChecker.getReturnTypeOfSignature(signature);
if (!type) {
return undefined;
}
const typeReferenceDescriptor = (0, plugin_utils_1.getTypeReferenceAsString)(type, typeChecker);
if (!typeReferenceDescriptor.typeName) {
return undefined;
}
if (typeReferenceDescriptor.typeName.includes('node_modules')) {
return undefined;
}
const identifier = (0, type_reference_to_identifier_util_1.typeReferenceToIdentifier)(typeReferenceDescriptor, hostFilename, options, factory, type, this._typeImports);
return factory.createPropertyAssignment('type', identifier);
}
createStatusPropertyAssignment(factory, node, existingProperties) {
if ((0, plugin_utils_1.hasPropertyKey)('status', existingProperties)) {
return undefined;
}
const statusNode = this.getStatusCodeIdentifier(factory, node);
return factory.createPropertyAssignment('status', statusNode);
}
getStatusCodeIdentifier(factory, node) {
const decorators = ts.canHaveDecorators(node) && ts.getDecorators(node);
const httpCodeDecorator = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)(['HttpCode'], decorators, factory);
if (httpCodeDecorator) {
const argument = (0, lodash_1.head)((0, ast_utils_1.getDecoratorArguments)(httpCodeDecorator));
if (argument) {
return argument;
}
}
const postDecorator = (0, plugin_utils_1.getDecoratorOrUndefinedByNames)(['Post'], decorators, factory);
if (postDecorator) {
return factory.createIdentifier('201');
}
return factory.createIdentifier('200');
}
normalizeImportPath(pathToSource, path) {
let relativePath = path_1.posix.relative((0, plugin_utils_1.convertPath)(pathToSource), (0, plugin_utils_1.convertPath)(path));
relativePath = relativePath[0] !== '.' ? './' + relativePath : relativePath;
return relativePath;
}
}
exports.ControllerClassVisitor = ControllerClassVisitor;