Skip to content

Commit

Permalink
feat(compiler): add initial HObject transformer
Browse files Browse the repository at this point in the history
  • Loading branch information
Mararok committed Sep 25, 2024
1 parent 0e3bd76 commit b3077a7
Showing 8 changed files with 723 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/Compiler/Transformer/Feature/FeatureTsTransformer.ts
Original file line number Diff line number Diff line change
@@ -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),
];
}

Original file line number Diff line number Diff line change
@@ -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)
)
);
}
}
112 changes: 112 additions & 0 deletions src/Compiler/Transformer/Feature/HObject/HObjectParseTsFactory.ts
Original file line number Diff line number Diff line change
@@ -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<keyof TestDto, unknown>;`
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);

}
}
Original file line number Diff line number Diff line change
@@ -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");
}



}
96 changes: 96 additions & 0 deletions src/Compiler/Transformer/Feature/HObject/HObjectToJSONTsFactory.ts
Original file line number Diff line number Diff line change
@@ -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,
);
}

}
Loading

0 comments on commit b3077a7

Please sign in to comment.