Skip to content

Commit

Permalink
fix: getting the codegen to output correct structs
Browse files Browse the repository at this point in the history
  • Loading branch information
dafuga committed Aug 16, 2023
1 parent 87c5e97 commit bab7028
Show file tree
Hide file tree
Showing 6 changed files with 379 additions and 193 deletions.
27 changes: 9 additions & 18 deletions src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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)
Expand Down Expand Up @@ -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}`)
}
}
159 changes: 9 additions & 150 deletions src/codegen/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,6 @@ export const EOSIO_CORE_TYPES = [
'UInt8Type',
]

interface FieldType {
name: string
type: string
}

export function generateClassDeclaration(
name: string,
members: ts.ClassElement[],
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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()) {
Expand All @@ -261,7 +116,7 @@ export function findCoreType(type: string): string | undefined {
}
}

export function findInternalType(type: string, namespace: string, abi: ABI.Def): string {
export function findInternalType(type: string, namespace: string | null, abi: ABI.Def): string {
let typeString = removeDecorators(type)

const relevantAbitype = findAbiType(typeString, abi)
Expand All @@ -279,17 +134,21 @@ export function findInternalType(type: string, namespace: string, abi: ABI.Def):
return formatInternalType(typeString, namespace, abi)
}

function formatInternalType(typeString: string, namespace: string, abi: ABI.Def): string {
function formatInternalType(typeString: string, namespace: string | null, abi: ABI.Def): string {
const structNames = abi.structs.map((struct) => struct.name.toLowerCase())

if (structNames.includes(typeString.toLowerCase())) {
return `${namespace}.${capitalize(typeString)}`
return `${!!namespace ? `${namespace}.` : ''}${generateStructClassName(typeString)}`
} else {
return findCoreClass(typeString) || capitalize(typeString)
}
}

function findVariantType(typeString: string, namespace: string, abi: ABI.Def): string | undefined {
export function generateStructClassName(name) {
return name.split('_').map((word) => capitalize(word)).join('')
}

function findVariantType(typeString: string, namespace: string | null, abi: ABI.Def): string | undefined {
const abiVariant = abi.variants.find(
(variant) => variant.name.toLowerCase() === typeString.toLowerCase()
)
Expand Down Expand Up @@ -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, '')
Expand Down
Loading

0 comments on commit bab7028

Please sign in to comment.