From 7334220ff34dc5456b8dc4bf98817961a506bbf1 Mon Sep 17 00:00:00 2001 From: apporc Date: Tue, 28 Nov 2023 15:02:01 +0800 Subject: [PATCH] Fix variant type generation --- src/commands/contract/helpers.ts | 92 ++++++----------- src/commands/contract/structs.ts | 123 +++++++++++++++++++---- test/data/contracts/mock-atomicassets.ts | 92 +++++++++++------ test/data/contracts/mock-boid.ts | 83 +++++++++------ 4 files changed, 251 insertions(+), 139 deletions(-) diff --git a/src/commands/contract/helpers.ts b/src/commands/contract/helpers.ts index fec47a4..705e1a4 100644 --- a/src/commands/contract/helpers.ts +++ b/src/commands/contract/helpers.ts @@ -34,34 +34,35 @@ export function getCoreImports(abi: ABI.Def) { const {type} = findAbiType(field.type, abi) - if (type.includes(' | ')) { - coreImports.push('Variant') + const coreClass = findCoreClassImport(type) - type.split(' | ').forEach((typeString) => { - const coreType = findCoreClassImport(typeString) - - if (coreType) { - coreTypes.push(coreType) - } - }) - } else { - const coreClass = findCoreClassImport(type) + if (coreClass) { + coreImports.push(coreClass) + } - if (coreClass) { - coreImports.push(coreClass) - } + // We don't need to add action types unless the struct is an action param + if (!structIsActionParams) { + continue + } - // We don't need to add action types unless the struct is an action param - if (!structIsActionParams) { - continue - } + const coreType = findCoreType(type) - const coreType = findCoreType(type) + if (coreType) { + coreTypes.push(coreType) + } + } + } - if (coreType) { - coreTypes.push(coreType) + if (abi.variants.length != 0) { + coreImports.push('Variant') + for (const variant of abi.variants) { + variant.types.forEach((typeString) => { + const {type: abiType} = findAbiType(typeString, abi) + const coreClass = findCoreClassImport(abiType) + if (coreClass) { + coreImports.push(coreClass) } - } + }) } } @@ -180,6 +181,7 @@ export function findInternalType( ): string { const {type: typeString, decorator} = findType(type, abi, typeNamespace) + // TODO: inside findType, namespace is prefixed, but format internal is doing the same return formatInternalType(typeString, typeNamespace, abi, decorator) } @@ -189,7 +191,7 @@ function formatInternalType( abi: ABI.Def, decorator = '' ): string { - const structNames = abi.structs.map((struct) => struct.name.toLowerCase()) + const structNames = [...abi.structs, ...abi.variants].map((struct) => struct.name.toLowerCase()) let type @@ -209,36 +211,10 @@ function findAliasType(typeString: string, abi: ABI.Def): string | undefined { return alias?.type && `${alias?.type}${decorator || ''}` } -function findVariantType( - typeString: string, - abi: ABI.Def, - typeNamespace: string, - context: string -): string | undefined { - const abiVariant = abi.variants.find( - (variant) => variant.name.toLowerCase() === typeString.toLowerCase() - ) - - if (!abiVariant) { - return - } - - return abiVariant.types - .map((type) => { - if (context === 'external') { - return parseType(findExternalType(type, typeNamespace, abi)) - } else { - return parseType(findInternalType(type, typeNamespace, abi)) - } - }) - .join(' | ') -} - export function findAbiType( type: string, abi: ABI.Def, - typeNamespace = '', - context = 'internal' + typeNamespace = '' ): {type: string; decorator?: string} { let typeString = parseType(trim(type)) @@ -252,13 +228,9 @@ export function findAbiType( typeString = extractDecoratorResponse.type const decorator = extractDecoratorResponse.decorator - const variantType = findVariantType(typeString, abi, typeNamespace, context) - - if (variantType) { - return {type: variantType, decorator} - } - - const abiType = abi.structs.find((abiType) => abiType.name === typeString)?.name + const abiType = [...abi.structs, ...abi.variants].find( + (abiType) => abiType.name === typeString + )?.name if (abiType) { return {type: `${typeNamespace}${formatClassName(abiType)}`, decorator} @@ -268,13 +240,13 @@ export function findAbiType( } export function findExternalType(type: string, typeNamespace = '', abi: ABI.Def): string { - const {type: typeString, decorator} = findType(type, abi, typeNamespace, 'external') + const {type: typeString, decorator} = findType(type, abi, typeNamespace) return `${findCoreType(typeString) || capitalize(typeString)}${decorator === '[]' ? '[]' : ''}` } -function findType(type: string, abi: ABI.Def, typeNamespace?: string, context = 'internal') { - return findAbiType(type, abi, typeNamespace, context) +function findType(type: string, abi: ABI.Def, typeNamespace?: string) { + return findAbiType(type, abi, typeNamespace) } const decorators = ['?', '[]'] diff --git a/src/commands/contract/structs.ts b/src/commands/contract/structs.ts index 50fb134..a4cb5ce 100644 --- a/src/commands/contract/structs.ts +++ b/src/commands/contract/structs.ts @@ -30,7 +30,9 @@ export function generateStructClasses(abi) { structMembers.push(generateStruct(struct, abi, true)) } - return structMembers + const variants: ts.ClassDeclaration[] = generateVariants(abi, true) + + return [...structMembers, ...variants] } export function getActionFieldFromAbi(abi: any): StructData[] { @@ -55,6 +57,69 @@ export function getActionFieldFromAbi(abi: any): StructData[] { return structTypes } +export function generateVariants(abi: any, isExport = false): ts.ClassDeclaration[] { + const variants: ts.ClassDeclaration[] = [] + for (const abiVariant of abi.variants) { + const decoratorArguments: ( + | ts.ObjectLiteralExpression + | ts.StringLiteral + | ts.Identifier + )[] = abiVariant.types.map((typeString) => + findVariantStructType(typeString, undefined, abi) + ) + + const decorators = [ + ts.factory.createDecorator( + ts.factory.createCallExpression( + ts.factory.createIdentifier('Variant.type'), + undefined, + [ + ts.factory.createStringLiteral(abiVariant.name), + ts.factory.createArrayLiteralExpression(decoratorArguments), + ] + ) + ), + ] + + const valueField = ts.factory.createPropertyDeclaration( + [], + ts.factory.createIdentifier('value'), + undefined, + ts.factory.createUnionTypeNode( + abiVariant.types.map((type) => { + return ts.factory.createTypeReferenceNode( + ts.factory.createIdentifier( + findFieldStructTypeString(type, undefined, abi) + ), + undefined + ) + }) + ), + undefined + ) + + variants.push( + ts.factory.createClassDeclaration( + isExport + ? [...decorators, ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)] + : decorators, + ts.factory.createIdentifier(formatClassName(abiVariant.name)), + undefined, // typeParameters + [ + ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [ + ts.factory.createExpressionWithTypeArguments( + ts.factory.createIdentifier('Struct'), + [] + ), + ]), + ], // heritageClauses + [valueField] + ) + ) + } + return variants +} + export function generateStruct(struct, abi, isExport = false): ts.ClassDeclaration { const decorators = [ ts.factory.createDecorator( @@ -139,24 +204,11 @@ export function generateField( ), ] - let typeReferenceNode: ts.TypeReferenceNode | ts.UnionTypeNode - const structTypeString = findFieldStructTypeString(field.type, namespace, abi) - if (structTypeString.includes(' | ')) { - typeReferenceNode = ts.factory.createUnionTypeNode( - structTypeString.split(' | ').map((type) => { - return ts.factory.createTypeReferenceNode( - ts.factory.createIdentifier(type), - undefined - ) - }) - ) - } else { - typeReferenceNode = ts.factory.createTypeReferenceNode( - extractDecorator(structTypeString).type - ) - } + const typeReferenceNode = ts.factory.createTypeReferenceNode( + extractDecorator(structTypeString).type + ) let typeNode: ts.TypeNode @@ -229,6 +281,39 @@ function findDependencies( return dependencies } +function findVariantStructType( + typeString: string, + namespace: string | undefined, + abi: ABI.Def +): ts.Identifier | ts.StringLiteral | ts.ObjectLiteralExpression { + const variantTypeString = findFieldStructTypeString(typeString, namespace, abi) + + if (['string', 'string[]', 'boolean', 'boolean[]'].includes(variantTypeString.toLowerCase())) { + return ts.factory.createStringLiteral(formatFieldString(variantTypeString)) + } + + const isArray = variantTypeString.endsWith('[]') + if (isArray) { + const optionsProps: ts.ObjectLiteralElementLike[] = [] + optionsProps.push( + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('type'), + ts.factory.createIdentifier(extractDecorator(variantTypeString).type) + ) + ) + optionsProps.push( + ts.factory.createPropertyAssignment( + ts.factory.createIdentifier('array'), + ts.factory.createTrue() + ) + ) + + const optionsObject = ts.factory.createObjectLiteralExpression(optionsProps) + return optionsObject + } else { + return ts.factory.createIdentifier(variantTypeString) + } +} function findFieldStructType( typeString: string, namespace: string | undefined, @@ -238,10 +323,6 @@ function findFieldStructType( findFieldStructTypeString(typeString, namespace, abi) ).type - if (fieldTypeString.includes(' | ')) { - return ts.factory.createIdentifier('Variant') - } - if (['string', 'string[]', 'boolean', 'boolean[]'].includes(fieldTypeString.toLowerCase())) { return ts.factory.createStringLiteral(formatFieldString(fieldTypeString)) } diff --git a/test/data/contracts/mock-atomicassets.ts b/test/data/contracts/mock-atomicassets.ts index 3345089..a0c4794 100644 --- a/test/data/contracts/mock-atomicassets.ts +++ b/test/data/contracts/mock-atomicassets.ts @@ -1,15 +1,9 @@ import type { Action, AssetType, - Bytes, - Float32, Float64Type, - Int16, Int32Type, - Int64, - Int8, NameType, - UInt16, UInt32Type, UInt64Type, } from '@wharfkit/antelope' @@ -17,10 +11,16 @@ import { ABI, Asset, Blob, + Bytes, + Float32, Float64, + Int16, Int32, + Int64, + Int8, Name, Struct, + UInt16, UInt32, UInt64, UInt8, @@ -418,30 +418,10 @@ export namespace Types { export class pair_string_ATOMIC_ATTRIBUTE extends Struct { @Struct.field('string') key!: string - @Struct.field(Variant) - value!: - | Int8 - | Int16 - | Int32 - | Int64 - | UInt8 - | UInt16 - | UInt32 - | UInt64 - | Float32 - | Float64 - | string - | Bytes - | Int16[] - | Int32[] - | Int64[] - | UInt8[] - | UInt16[] - | UInt32[] - | UInt64[] - | Float32[] - | Float64[] - | string[] + @Struct.field( + variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT_VEC_DOUBLE_VEC_STRING_VEC + ) + value!: variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT_VEC_DOUBLE_VEC_STRING_VEC } @Struct.type('createcol') export class createcol extends Struct { @@ -780,6 +760,58 @@ export namespace Types { @Struct.field(Asset) token_to_withdraw!: Asset } + @Variant.type( + 'variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT_VEC_DOUBLE_VEC_STRING_VEC', + [ + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + Float32, + Float64, + 'string', + Bytes, + {type: Int16, array: true}, + {type: Int32, array: true}, + {type: Int64, array: true}, + {type: UInt8, array: true}, + {type: UInt16, array: true}, + {type: UInt32, array: true}, + {type: UInt64, array: true}, + {type: Float32, array: true}, + {type: Float64, array: true}, + 'string[]', + ] + ) + export class variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT_VEC_DOUBLE_VEC_STRING_VEC extends Struct { + value: + | Int8 + | Int16 + | Int32 + | Int64 + | UInt8 + | UInt16 + | UInt32 + | UInt64 + | Float32 + | Float64 + | string + | Bytes + | Int16[] + | Int32[] + | Int64[] + | UInt8[] + | UInt16[] + | UInt32[] + | UInt64[] + | Float32[] + | Float64[] + | string[] + } } export const TableMap = { assets: Types.assets_s, diff --git a/test/data/contracts/mock-boid.ts b/test/data/contracts/mock-boid.ts index e0f3cb1..3b043af 100644 --- a/test/data/contracts/mock-boid.ts +++ b/test/data/contracts/mock-boid.ts @@ -1,11 +1,7 @@ import type { Action, BytesType, - Float64, - Int16, Int32Type, - Int64, - Int8, NameType, PublicKeyType, SignatureType, @@ -20,7 +16,11 @@ import { Blob, Bytes, Float32, + Float64, + Int16, Int32, + Int64, + Int8, Name, PublicKey, Signature, @@ -468,30 +468,8 @@ export namespace Types { export class AtomicAttribute extends Struct { @Struct.field('string') key!: string - @Struct.field(Variant) - value!: - | Int8 - | Int16 - | Int32 - | Int64 - | UInt8 - | UInt16 - | UInt32 - | UInt64 - | Float32 - | Float64 - | string - | Int8[] - | Int16[] - | Int32[] - | Int64[] - | Bytes - | UInt16[] - | UInt32[] - | UInt64[] - | Float32[] - | Float64[] - | string[] + @Struct.field(AtomicValue) + value!: AtomicValue } @Struct.type('AtomicFormat') export class AtomicFormat extends Struct { @@ -1255,6 +1233,55 @@ export namespace Types { @Struct.field(Name) to!: Name } + @Variant.type('AtomicValue', [ + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + Float32, + Float64, + 'string', + {type: Int8, array: true}, + {type: Int16, array: true}, + {type: Int32, array: true}, + {type: Int64, array: true}, + Bytes, + {type: UInt16, array: true}, + {type: UInt32, array: true}, + {type: UInt64, array: true}, + {type: Float32, array: true}, + {type: Float64, array: true}, + 'string[]', + ]) + export class AtomicValue extends Struct { + value: + | Int8 + | Int16 + | Int32 + | Int64 + | UInt8 + | UInt16 + | UInt32 + | UInt64 + | Float32 + | Float64 + | string + | Int8[] + | Int16[] + | Int32[] + | Int64[] + | Bytes + | UInt16[] + | UInt32[] + | UInt64[] + | Float32[] + | Float64[] + | string[] + } } export const TableMap = { accounts: Types.Account,