UNPKG

@rxap/ts-morph

Version:

Provides utilities for manipulating TypeScript code using the ts-morph library. It offers a fluent API to add, modify, and remove code elements such as classes, decorators, imports, and properties in both Angular and NestJS projects. This package simplifi

494 lines 23.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CoerceUpstreamBasicOperationImplementation = CoerceUpstreamBasicOperationImplementation; exports.BuildOperationDtoClassName = BuildOperationDtoClassName; exports.CoerceOperationDtoClass = CoerceOperationDtoClass; exports.BuiltDtoDataMapperImplementation = BuiltDtoDataMapperImplementation; exports.BuildDtoReturnImplementation = BuildDtoReturnImplementation; exports.BuildUpstreamGetDataImplementation = BuildUpstreamGetDataImplementation; exports.CoerceUpstreamDefaultOperationImplementation = CoerceUpstreamDefaultOperationImplementation; exports.BuildMethodQueryParameters = BuildMethodQueryParameters; exports.BuildMethodParamParameters = BuildMethodParamParameters; exports.BuildMethodBodyParameters = BuildMethodBodyParameters; exports.CoerceApiQueryDecorators = CoerceApiQueryDecorators; exports.CoerceOperationParamList = CoerceOperationParamList; exports.CoerceNestOperation = CoerceNestOperation; const utilities_1 = require("@rxap/utilities"); const ts_morph_1 = require("ts-morph"); const coerce_class_method_1 = require("../coerce-class-method"); const coerce_decorator_1 = require("../coerce-decorator"); const coerce_imports_1 = require("../coerce-imports"); const coerce_property_declaration_1 = require("../coerce-property-declaration"); const coerce_statements_1 = require("../coerce-statements"); const upstream_options_1 = require("../upstream-options"); const write_type_1 = require("../write-type"); const coerce_dto_class_1 = require("./coerce-dto-class"); const coerce_nest_module_import_1 = require("./coerce-nest-module-import"); const coerce_nest_module_provider_1 = require("./coerce-nest-module-provider"); const find_nest_module_source_file_1 = require("./find-nest-module-source-file"); const get_controller_class_1 = require("./get-controller-class"); const operation_id_utilities_1 = require("./operation-id-utilities"); function CoerceUpstreamBasicOperationImplementation(classDeclaration, moduleSourceFile, upstream) { if ((0, upstream_options_1.IsNormalizedOpenApiUpstreamOptions)(upstream)) { const sourceFile = classDeclaration.getSourceFile(); const commandClassName = (0, operation_id_utilities_1.OperationIdToCommandClassName)(upstream.operationId); (0, coerce_imports_1.CoerceImports)(sourceFile, { namedImports: [commandClassName], moduleSpecifier: (0, operation_id_utilities_1.OperationIdToCommandClassImportPath)(upstream.operationId, upstream.scope, upstream.isService), }); (0, coerce_nest_module_import_1.CoerceNestModuleImport)(moduleSourceFile, { moduleName: 'HttpModule', moduleSpecifier: '@nestjs/axios', }); (0, coerce_nest_module_provider_1.CoerceNestModuleProvider)(moduleSourceFile, { providerObject: commandClassName, moduleSpecifier: (0, operation_id_utilities_1.OperationIdToCommandClassImportPath)(upstream.operationId, upstream.scope, upstream.isService), }); const commandMemberName = (0, utilities_1.camelize)(commandClassName); (0, coerce_imports_1.CoerceImports)(sourceFile, { namedImports: ['Inject'], moduleSpecifier: '@nestjs/common', }); (0, coerce_property_declaration_1.CoercePropertyDeclaration)(classDeclaration, commandMemberName, { type: commandClassName, hasExclamationToken: true, scope: ts_morph_1.Scope.Private, isReadonly: true, decorators: [ { name: 'Inject', arguments: [commandClassName], }, ], }); return { className: commandClassName, memberName: commandMemberName, }; } throw new Error(`Upstream kind '${upstream.kind}' not supported`); } function BuildOperationDtoClassName(controllerName, options) { const { dtoClassNameSuffix, dtoClassName, } = options; return dtoClassName !== null && dtoClassName !== void 0 ? dtoClassName : (dtoClassNameSuffix ? (0, utilities_1.CoerceSuffix)(controllerName, (0, utilities_1.CoercePrefix)(dtoClassNameSuffix, '-')) : controllerName); } function CoerceOperationDtoClass(classDeclaration, controllerName, moduleSourceFile, options) { const sourceFile = classDeclaration.getSourceFile(); const project = sourceFile.getProject(); const { dtoClassNameSuffix, dtoClassName, propertyList = [], isReturnVoid, buildOperationDtoClassName = BuildOperationDtoClassName } = options; let dto = null; if (propertyList.length > 0 || isReturnVoid === false || dtoClassNameSuffix || dtoClassName) { dto = (0, coerce_dto_class_1.CoerceDtoClass)({ project, name: buildOperationDtoClassName(controllerName, options), propertyList, }); } return dto; } function BuiltDtoDataMapperImplementation(classDeclaration, moduleSourceFile, dto, options) { const { propertyList = [], upstream, isArray, } = options; return () => { var _a; if (upstream) { const mapper = {}; for (const property of propertyList) { mapper[property.name] = `data.${(_a = property.source) !== null && _a !== void 0 ? _a : property.name}`; } if (isArray) { return w => { w.write('data.map(item => ('); ts_morph_1.Writers.object(mapper)(w); w.write('))'); }; } else { return ts_morph_1.Writers.object(mapper); } } else { return isArray ? '[]' : '{}'; } }; } function BuildDtoReturnImplementation(classDeclaration, moduleSourceFile, dto, options) { return (operationOptions) => { var _a; const { isArray, isReturnVoid = !dto, builtDtoDataMapperImplementation = BuiltDtoDataMapperImplementation, } = options; if (!isReturnVoid && dto) { const sourceFile = classDeclaration.getSourceFile(); (0, coerce_imports_1.CoerceImports)(sourceFile, { namedImports: [isArray ? 'ToDtoInstanceList' : 'ToDtoInstance'], moduleSpecifier: '@rxap/nest-dto', }); const mapper = builtDtoDataMapperImplementation(classDeclaration, moduleSourceFile, dto, options)(operationOptions); operationOptions.returnType = dto.className + (isArray ? '[]' : ''); (_a = operationOptions.statements) !== null && _a !== void 0 ? _a : (operationOptions.statements = []); operationOptions.statements = (0, utilities_1.coerceArray)(operationOptions.statements); if (isArray) { operationOptions.statements.push('return ToDtoInstanceList('); } else { operationOptions.statements.push('return ToDtoInstance('); } operationOptions.statements.push(dto.className + ',', w => { if (typeof mapper === 'string') { w.write(mapper); } else { mapper(w); } w.write(','); }, ');'); } }; } function BuildUpstreamGetDataImplementation(classDeclaration, moduleSourceFile, dto, options) { return (operationOptions) => { var _a; const { buildUpstreamGetParametersImplementation = (() => () => ''), upstream, } = options; if (upstream) { if ((0, upstream_options_1.IsNormalizedOpenApiUpstreamOptions)(upstream)) { const { memberName: commandMemberName } = CoerceUpstreamBasicOperationImplementation(classDeclaration, moduleSourceFile, upstream); const commandParameter = buildUpstreamGetParametersImplementation(classDeclaration, moduleSourceFile, dto, options)(operationOptions); (_a = operationOptions.statements) !== null && _a !== void 0 ? _a : (operationOptions.statements = []); operationOptions.statements = (0, utilities_1.coerceArray)(operationOptions.statements); operationOptions.statements.push(w => { w.write(`const data = await this.${commandMemberName}.execute(`); if (typeof commandParameter === 'function') { commandParameter(w); } else { w.write(commandParameter); } w.write(');'); }); } } }; } function CoerceUpstreamDefaultOperationImplementation(classDeclaration, moduleSourceFile, dto, options) { return (operationOptions) => { var _a; const { upstream, buildDtoReturnImplementation = BuildDtoReturnImplementation, buildUpstreamGetDataImplementation = BuildUpstreamGetDataImplementation, buildGetDataImplementation = utilities_1.noop, } = options; (_a = buildGetDataImplementation(classDeclaration, moduleSourceFile, dto, options)) === null || _a === void 0 ? void 0 : _a(operationOptions); if (upstream) { operationOptions.isAsync = true; buildUpstreamGetDataImplementation(classDeclaration, moduleSourceFile, dto, options)(operationOptions); } buildDtoReturnImplementation(classDeclaration, moduleSourceFile, dto, options)(operationOptions); }; } function BuildMethodQueryParameters(queryList, sourceFile) { if (queryList.length) { return queryList.map(query => { var _a, _b; if (query.defaultValue === undefined) { return query; } (0, coerce_imports_1.CoerceImports)(sourceFile, { namedImports: ['DefaultValuePipe'], moduleSpecifier: '@nestjs/common', }); const pipeList = (_b = (_a = query.pipeList) === null || _a === void 0 ? void 0 : _a.slice()) !== null && _b !== void 0 ? _b : []; pipeList.push(w => { w.write('new DefaultValuePipe('); if (typeof query.defaultValue === 'string') { w.write(query.defaultValue); } else if (typeof query.defaultValue === 'function') { query.defaultValue(w); } else { throw new Error('Invalid default value. Must be a string or a function'); } w.write(')'); }); return Object.assign(Object.assign({}, query), { pipeList }); }).map(({ alias, name, type, hasQuestionToken, required, defaultValue, pipeList = [], isArray }) => ({ name: alias !== null && alias !== void 0 ? alias : name, type: (0, write_type_1.WriteType)({ type: type !== null && type !== void 0 ? type : 'unknown', isArray: isArray }, sourceFile), hasQuestionToken: hasQuestionToken !== null && hasQuestionToken !== void 0 ? hasQuestionToken : (!required && defaultValue === undefined), decorators: [ { name: 'Query', arguments: [ w => w.quote(name), ...pipeList.map(pipe => { if (typeof pipe === 'string') { return pipe; } if (typeof pipe === 'function') { return pipe; } return (0, write_type_1.WriteType)(pipe, sourceFile); }), ], }, ], })); } return []; } function BuildMethodParamParameters(paramList, sourceFile) { if (paramList.length) { return paramList.map(({ alias, name, type }) => ({ name: alias !== null && alias !== void 0 ? alias : name, type: type ? (0, write_type_1.WriteType)({ type: type, isArray: false }, sourceFile) : undefined, decorators: [ { name: 'Param', arguments: [w => w.quote(name)], }, ], })); } return []; } function BuildMethodBodyParameters(body) { if (body) { return [ { name: 'body', type: body, decorators: [ { name: 'Body', arguments: [], }, ], }, ]; } return []; } function CoerceApiQueryDecorators(queryList, methodDeclaration) { for (const query of queryList) { (0, coerce_decorator_1.CoerceDecorator)(methodDeclaration, 'ApiQuery', { arguments: [ ts_morph_1.Writers.object({ name: w => w.quote(query.name), required: query.required ? 'true' : 'false', isArray: query.isArray ? 'true' : 'false', }), ], }, () => decorator => { var _a; if (decorator.getArguments().length) { const [objectLiteralExpression] = decorator.getArguments(); if (objectLiteralExpression.isKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression)) { const namePropertyElement = objectLiteralExpression.getProperty('name'); if (namePropertyElement === null || namePropertyElement === void 0 ? void 0 : namePropertyElement.isKind(ts_morph_1.SyntaxKind.PropertyAssignment)) { const name = (_a = namePropertyElement.getInitializer()) === null || _a === void 0 ? void 0 : _a.getText().trim().replace(/^'/, '').replace(/'$/, ''); return name === query.name; } } } return false; }); } } function CoerceOperationParamList(paramList, classDeclaration) { var _a, _b; const currentControllerPath = (_b = (_a = classDeclaration.getDecoratorOrThrow('Controller').getArguments()[0]) === null || _a === void 0 ? void 0 : _a.getText()) !== null && _b !== void 0 ? _b : ''; const fragments = currentControllerPath.split('/'); const parameters = fragments.filter(fragment => fragment.startsWith(':')); for (const parameter of parameters) { if (!paramList.some(param => param.name === parameter.substr(1))) { paramList.push({ name: parameter.substr(1) }); } } } function CoerceNestOperation(sourceFile, options, moduleSourceFile) { var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k; const project = sourceFile.getProject(); if (!moduleSourceFile) { moduleSourceFile = (0, find_nest_module_source_file_1.FindNestModuleSourceFile)(project, sourceFile.getDirectoryPath()); if (!moduleSourceFile) { throw new Error(`Could not find Nest module in '${sourceFile.getDirectoryPath()}'`); } } const { tsMorphTransform = () => void 0, coerceUpstreamOperationImplementation = CoerceUpstreamDefaultOperationImplementation, coerceOperationDtoClass = CoerceOperationDtoClass, overwrite, controllerPath, } = options; const moduleName = (0, utilities_1.dasherize)(moduleSourceFile.getClasses()[0].getName().replace(/Module$/, '')); const classDeclaration = (0, get_controller_class_1.GetControllerClass)(sourceFile); const controllerName = (0, utilities_1.dasherize)(classDeclaration.getName().replace(/Controller$/, '')); const dto = coerceOperationDtoClass(classDeclaration, controllerName, moduleSourceFile, options); if (dto) { (0, coerce_imports_1.CoerceImports)(sourceFile, { namedImports: [dto.className], moduleSpecifier: dto.filePath, }); } const operationOptions = { paramList: (_a = options.paramList) !== null && _a !== void 0 ? _a : [], queryList: (_b = options.queryList) !== null && _b !== void 0 ? _b : [], operationName: options.operationName, path: (_c = options.path) !== null && _c !== void 0 ? _c : null, returnType: (_d = options.returnType) !== null && _d !== void 0 ? _d : 'void', isAsync: (_e = options.isAsync) !== null && _e !== void 0 ? _e : true, method: (_f = options.method) !== null && _f !== void 0 ? _f : 'get', body: (_g = options.body) !== null && _g !== void 0 ? _g : null, statements: (_h = options.statements) !== null && _h !== void 0 ? _h : null, decorators: (_j = options.decorators) !== null && _j !== void 0 ? _j : [], }; coerceUpstreamOperationImplementation(classDeclaration, moduleSourceFile, dto, options)(operationOptions); Object.assign(operationOptions, (_k = tsMorphTransform(project, sourceFile, classDeclaration, controllerName, moduleSourceFile, dto, options)) !== null && _k !== void 0 ? _k : {}); const { paramList, queryList, operationName, returnType, isAsync, body, decorators, } = operationOptions; let { path, statements, method, } = operationOptions; if (controllerPath) { console.log(`Overwrite controller path with '${controllerPath}' for operation '${operationName}' in controller '${controllerName}' in module '${moduleName}'`.yellow); classDeclaration.getDecoratorOrThrow('Controller').set({ arguments: [w => w.quote(controllerPath)], }); } else if (moduleName !== controllerName) { const parentParamList = paramList.filter(p => p.fromParent); classDeclaration.getDecoratorOrThrow('Controller').set({ arguments: [ w => w.quote(`${moduleName}/${parentParamList.length ? parentParamList.map(param => `:${param.name}`).join('/') + '/' : ''}${controllerName.replace(moduleName + '-', '')}`), ], }); } CoerceOperationParamList(paramList, classDeclaration); method = (0, utilities_1.capitalize)(method.toLowerCase()); queryList.forEach(query => { var _a; query.name = (0, utilities_1.camelize)(query.name); if (query.alias) { query.alias = (0, utilities_1.camelize)(query.alias); } (_a = query.type) !== null && _a !== void 0 ? _a : (query.type = 'string'); }); paramList.forEach(param => { var _a; param.name = (0, utilities_1.camelize)(param.name); if (param.alias) { param.alias = (0, utilities_1.camelize)(param.alias); } (_a = param.type) !== null && _a !== void 0 ? _a : (param.type = 'string'); }); const importStructures = [ { namedImports: [method], moduleSpecifier: '@nestjs/common', }, ]; if (!statements) { statements = ['throw new NotImplementedException();']; importStructures.push({ namedImports: ['NotImplementedException'], moduleSpecifier: '@nestjs/common', }); } if (queryList.length) { importStructures.push({ namedImports: ['Query'], moduleSpecifier: '@nestjs/common', }); importStructures.push({ namedImports: ['ApiQuery'], moduleSpecifier: '@nestjs/swagger', }); } if (paramList.length) { importStructures.push({ namedImports: ['Param'], moduleSpecifier: '@nestjs/common', }); } if (body) { importStructures.push({ namedImports: ['Body'], moduleSpecifier: '@nestjs/common', }); } if (!path) { if (paramList.filter(param => !param.fromParent).length) { path = paramList.filter(param => !param.fromParent).map(param => `:${param.name}`).join('/'); } } else { const match = path.match(/:(\w+)/g); for (const param of match !== null && match !== void 0 ? match : []) { if (!paramList.find(p => p.name === param.substr(1))) { paramList.push({ name: param.substr(1), type: 'string', }); } } } // TODO : check that all defined param in the paramList are present in the path const methodDeclaration = (0, coerce_class_method_1.CoerceClassMethod)(classDeclaration, (0, utilities_1.camelize)(operationName), { scope: ts_morph_1.Scope.Public, isAsync, }); // region coerce parameters const existingParameters = methodDeclaration.getParameters().map(p => p.getStructure()); (0, utilities_1.CoerceArrayItems)(existingParameters, [ ...BuildMethodQueryParameters(queryList, sourceFile), ...BuildMethodParamParameters(paramList, sourceFile), ...BuildMethodBodyParameters(body), ], (a, b) => a.name === b.name); methodDeclaration.getParameters().forEach(p => p.remove()); function sortByName(a, b) { if (a.name < b.name) { return -1; } if (a.name > b.name) { return 1; } return 0; } function sortByOptional(a, b) { if (a.hasQuestionToken && !b.hasQuestionToken) { return 1; } if (!a.hasQuestionToken && b.hasQuestionToken) { return -1; } return 0; } for (const parameter of existingParameters.sort(sortByName).sort(sortByOptional)) { methodDeclaration.addParameter(parameter); } // endregion if (returnType) { if (isAsync) { methodDeclaration.setReturnType(w => { w.write('Promise<'); if (typeof returnType === 'string') { w.write(returnType); } else if (typeof returnType === 'function') { returnType(w); } else { throw new Error('Invalid return type. Must be a string or a function'); } w.write('>'); }); } else { methodDeclaration.setReturnType(returnType); } } decorators.forEach(decorator => { (0, coerce_decorator_1.CoerceDecorator)(methodDeclaration, decorator.name, decorator); }); const methodDecorator = (0, coerce_decorator_1.CoerceDecorator)(methodDeclaration, method, { arguments: path ? [w => w.quote(path)] : [] }); if (overwrite) { methodDecorator.set({ arguments: path ? [w => w.quote(path)] : [] }); } CoerceApiQueryDecorators(queryList, methodDeclaration); (0, coerce_statements_1.CoerceStatements)(methodDeclaration, statements, Array.isArray(overwrite) ? overwrite.includes('statements') : overwrite !== null && overwrite !== void 0 ? overwrite : false); (0, coerce_imports_1.CoerceImports)(sourceFile, importStructures); return methodDeclaration; } //# sourceMappingURL=coerce-nest-operation.js.map