diff --git a/src/Compiler/Transformer/Feature/FeatureTsTransformer.ts b/src/Compiler/Transformer/Feature/FeatureTsTransformer.ts index 51826bd..a84999d 100644 --- a/src/Compiler/Transformer/Feature/FeatureTsTransformer.ts +++ b/src/Compiler/Transformer/Feature/FeatureTsTransformer.ts @@ -11,7 +11,7 @@ import { FeatureModuleDiscoverer, type FeatureSourcePath } from "../../../Util/F import { TsImportHelper } from "../TsImportHelper"; import { ModuleClassTsTransformer } from "../ModuleClassTsTransformer"; import type { FeatureTransformContext } from "./FeatureTransformContext"; -//import { HObjectTsTransformer } from "./HObject/HObjectTsTransformer"; +import { HObjectTsTransformer } from "./HObject/HObjectTsTransformer"; /** @@ -36,7 +36,7 @@ export class FeatureTsTransformer { this.transformers = [ new FeatureInfraDomainModuleTsTransformer(helpers), new FeatureModuleTsTransformer(helpers), - //new HObjectTsTransformer(helpers), + new HObjectTsTransformer(helpers), ]; } diff --git a/src/Compiler/Transformer/Feature/HObject/HObjectConstructorTsFactory.ts b/src/Compiler/Transformer/Feature/HObject/HObjectConstructorTsFactory.ts new file mode 100644 index 0000000..012cbbb --- /dev/null +++ b/src/Compiler/Transformer/Feature/HObject/HObjectConstructorTsFactory.ts @@ -0,0 +1,37 @@ +import ts from "typescript"; +import type { HObjectPropertyTsMeta } from "./HObjectPropertyTsMeta"; + +export class HObjectToConstructorTsFactory { + public create(properties: HObjectPropertyTsMeta[]): ts.ConstructorDeclaration { + const propertiesSorted = [...properties.filter(p => !p.optional), ...properties.filter(p => p.optional)]; + const constructorParameters = propertiesSorted.map(p => this.createParameter(p)); + const constructorAssignments = propertiesSorted.map(p => this.createAssignment(p)); + + return ts.factory.createConstructorDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.PublicKeyword)], + constructorParameters, + ts.factory.createBlock(constructorAssignments, true) + ); + } + + private createParameter(p: HObjectPropertyTsMeta): ts.ParameterDeclaration { + return ts.factory.createParameterDeclaration( + undefined, + undefined, + ts.factory.createIdentifier(p.name), + p.optional ? ts.factory.createToken(ts.SyntaxKind.QuestionToken) : undefined, + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword), + undefined + ); + } + + private createAssignment(p: HObjectPropertyTsMeta): ts.Statement { + return ts.factory.createExpressionStatement( + ts.factory.createBinaryExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createThis(), p.name), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + ts.factory.createIdentifier(p.name) + ) + ); + } +} \ No newline at end of file diff --git a/src/Compiler/Transformer/Feature/HObject/HObjectParseTsFactory.ts b/src/Compiler/Transformer/Feature/HObject/HObjectParseTsFactory.ts new file mode 100644 index 0000000..317a1a7 --- /dev/null +++ b/src/Compiler/Transformer/Feature/HObject/HObjectParseTsFactory.ts @@ -0,0 +1,112 @@ + +import ts from "typescript"; +import type { HObjectPropertyTsMeta } from "./HObjectPropertyTsMeta"; + +import type { ImportDeclarationWrapper } from "../../Helper/ImportDeclarationWrapper"; +import { TsTransfromerHelper } from "../../TsTransformerHelper"; +import { HObjectPropertyParseTsFactory } from "./HObjectPropertyParseTsFactory"; + +export class HObjectParseTsFactory { + + private propertyTsFactory: HObjectPropertyParseTsFactory; + + public constructor() { + this.propertyTsFactory = new HObjectPropertyParseTsFactory(); + } + + public create(hObjectClassName: string, properties: HObjectPropertyTsMeta[], hCommonImportDecl: ImportDeclarationWrapper): ts.MethodDeclaration { + const returnType = ts.factory.createTypeReferenceNode( + hCommonImportDecl.getEntityName('R'), [ts.factory.createTypeReferenceNode(hObjectClassName, [])] + ); + + const parameters = [ + ts.factory.createParameterDeclaration(undefined, undefined, 'plain', undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)) + ]; + + return ts.factory.createMethodDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.PublicKeyword), ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)], + undefined, // * + 'parse', + undefined, // '?' + undefined, // generic + parameters, + returnType, + ts.factory.createBlock(this.createBody(hObjectClassName, properties), true) + ); + } + + private createBody(hObjectClassName: string, properties: HObjectPropertyTsMeta[]): ts.Statement[] { + + const statements: ts.Statement[] = [ + this.createCheckIsObject(hObjectClassName), + this.createPlainObjVarDeclaration(hObjectClassName), + this.createIssuesVarDeclaration(), + ]; + + for (const p of properties) { + statements.push(...this.propertyTsFactory.create(p)); + } + + return statements; + } + + /** + ` + if (typeof plain !== 'object') { + return PlainParseHelper.HObjectIsNotObjectParseErr(hObjectClassName as any, plain]); + } + ` + */ + private createCheckIsObject(hObjectClassName: string): ts.Statement { + // `typeof plain !== 'object'` + const condtionExpression = ts.factory.createBinaryExpression( + ts.factory.createTypeOfExpression(ts.factory.createIdentifier('plain')), + ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), + ts.factory.createStringLiteral('object') + ); + + const block = ts.factory.createBlock([ + ts.factory.createReturnStatement( + ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier('PlainParseHelper'), + 'HObjectIsNotObjectParseErr' + ), + undefined, + [ + ts.factory.createAsExpression( + ts.factory.createIdentifier(hObjectClassName), + ts.factory.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword) + ), + ts.factory.createIdentifier('plain') + ] + ) + ) + ], true); + + return ts.factory.createIfStatement(condtionExpression, block); + } + + // `const p = plain as Record;` + private createPlainObjVarDeclaration(hObjectClassName: string): ts.Statement { + const initializer = ts.factory.createAsExpression( + ts.factory.createIdentifier('plain'), + ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier('Record'), + [ + ts.factory.createTypeOperatorNode(ts.SyntaxKind.KeyOfKeyword, ts.factory.createTypeReferenceNode(hObjectClassName, undefined)), + ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword), + ] + ) + ); + return TsTransfromerHelper.createConstStatement("p", undefined, initializer); + } + + // `const issues: PlainParseIssue[] = [];` + private createIssuesVarDeclaration() { + const type = ts.factory.createArrayTypeNode(ts.factory.createTypeReferenceNode('PlainParseIssue', undefined)); + const initializer = ts.factory.createArrayLiteralExpression([], false); + return TsTransfromerHelper.createConstStatement("issues", type, initializer); + + } +} \ No newline at end of file diff --git a/src/Compiler/Transformer/Feature/HObject/HObjectPropertyParseTsFactory.ts b/src/Compiler/Transformer/Feature/HObject/HObjectPropertyParseTsFactory.ts new file mode 100644 index 0000000..21037e2 --- /dev/null +++ b/src/Compiler/Transformer/Feature/HObject/HObjectPropertyParseTsFactory.ts @@ -0,0 +1,122 @@ +import ts from "typescript"; +import { CollectionType, HObjectPropertyPrimitiveType, HObjectPropertyTsMeta } from "./HObjectPropertyTsMeta"; +import { TsTransfromerHelper } from "../../TsTransformerHelper"; + +export class HObjectPropertyParseTsFactory { + public create(meta: HObjectPropertyTsMeta): ts.Statement[] { + if (meta.isPrimitive()) { + switch (meta.tsType) { + case HObjectPropertyPrimitiveType.string: return this.createStringParse(meta); + } + } else { + // TODO HObject parse create + return []; + } + + return []; + //throw new LogicError('Unspported property meta in parse(), meta: ' + meta); + + } + + private createStringParse(meta: HObjectPropertyTsMeta): ts.Statement[] { + /* + let initializer; + if (meta.validationRules) { + if (meta.validationRules[0].isStringLengthRule()) { + switch (meta.validationRules[0].extraRuleParts.join('.')) { + case "length": + initializer = this.createPrimitivePropertyParseCode(meta, "parseStringLength", { + scalar: meta.validationRules[0].args[0], + }); + break; + case "length.min": + initializer = + break; + case "length.max": + initializer = + break; + } + } + } else { + initializer = this.createPrimitivePropertyParseCode(meta, "parseString"); + }* */ + + const initializer = this.createPrimitivePropertyParseCode(meta, "parseString"); + return meta.optional + ? this.createOptionalPropertyParse(meta, initializer) + : [TsTransfromerHelper.createConstStatement(meta.name, undefined, initializer)]; + } + + private createPrimitivePropertyParseCode(meta: HObjectPropertyTsMeta, helperMethod: string): ts.Expression { + if (meta.collectionType === CollectionType.Array) { + return this.createParsePrimitiveArray(meta, helperMethod); + } else { + return this.createPlainParseHelperCall(meta, helperMethod); + } + } + + private createOptionalPropertyParse(meta: HObjectPropertyTsMeta, initializer: ts.Expression): ts.Statement[] { + const letPropertyVar = TsTransfromerHelper.createLetStatement(meta.name); + // `p.propName === undefined` + const ifCondition = ts.factory.createBinaryExpression( + this.createPlainObjPropertyAccess(meta), + ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsEqualsToken), + ts.factory.createIdentifier('undefined') + ); + + const optionalPropertyIf = ts.factory.createIfStatement( + ifCondition, + ts.factory.createBlock([this.createAssignInitializerToLetVar(meta, initializer)], true) + ); + return [letPropertyVar, optionalPropertyIf]; + } + + private createAssignInitializerToLetVar(meta: HObjectPropertyTsMeta, initializer: ts.Expression): ts.Statement { + return ts.factory.createExpressionStatement( + ts.factory.createBinaryExpression( + ts.factory.createIdentifier(meta.name), + ts.factory.createToken(ts.SyntaxKind.EqualsToken), + initializer, + ) + ); + } + + private createParsePrimitiveArray(meta: HObjectPropertyTsMeta, parse: ts.Expression | string) { + parse = typeof parse === 'string' ? this.createPlainParseHelperPropertyAccess(parse) : parse; + return this.createPlainParseHelperCall(meta, "parsePrimitiveArray", [parse]); + } + + private createPlainParseHelperCall(meta: HObjectPropertyTsMeta, method: string, args: ts.Expression[] = []) { + return ts.factory.createCallExpression( + this.createPlainParseHelperPropertyAccess(method), + undefined, + [ + this.createPlainObjPropertyAccess(meta), + ...args, + ts.factory.createStringLiteral(meta.name), + ts.factory.createIdentifier('issues') + ] + ); + } + + private createPlainParseHelperPropertyAccess(name: string): ts.PropertyAccessExpression { + return ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier("PlainParseHelper"), + ts.factory.createIdentifier(name) + ); + } + + private createPlainObjPropertyAccess(meta: HObjectPropertyTsMeta): ts.PropertyAccessExpression { + return ts.factory.createPropertyAccessExpression( + this.createPlainObjIdentifier(), + ts.factory.createIdentifier(meta.name) + ); + } + + private createPlainObjIdentifier(): ts.Identifier { + return ts.factory.createIdentifier("p"); + } + + + +} \ No newline at end of file diff --git a/src/Compiler/Transformer/Feature/HObject/HObjectToJSONTsFactory.ts b/src/Compiler/Transformer/Feature/HObject/HObjectToJSONTsFactory.ts new file mode 100644 index 0000000..344e240 --- /dev/null +++ b/src/Compiler/Transformer/Feature/HObject/HObjectToJSONTsFactory.ts @@ -0,0 +1,96 @@ +import ts from "typescript"; +import { CollectionType, HObjectPropertyTsMeta, HObjectPropertyPrimitiveType, type PrimitiveHObjectPropertyTsMeta } from "./HObjectPropertyTsMeta"; +import type { ImportDeclarationWrapper } from "../../Helper/ImportDeclarationWrapper"; + +export class HObjectToJSONTsFactory { + + public create(hObjectClassName: string, properties: HObjectPropertyTsMeta[], hCommonImportDecl: ImportDeclarationWrapper): ts.MethodDeclaration { + const returnType = ts.factory.createTypeReferenceNode( + hCommonImportDecl.getEntityName('JsonObjectType'), [ts.factory.createTypeReferenceNode(hObjectClassName, [])] + ); + + return ts.factory.createMethodDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.PublicKeyword), ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)], + undefined, // * + 'toJSON', + undefined, // '?' + undefined, // generic + [], + returnType, + ts.factory.createBlock(this.createBody(properties), true) + ); + } + + private createBody(properties: HObjectPropertyTsMeta[]): ts.Statement[] { + const props: ts.ObjectLiteralElementLike[] = []; + for (const p of properties) { + const initializer = p.isPrimitive() ? this.createPrimitivePropInitializer(p) : this.createHObjectPropInitializer(p); + props.push(ts.factory.createPropertyAssignment(p.name, initializer)); + } + + return [ + ts.factory.createReturnStatement(ts.factory.createObjectLiteralExpression(props, true)) + ]; + } + + private createPrimitivePropInitializer(p: PrimitiveHObjectPropertyTsMeta): ts.Expression { + if (p.tsType === HObjectPropertyPrimitiveType.bigint) { + if (p.collectionType === CollectionType.Array) { + return this.createMethodOfPropCall( + ts.factory.createThis(), p.name, 'map', + this.createArrayMapCallArgs(this.createMethodCall('item', 'toString', []),), + p.optional + ); + } + return this.createMethodOfPropCall(ts.factory.createThis(), p.name, 'toString', [], p.optional); + } + + return ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier(p.name)); + } + + private createHObjectPropInitializer(p: HObjectPropertyTsMeta): ts.Expression { + if (p.collectionType === CollectionType.Array) { + return this.createMethodOfPropCall( + ts.factory.createThis(), p.name, 'map', + this.createArrayMapCallArgs(this.createMethodCall('item', 'toJSON', []),), + p.optional + ); + } + + return ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier(p.name)); + } + + private createArrayMapCallArgs(body: ts.ConciseBody, itemVarName = 'item') { + return [ + ts.factory.createArrowFunction( + undefined, + undefined, + [ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier(itemVarName))], + undefined, + ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), + body + ) + ]; + } + + private createMethodOfPropCall(objExp: ts.Expression, prop: string, method: string, args: ts.Expression[], optional: boolean): ts.CallExpression { + return ts.factory.createCallChain( + ts.factory.createPropertyAccessExpression( + ts.factory.createPropertyAccessExpression(objExp, ts.factory.createIdentifier(prop)), + ts.factory.createIdentifier(method) + ), + optional ? ts.factory.createToken(ts.SyntaxKind.QuestionDotToken) : undefined, + undefined, + args + ); + } + + private createMethodCall(varName: string, method: string, args: ts.Expression[]): ts.CallExpression { + return ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(varName), method), + undefined, + args, + ); + } + +} \ No newline at end of file diff --git a/src/Compiler/Transformer/Feature/HObject/HObjectTsTransformer.ts b/src/Compiler/Transformer/Feature/HObject/HObjectTsTransformer.ts new file mode 100644 index 0000000..00492de --- /dev/null +++ b/src/Compiler/Transformer/Feature/HObject/HObjectTsTransformer.ts @@ -0,0 +1,172 @@ +import { HObjectPropertyExtractor } from './HObjectPropertyExtractor'; +import ts from "typescript"; +import type { FeatureMeta } from "../../../../Util/Feature/Meta/FeatureMeta"; +import { AbstractFeatureTsTransformer, type FeatureTsTransformerHelpers } from "../AbstractFeatureTsTransformer"; +import type { FeatureTransformContext } from "../FeatureTransformContext"; +import { HObjectParseTsFactory } from "./HObjectParseTsFactory"; +import { HObjectToJSONTsFactory } from "./HObjectToJSONTsFactory"; +import type { FeatureSourcePath } from "../../../../Util/Feature/FeatureModuleDiscoverer"; +import { HObjectToConstructorTsFactory } from "./HObjectConstructorTsFactory"; +import { ImportDeclarationWrapper } from "../../Helper/ImportDeclarationWrapper"; +import { HObjectKind } from "@/Util/Feature/Meta"; + +interface VisitContext extends FeatureTransformContext { + source: ts.SourceFile; + importNameToDeclarationMap: Map; + hCommonImportDecl?: ImportDeclarationWrapper; +} + +const HObjectHCommonImports = [ + 'R', + 'OK', + 'type JsonObjectType', + 'type PlainParsableHObjectType', + 'type PlainParseError', + 'PlainParseHelper', + 'InvalidTypePlainParseIssue', + 'HObjectTypeMeta', + 'PlainParseIssue', + 'TooBigPlainParseIssue', + 'TooSmallPlainParseIssue' +]; + +export class HObjectTsTransformer extends AbstractFeatureTsTransformer { + + private propertyExtractor: HObjectPropertyExtractor; + private constructorTsFactory: HObjectToConstructorTsFactory; + private parseTsFactory: HObjectParseTsFactory; + private toJSONTsFactory: HObjectToJSONTsFactory; + + public constructor(helpers: FeatureTsTransformerHelpers) { + super(helpers); + + this.propertyExtractor = new HObjectPropertyExtractor(); + this.constructorTsFactory = new HObjectToConstructorTsFactory(), + this.parseTsFactory = new HObjectParseTsFactory(); + this.toJSONTsFactory = new HObjectToJSONTsFactory(); + } + + public supports(featureSourcePath: FeatureSourcePath, feature: FeatureMeta): boolean { + const meta = feature.hObjectMap.get(featureSourcePath.localSourcePath); + if (meta && meta.kind !== HObjectKind.AggregateRoot && meta.kind !== HObjectKind.Entity) { + return true; + } + + return false; + } + + public transform(source: ts.SourceFile, context: FeatureTransformContext): ts.SourceFile { + const visitContext: VisitContext = { + ...context, + importNameToDeclarationMap: new Map(), + source, + }; + + const transformed = ts.factory.updateSourceFile(source, ts.visitNodes( + source.statements, + this.visitSourceStatment.bind(this, visitContext), + (node): node is ts.Statement => ts.isStatement(node)) + ); + + return transformed; + } + + private visitSourceStatment(visitContext: VisitContext, node: ts.Statement) { + if (ts.isImportDeclaration(node)) { + return this.visitImportDeclaration(visitContext, node); + } + + if (ts.isClassDeclaration(node)) { + if (!visitContext.hCommonImportDecl) { + visitContext.hCommonImportDecl = ImportDeclarationWrapper.create(HObjectHCommonImports, '@hexancore/common', visitContext.tsContext); + return [ + visitContext.hCommonImportDecl.declaration, + this.updateClassDeclaration(node, visitContext as any) + ]; + } + + return this.updateClassDeclaration(node, visitContext as any); + } + + return node; + } + + private visitImportDeclaration(visitContext: VisitContext, node: ts.ImportDeclaration) { + const wrapper = ImportDeclarationWrapper.from(node, visitContext.tsContext); + + if (this.isHexancoreCommonImport(visitContext, node)) { + visitContext.hCommonImportDecl = wrapper; + return this.extendHexancoreCommonImportDecl(visitContext.hCommonImportDecl); + } + + wrapper.importNames.forEach(name => { + visitContext.importNameToDeclarationMap.set(name, wrapper); + }); + + return wrapper.declaration; + } + + private isHexancoreCommonImport(visitContext: VisitContext, node: ts.ImportDeclaration): boolean { + const moduleSpecifier = node.moduleSpecifier; + return !visitContext.hCommonImportDecl && ts.isStringLiteral(moduleSpecifier) && moduleSpecifier.text === '@hexancore/common'; + } + + private extendHexancoreCommonImportDecl(decl: ImportDeclarationWrapper): ts.ImportDeclaration { + const newImports = HObjectHCommonImports.filter(i => !decl.hasNamedAccess(i)); + return decl.addNamedImports(newImports); + } + + private updateClassDeclaration(node: ts.ClassDeclaration, visitContext: Required) { + const properties = this.propertyExtractor.extract(node, visitContext.source, visitContext.diagnostics); + const hObjectClassName = node.name!.text; + + const newMembers = [ + this.createHObjectMetaClassProperty(hObjectClassName, visitContext), + ...node.members, + ]; + + if (properties.length > 0) { + newMembers.push(this.constructorTsFactory.create(properties)); + newMembers.push(this.parseTsFactory.create(hObjectClassName, properties, visitContext.hCommonImportDecl)); + newMembers.push(this.toJSONTsFactory.create(hObjectClassName, properties, visitContext.hCommonImportDecl)); + } + + return ts.factory.updateClassDeclaration( + node, + node.modifiers, + node.name, + node.typeParameters, + node.heritageClauses, + ts.factory.createNodeArray(newMembers) + ); + } + + private createHObjectMetaClassProperty(hObjClassName: string, visitContext: VisitContext) { + const featureHObjMeta = visitContext.feature.hObjectMap.get(visitContext.featureSourcePath.localSourcePath)!; + + const hObjTypeMetaCreateParameters = [ + ts.factory.createStringLiteral(visitContext.feature.name), + ts.factory.createStringLiteral(featureHObjMeta.context), + ts.factory.createStringLiteral(featureHObjMeta.kind + ''), + ts.factory.createStringLiteral(featureHObjMeta.name), + ts.factory.createIdentifier(hObjClassName) + ]; + + const hobjectTypeMetaCreateExp = ts.factory.createCallExpression( + ts.factory.createPropertyAccessExpression( + ts.factory.createIdentifier("HObjectTypeMeta"), + ts.factory.createIdentifier(featureHObjMeta.layer) + ), + undefined, + hObjTypeMetaCreateParameters + ); + + return ts.factory.createPropertyDeclaration( + [ts.factory.createModifier(ts.SyntaxKind.PublicKeyword), ts.factory.createModifier(ts.SyntaxKind.StaticKeyword)], + "HOBJ_META", + undefined, + undefined, + hobjectTypeMetaCreateExp + ); + } +} \ No newline at end of file diff --git a/test/integration/Compiler/Transformer/HObject/HObjectTsTransformer.test.ts b/test/integration/Compiler/Transformer/HObject/HObjectTsTransformer.test.ts new file mode 100644 index 0000000..fab5898 --- /dev/null +++ b/test/integration/Compiler/Transformer/HObject/HObjectTsTransformer.test.ts @@ -0,0 +1,59 @@ +/** + * @group integration + */ + +import { TsTransformerTestHelper } from "@/Compiler/Test/TsTransformerTestHelper"; +import { FeatureTsTransformer } from "@/Compiler/Transformer/Feature/FeatureTsTransformer"; +import { HObjectTsTransformer } from "@/Compiler/Transformer/Feature/HObject/HObjectTsTransformer"; +import { LIBS_DIRNAME } from "@test/libs"; +import type { TransformerFactory, SourceFile } from "typescript"; + +describe(HObjectTsTransformer.constructor.name, () => { + const helper = TsTransformerTestHelper.createFromTsConfig(process.cwd() + "/tsconfig.json"); + const sourceRoot = LIBS_DIRNAME + "/test-lib/src"; + let transformer: FeatureTsTransformer; + + let transformerFactory: TransformerFactory; + + beforeAll(() => { + + const compilerRoot = process.cwd() + "/lib/Compiler"; + transformer = FeatureTsTransformer.create(sourceRoot, compilerRoot); + transformerFactory = helper.createTransformerFactory(transformer.transform.bind(transformer)); + }); + + test("transform: Command", () => { + const sourceFilePath = `${sourceRoot}/Book/Application/Book/Command/Create/BookCreateCommand.ts`; + + const out = helper.transformExistingAndReturnAsString(sourceFilePath, [transformerFactory]); + + expect(out).toMatchSnapshot(); + }); + + test("transform: Query", () => { + const sourceFilePath = `${sourceRoot}/Book/Application/Book/Query/GetById/BookGetByIdQuery.ts`; + + const out = helper.transformExistingAndReturnAsString(sourceFilePath, [transformerFactory]); + + expect(out).toMatchSnapshot(); + }); + + test("transform: DTO", () => { + const sourceFilePath = `${sourceRoot}/Book/Application/Book/Dto/TestTransformDto.ts`; + + const out = helper.transformExistingAndReturnAsString(sourceFilePath, [transformerFactory]); + + expect(out).toMatchSnapshot(); + }); + + test("transform: ValueObject", () => { + const sourceFilePath = `${sourceRoot}/Book/Domain/Book/Shared/ValueObject/BookId.ts`; + + const out = helper.transformExistingAndReturnAsString(sourceFilePath, [transformerFactory]); + + expect(out).toMatchSnapshot(); + }); + +}); + + diff --git a/test/integration/Compiler/Transformer/HObject/__snapshots__/HObjectTsTransformer.test.ts.snap b/test/integration/Compiler/Transformer/HObject/__snapshots__/HObjectTsTransformer.test.ts.snap new file mode 100644 index 0000000..062351b --- /dev/null +++ b/test/integration/Compiler/Transformer/HObject/__snapshots__/HObjectTsTransformer.test.ts.snap @@ -0,0 +1,123 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Function transform: Command 1`] = ` +"import { HCommand, R, OK, type JsonObjectType, type PlainParsableHObjectType, type PlainParseError, PlainParseHelper, InvalidTypePlainParseIssue, HObjectTypeMeta, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "@hexancore/common"; +export class BookCreateCommand extends HCommand { + public static HOBJ_META = HObjectTypeMeta.application("Book", "Book", "Command", "Create", BookCreateCommand); + public title!: string; + public constructor(title: any) { + this.title = title; + } + public static parse(plain: unknown): common_1.R { + if (typeof plain !== "object") { + return PlainParseHelper.HObjectIsNotObjectParseErr(BookCreateCommand as any, plain); + } + const p = plain as Record; + const issues: PlainParseIssue[] = []; + const title = PlainParseHelper.parseString(p.title, "title", issues); + } + public static toJSON(): common_1.JsonObjectType { + return { + title: this.title + }; + } +} +" +`; + +exports[`Function transform: DTO 1`] = ` +"import { Dto, v, RefId, R, OK, type JsonObjectType, type PlainParsableHObjectType, type PlainParseError, PlainParseHelper, InvalidTypePlainParseIssue, HObjectTypeMeta, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from '@hexancore/common'; +export class TestTransformDto extends Dto { + public static HOBJ_META = HObjectTypeMeta.application("Book", "Book", "Dto", "TestTransformDto", TestTransformDto); + public optionalField?: string; + public numberField!: number; + public stringField!: string; + public booleanField!: boolean; + public bigintField!: bigint; + public primitiveArrayField!: string[]; + public uintField!: v.uint; + public ruleWithArgsField!: v.int.between<-10, 100>; + public ruleArrayField!: v.int.between<-10, 100>[]; + public ruleArrayWithItemsField!: v.int.between<-10, 100>[] & v.items.between<2, 5>; + public hObjField!: RefId; + public hObjArrayField!: RefId[]; + public constructor(numberField: any, stringField: any, booleanField: any, bigintField: any, primitiveArrayField: any, uintField: any, ruleWithArgsField: any, ruleArrayField: any, ruleArrayWithItemsField: any, hObjField: any, hObjArrayField: any, optionalField?: any) { + this.numberField = numberField; + this.stringField = stringField; + this.booleanField = booleanField; + this.bigintField = bigintField; + this.primitiveArrayField = primitiveArrayField; + this.uintField = uintField; + this.ruleWithArgsField = ruleWithArgsField; + this.ruleArrayField = ruleArrayField; + this.ruleArrayWithItemsField = ruleArrayWithItemsField; + this.hObjField = hObjField; + this.hObjArrayField = hObjArrayField; + this.optionalField = optionalField; + } + public static parse(plain: unknown): common_1.R { + if (typeof plain !== "object") { + return PlainParseHelper.HObjectIsNotObjectParseErr(TestTransformDto as any, plain); + } + const p = plain as Record; + const issues: PlainParseIssue[] = []; + let optionalField; + if (p.optionalField !== undefined) { + optionalField = PlainParseHelper.parseString(p.optionalField, "optionalField", issues); + } + const stringField = PlainParseHelper.parseString(p.stringField, "stringField", issues); + const primitiveArrayField = PlainParseHelper.parsePrimitiveArray(p.primitiveArrayField, PlainParseHelper.parseString, "primitiveArrayField", issues); + } + public static toJSON(): common_1.JsonObjectType { + return { + optionalField: this.optionalField, + numberField: this.numberField, + stringField: this.stringField, + booleanField: this.booleanField, + bigintField: this.bigintField.toString(), + primitiveArrayField: this.primitiveArrayField, + uintField: this.uintField, + ruleWithArgsField: this.ruleWithArgsField, + ruleArrayField: this.ruleArrayField, + ruleArrayWithItemsField: this.ruleArrayWithItemsField, + hObjField: this.hObjField, + hObjArrayField: this.hObjArrayField.map(item => item.toJSON()) + }; + } +} +" +`; + +exports[`Function transform: Query 1`] = ` +"import { HQuery, R, OK, type JsonObjectType, type PlainParsableHObjectType, type PlainParseError, PlainParseHelper, InvalidTypePlainParseIssue, HObjectTypeMeta, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "@hexancore/common"; +import type { BookDto } from "../../Dto/BookDto"; +export class BookGetByIdQuery extends HQuery { + public static HOBJ_META = HObjectTypeMeta.application("Book", "Book", "Query", "GetById", BookGetByIdQuery); + public title!: string; + public constructor(title: any) { + this.title = title; + } + public static parse(plain: unknown): common_1.R { + if (typeof plain !== "object") { + return PlainParseHelper.HObjectIsNotObjectParseErr(BookGetByIdQuery as any, plain); + } + const p = plain as Record; + const issues: PlainParseIssue[] = []; + const title = PlainParseHelper.parseString(p.title, "title", issues); + } + public static toJSON(): common_1.JsonObjectType { + return { + title: this.title + }; + } +} +" +`; + +exports[`Function transform: ValueObject 1`] = ` +"import { UIntValue, R, OK, type JsonObjectType, type PlainParsableHObjectType, type PlainParseError, PlainParseHelper, InvalidTypePlainParseIssue, HObjectTypeMeta, PlainParseIssue, TooBigPlainParseIssue, TooSmallPlainParseIssue } from "@hexancore/common"; +export class BookId extends UIntValue { + public static HOBJ_META = HObjectTypeMeta.domain("Book", "Book", "ValueObject", "BookId", BookId); +} +" +`;