diff --git a/src/codegen.ts b/src/codegen.ts index c609fd9..6b3c4fa 100644 --- a/src/codegen.ts +++ b/src/codegen.ts @@ -4,18 +4,18 @@ import * as prettier from 'prettier' import { EOSIO_CORE_CLASSES, EOSIO_CORE_TYPES, - generateField, generateImportStatement, - generateStruct, - getFieldTypesFromAbi, } from './codegen/helpers' import {generateNamespace, generateNamespaceName} from './codegen/namespace' import {generateContractClass} from './codegen/contract' import {abiToBlob} from './utils' +import { generateStructClasses } from './codegen/structs' const printer = ts.createPrinter() export async function codegen(contractName, abi) { + try { + const namespaceName = generateNamespaceName(contractName) const importCoreStatement = generateImportStatement( @@ -37,21 +37,8 @@ export async function codegen(contractName, abi) { const {classDeclaration} = await generateContractClass(namespaceName, contractName) - // Extract fields from the ABI - const structs = getFieldTypesFromAbi(abi) - - const structDeclarations: ts.ClassDeclaration[] = [] - - // Iterate through structs and create struct with fields - for (const struct of structs) { - const structMembers: ts.ClassElement[] = [] - - for (const field of struct.fields) { - structMembers.push(generateField(field, true, `${namespaceName}.Types`, abi)) - } - - structDeclarations.push(generateStruct(struct.structName, true, structMembers)) - } + // Iterate through structs and create struct classes with fields + const structDeclarations = generateStructClasses(abi) // Encode the ABI as a binary hex string const abiBlob = abiToBlob(abi) @@ -118,4 +105,8 @@ export async function codegen(contractName, abi) { const options = await prettier.resolveConfig(process.cwd()) return prettier.format(printer.printFile(sourceFile), options) + + } catch (e) { + console.error(`An error occurred while generating the contract code: ${e}`) + } } diff --git a/src/codegen/helpers.ts b/src/codegen/helpers.ts index 05bccd5..d1687e7 100644 --- a/src/codegen/helpers.ts +++ b/src/codegen/helpers.ts @@ -1,4 +1,4 @@ -import {ABI} from '@wharfkit/antelope' +import {ABI, Struct} from '@wharfkit/antelope' import * as ts from 'typescript' import {capitalize} from '../utils' @@ -31,11 +31,6 @@ export const EOSIO_CORE_TYPES = [ 'UInt8Type', ] -interface FieldType { - name: string - type: string -} - export function generateClassDeclaration( name: string, members: ts.ClassElement[], @@ -69,65 +64,6 @@ export function generateClassDeclaration( return classDeclaration } -export function generatePropertyDeclarationForField(name: string, type: ABI.ResolvedType) { - // field options is an object with all optional fields, e.g.: {array: true, optional: true, extension: true} - const optionsProps: ts.ObjectLiteralElementLike[] = [] - if (type.isArray) { - optionsProps.push( - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier('array'), - ts.factory.createTrue() - ) - ) - } - if (type.isOptional) { - optionsProps.push( - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier('optional'), - ts.factory.createTrue() - ) - ) - } - if (type.isExtension) { - optionsProps.push( - ts.factory.createPropertyAssignment( - ts.factory.createIdentifier('extension'), - ts.factory.createTrue() - ) - ) - } - let optionsObject: ts.ObjectLiteralExpression | undefined - if (optionsProps.length > 0) { - optionsObject = ts.factory.createObjectLiteralExpression(optionsProps) - } - // decorator is a function call, e.g.: @Struct.field(fieldTypeStringorClass, options) - const decoratorArguments: ts.Expression[] = [ts.factory.createStringLiteral(type.name)] - if (optionsObject) { - decoratorArguments.push(optionsObject) - } - const fieldDecorator = ts.factory.createDecorator( - ts.factory.createCallExpression( - ts.factory.createPropertyAccessExpression( - ts.factory.createIdentifier('Struct'), - ts.factory.createIdentifier('field') - ), - undefined, // type arguments - decoratorArguments - ) - ) - // modifier token: declare - const declareModifier = ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword) - - const propertyDeclaration = ts.factory.createPropertyDeclaration( - [fieldDecorator, declareModifier], // decorators - ts.factory.createIdentifier(name), // name - undefined, // question token - ts.factory.createTypeReferenceNode(type.name), // type - undefined // initializer - ) - return propertyDeclaration -} - export function generateImportStatement(classes, path): ts.ImportDeclaration { return ts.factory.createImportDeclaration( undefined, // modifiers @@ -148,66 +84,6 @@ export function generateImportStatement(classes, path): ts.ImportDeclaration { ) } -export function generateStruct( - structName: string, - isExport = false, - members: ts.ClassElement[] = [] -): ts.ClassDeclaration { - const decorators = [ - ts.factory.createDecorator( - ts.factory.createCallExpression(ts.factory.createIdentifier('Struct.type'), undefined, [ - ts.factory.createStringLiteral(structName), - ]) - ), - ] - - return ts.factory.createClassDeclaration( - isExport - ? [...decorators, ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)] - : decorators, - ts.factory.createIdentifier(capitalize(structName)), - undefined, // typeParameters - [ - ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ - ts.factory.createExpressionWithTypeArguments( - ts.factory.createIdentifier('Struct'), - [] - ), - ]), - ], // heritageClauses - members // Pass the members array - ) -} - -export function generateField( - field: FieldType, - isExport = false, - namespace: string, - abi: ABI.Def -): ts.PropertyDeclaration { - const fieldName = field.name.toLowerCase() - - const decorators = [ - ts.factory.createDecorator( - ts.factory.createCallExpression( - ts.factory.createIdentifier('Struct.field'), - undefined, - [ts.factory.createStringLiteral(field.type)] - ) - ), - ] - - return ts.factory.createPropertyDeclaration( - isExport - ? [...decorators, ts.factory.createModifier(ts.SyntaxKind.DeclareKeyword)] - : decorators, - ts.factory.createIdentifier(fieldName), // Fixed: Use field.name as the identifier - undefined, // questionToken - ts.factory.createTypeReferenceNode(findInternalType(field.type, namespace, abi)), // Fixed: Use field.type as the type reference - undefined // initializer - ) -} - export function generateInterface( interfaceName: string, isExport = false, @@ -224,27 +100,6 @@ export function generateInterface( ) } -export function getFieldTypesFromAbi(abi: any): {structName: string; fields: FieldType[]}[] { - const structTypes: {structName: string; fields: FieldType[]}[] = [] - - if (abi && abi.structs) { - for (const struct of abi.structs) { - const fields: FieldType[] = [] - - for (const field of struct.fields) { - fields.push({ - name: field.name.charAt(0).toUpperCase() + field.name.slice(1), - type: field.type, - }) - } - - structTypes.push({structName: struct.name, fields}) - } - } - - return structTypes -} - export function findCoreClass(type: string): string | undefined { for (const coreType of EOSIO_CORE_CLASSES) { if (type.split('_').join('') === coreType.toLowerCase()) { @@ -283,12 +138,16 @@ function formatInternalType(typeString: string, namespace: string, abi: ABI.Def) const structNames = abi.structs.map((struct) => struct.name.toLowerCase()) if (structNames.includes(typeString.toLowerCase())) { - return `${namespace}.${capitalize(typeString)}` + return `${namespace}.${generateStructClassName(typeString)}` } else { return findCoreClass(typeString) || capitalize(typeString) } } +export function generateStructClassName(name) { + return name.split('_').map((word) => capitalize(word)).join('') +} + function findVariantType(typeString: string, namespace: string, abi: ABI.Def): string | undefined { const abiVariant = abi.variants.find( (variant) => variant.name.toLowerCase() === typeString.toLowerCase() @@ -322,7 +181,7 @@ export function findExternalType(type: string, abi: ABI.Def): string { } const decorators = ['?', '[]'] -function removeDecorators(type: string) { +export function removeDecorators(type: string) { for (const decorator of decorators) { if (type.includes(decorator)) { type = type.replace(decorator, '') diff --git a/src/codegen/structs.ts b/src/codegen/structs.ts new file mode 100644 index 0000000..af7aaca --- /dev/null +++ b/src/codegen/structs.ts @@ -0,0 +1,167 @@ +import { ABI } from "@wharfkit/session" +import ts from "typescript" +import { capitalize } from "../utils" +import { findInternalType, generateStructClassName, removeDecorators } from "./helpers" + +interface FieldType { + name: string + type: string +} + +interface StructData { + structName: string + fields: FieldType[] +} + +export function generateStructClasses(abi) { + console.log({abi}) + const structs = getFieldTypesFromAbi(abi) + console.log({structs}) + const orderedStructs = orderStructs(structs) + + console.log({orderedStructs}) + + const structMembers: ts.ClassDeclaration[] = [] + + console.log({structMembers}) + + for (const struct of orderedStructs) { + structMembers.push(generateStruct(struct, abi, true)) + } + + console.log({structMembers}) + + return structMembers +} + +export function getFieldTypesFromAbi(abi: any): StructData[] { + const structTypes: {structName: string; fields: FieldType[]}[] = [] + + if (abi && abi.structs) { + for (const struct of abi.structs) { + const fields: FieldType[] = [] + + for (const field of struct.fields) { + fields.push({ + name: field.name.charAt(0).toUpperCase() + field.name.slice(1), + type: field.type, + }) + } + + structTypes.push({structName: struct.name, fields}) + } + } + + return structTypes +} + +export function generateStruct( + struct, + abi, + isExport = false, +): ts.ClassDeclaration { + const decorators = [ + ts.factory.createDecorator( + ts.factory.createCallExpression(ts.factory.createIdentifier('Struct.type'), undefined, [ + ts.factory.createStringLiteral(struct.structName), + ]) + ), + ] + + const members: ts.ClassElement[] = [] + + console.log({generateStruct: struct}) + + for (const field of struct.fields) { + members.push(generateField(field, `Types`, abi)) + } + + console.log({members}) + + return ts.factory.createClassDeclaration( + isExport + ? [...decorators, ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)] + : decorators, + ts.factory.createIdentifier(generateStructClassName(struct.structName)), + undefined, // typeParameters + [ + ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ + ts.factory.createExpressionWithTypeArguments( + ts.factory.createIdentifier('Struct'), + [] + ), + ]), + ], // heritageClauses + members // Pass the members array + ) +} + +export function generateField( + field: FieldType, + namespace: string, + abi: ABI.Def +): ts.PropertyDeclaration { + const fieldName = field.name.toLowerCase() + + console.log({fieldName}) + + const decorators = [ + ts.factory.createDecorator( + ts.factory.createCallExpression( + ts.factory.createIdentifier('Struct.field'), + undefined, + [findFieldStructType(field.type, namespace, abi)] + ) + ), + ] + + return ts.factory.createPropertyDeclaration( + decorators, + ts.factory.createIdentifier(fieldName), + ts.factory.createToken(ts.SyntaxKind.ExclamationToken), + ts.factory.createTypeReferenceNode(findInternalType(field.type, namespace, abi)), + undefined // initializer + ) +} + +function orderStructs(structs) { + const orderedStructs: StructData[] = [] + const structNames = structs.map((struct) => struct.structName) + + console.log({structNames}) + + for (const struct of structs) { + for (const field of struct.fields) { + const fieldType = removeDecorators(field.type) + + console.log({fieldType}) + + if (structNames.includes(fieldType.toLowerCase())) { + const dependencyStruct = structs.find((struct) => struct.structName === fieldType.toLowerCase()) + orderedStructs.push(dependencyStruct) + } + } + + orderedStructs.push(struct) + } + + return orderedStructs.filter((struct, index, self) => { + return index === self.findIndex((s) => ( + s.structName === struct.structName + )) + }) +} + +function findFieldStructType(typeString: string, namespace: string, abi: ABI.Def): ts.Identifier | ts.StringLiteral { + const fieldType = findInternalType(typeString, namespace, abi) + + if (['String', 'Boolean', 'Number'].includes(fieldType)) { + return ts.factory.createStringLiteral(fieldType.toLowerCase()) + } + + if (fieldType === 'Symbol') { + return ts.factory.createIdentifier('Asset.Symbol') + } + + return ts.factory.createIdentifier(fieldType) +} \ No newline at end of file diff --git a/test/tmp/rewards.gm.ts b/test/tmp/rewards.gm.ts new file mode 100644 index 0000000..36df333 --- /dev/null +++ b/test/tmp/rewards.gm.ts @@ -0,0 +1,125 @@ +import {ActionOptions, Contract as BaseContract, ContractArgs, PartialBy} from '../../src/index' +import { + ABI, + APIClient, + Session, + Struct, + TransactResult, + Asset, + Checksum256, + Float64, + Name, + TimePoint, + TimePointSec, + UInt128, + UInt16, + UInt32, + UInt64, + UInt8, + AssetType, + Blob, + Checksum256Type, + Float64Type, + NameType, + TimePointType, + UInt128Type, + UInt16Type, + UInt32Type, + UInt64Type, + UInt8Type, +} from '@wharfkit/session' +export namespace RewardsGm { + export const abiBlob = Blob.from( + 'DmVvc2lvOjphYmkvMS4yAAoHYWRkdXNlcgACB2FjY291bnQEbmFtZQZ3ZWlnaHQGdWludDE2BWNsYWltAAIHYWNjb3VudARuYW1lBmFtb3VudAZhc3NldD8GY29uZmlnAAMMdG9rZW5fc3ltYm9sBnN5bWJvbA5vcmFjbGVfYWNjb3VudARuYW1lDG9yYWNsZV9wYWlycw1vcmFjbGVfcGFpcltdCWNvbmZpZ3VyZQADDHRva2VuX3N5bWJvbAZzeW1ib2wOb3JhY2xlX2FjY291bnQEbmFtZQxvcmFjbGVfcGFpcnMNb3JhY2xlX3BhaXJbXQdkZWx1c2VyAAEHYWNjb3VudARuYW1lC29yYWNsZV9wYWlyAAIEbmFtZQRuYW1lCXByZWNpc2lvbgZ1aW50MTYKcHJpY2VfaW5mbwADBHBhaXIGc3RyaW5nBXByaWNlB2Zsb2F0NjQJdGltZXN0YW1wCnRpbWVfcG9pbnQHcmVjZWlwdAADB2FjY291bnQEbmFtZQZhbW91bnQFYXNzZXQGdGlja2VyDHByaWNlX2luZm9bXQp1cGRhdGV1c2VyAAIHYWNjb3VudARuYW1lBndlaWdodAZ1aW50MTYIdXNlcl9yb3cAAwdhY2NvdW50BG5hbWUGd2VpZ2h0BnVpbnQxNgdiYWxhbmNlBWFzc2V0BgAAAOAqrFMyB2FkZHVzZXKVAi0tLQpzcGVjX3ZlcnNpb246ICIwLjIuMCIKdGl0bGU6IEFkZCB1c2VyCnN1bW1hcnk6ICdBZGQgbmV3IHVzZXIge3tub3dyYXAgYWNjb3VudH19JwppY29uOiBodHRwczovL2FsbW9zdC5kaWdpdGFsL2ltYWdlcy9taXNjX2ljb24ucG5nIzZmNWVhOTc4YjA0ZDAzZTAxOGIzNzlhMmJhYzRjMTBiNWE4ZmUwY2Q1ZTZlMTVjODg4MjhkYzk4NmJlOTZjZmYKLS0tCgp7e2FjY291bnR9fSBpcyBhZGRlZCB0byB0aGUgcmV3YXJkcyBzaGFyaW5nIGxpc3Qgd2l0aCB3ZWlnaHQge3t3ZWlnaHR9fS4AAAAAAOlMRAVjbGFpbfYCLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogQ2xhaW0Kc3VtbWFyeTogJ0NsYWltIHJld2FyZHMgZm9yIHt7bm93cmFwIGFjY291bnR9fScKaWNvbjogaHR0cHM6Ly9hbG1vc3QuZGlnaXRhbC9pbWFnZXMvY2xhaW1faWNvbi5wbmcjYmI1OTdmNGFjYzEzMDU5MjU5MTJlMThlN2I0Y2Y3MDhkMWZhZWMyYWE4OGI3YTUzZDg3OTY5ZTA0NTE2OGVjZgotLS0KCnt7I2lmX2hhc192YWx1ZSBhbW91bnR9fQogICAge3thY2NvdW50fX0gY2xhaW1zIHt7YW1vdW50fX0gZnJvbSB0aGVpciByZXdhcmRzIGJhbGFuY2UuCnt7ZWxzZX19CiAgICB7e2FjY291bnR9fSBjbGFpbXMgdGhlaXIgZW50aXJlIHJld2FyZHMgYmFsYW5jZS4Ke3svaWZfaGFzX3ZhbHVlfX0AAFBXM7cmRQljb25maWd1cmUAAAAA4Cqso0oHZGVsdXNlcsQCLS0tCnNwZWNfdmVyc2lvbjogIjAuMi4wIgp0aXRsZTogRGVsZXRlIHVzZXIKc3VtbWFyeTogJ0RlbGV0ZSB1c2VyIHt7bm93cmFwIGFjY291bnR9fScKaWNvbjogaHR0cHM6Ly9hbG1vc3QuZGlnaXRhbC9pbWFnZXMvbWlzY19pY29uLnBuZyM2ZjVlYTk3OGIwNGQwM2UwMThiMzc5YTJiYWM0YzEwYjVhOGZlMGNkNWU2ZTE1Yzg4ODI4ZGM5ODZiZTk2Y2ZmCi0tLQoKe3thY2NvdW50fX0gaXMgaXMgcmVtb3ZlZCBmcm9tIHRoZSByZXdhcmRzIHNoYXJpbmcgbGlzdC4KClVzZXJzIGNhbiBvbmx5IGJlIHJlbW92ZWQgaWYgdGhlaXIgcmV3YXJkcyBiYWxhbmNlIGlzIHplcm8uAAAAIFenkLoHcmVjZWlwdAAAwFVYq2xS1Qp1cGRhdGV1c2VygAItLS0Kc3BlY192ZXJzaW9uOiAiMC4yLjAiCnRpdGxlOiBVcGRhdGUgdXNlcgpzdW1tYXJ5OiAnVXBkYXRlIHVzZXIge3tub3dyYXAgYWNjb3VudH19JwppY29uOiBodHRwczovL2FsbW9zdC5kaWdpdGFsL2ltYWdlcy9taXNjX2ljb24ucG5nIzZmNWVhOTc4YjA0ZDAzZTAxOGIzNzlhMmJhYzRjMTBiNWE4ZmUwY2Q1ZTZlMTVjODg4MjhkYzk4NmJlOTZjZmYKLS0tCgp7e2FjY291bnR9fSBpcyB1cGRhdGVkIHRvIGhhdmUgd2VpZ2h0IHt7d2VpZ2h0fX0uAgAAAAAwtyZFA2k2NAAABmNvbmZpZwAAAAAAfBXWA2k2NAAACHVzZXJfcm93AAAAAA==' + ) + export const abi = ABI.from(abiBlob) + export class Contract extends BaseContract { + constructor(args: PartialBy) { + super({ + client: args.client, + abi: abi, + account: Name.from('rewards.gm'), + }) + } + } + export namespace Types { + @Struct.type('adduser') + export class Adduser extends Struct { + @Struct.field(Name) + account!: Name + @Struct.field(UInt16) + weight!: UInt16 + } + @Struct.type('claim') + export class Claim extends Struct { + @Struct.field(Name) + account!: Name + @Struct.field(Asset) + amount!: Asset + } + @Struct.type('oracle_pair') + export class OraclePair extends Struct { + @Struct.field(Name) + name!: Name + @Struct.field(UInt16) + precision!: UInt16 + } + @Struct.type('config') + export class Config extends Struct { + @Struct.field(Asset.Symbol) + token_symbol!: Symbol + @Struct.field(Name) + oracle_account!: Name + @Struct.field(Types.OraclePair) + oracle_pairs!: Types.OraclePair + } + @Struct.type('configure') + export class Configure extends Struct { + @Struct.field(Asset.Symbol) + token_symbol!: Symbol + @Struct.field(Name) + oracle_account!: Name + @Struct.field(Types.OraclePair) + oracle_pairs!: Types.OraclePair + } + @Struct.type('deluser') + export class Deluser extends Struct { + @Struct.field(Name) + account!: Name + } + @Struct.type('price_info') + export class PriceInfo extends Struct { + @Struct.field('string') + pair!: String + @Struct.field(Float64) + price!: Float64 + @Struct.field(TimePoint) + timestamp!: TimePoint + } + @Struct.type('receipt') + export class Receipt extends Struct { + @Struct.field(Name) + account!: Name + @Struct.field(Asset) + amount!: Asset + @Struct.field(Types.PriceInfo) + ticker!: Types.PriceInfo + } + @Struct.type('updateuser') + export class Updateuser extends Struct { + @Struct.field(Name) + account!: Name + @Struct.field(UInt16) + weight!: UInt16 + } + @Struct.type('user_row') + export class UserRow extends Struct { + @Struct.field(Name) + account!: Name + @Struct.field(UInt16) + weight!: UInt16 + @Struct.field(Asset) + balance!: Asset + } + } +} diff --git a/test/utils/codegen.ts b/test/utils/codegen.ts index 41858e6..67d9e9d 100644 --- a/test/utils/codegen.ts +++ b/test/utils/codegen.ts @@ -30,5 +30,5 @@ export async function generateCodegenContract(contractName: string) { } export function removeCodegenContracts() { - fs.rmSync('test/tmp', {recursive: true, force: true}) + // fs.rmSync('test/tmp', {recursive: true, force: true}) }