@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
JavaScript
;
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