Skip to content

Commit

Permalink
Generated code support for readonly transactions and return values (#31)
Browse files Browse the repository at this point in the history
* Initial testing.gm for return values

* Failing test

* Fixed contract name

* Updated contract kit

* The updated test

* The changes we need added to code generation

* chore: implementing return values codegen (#32)

* chore: implementing return values codegen

* enhancement: making data param on readonly method optional

---------

Co-authored-by: Daniel Fugere <[email protected]>
  • Loading branch information
aaroncox and dafuga authored Jan 17, 2024
1 parent a43b105 commit 54fb057
Show file tree
Hide file tree
Showing 14 changed files with 1,118 additions and 20 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ update_mock_contracts: node_modules clean lib
node lib/cli.js generate eosio.token -u https://jungle4.greymass.com -j test/data/abis/eosio.token.json -f test/data/contracts/mock-eosio.token.ts -e .eslintrc
node lib/cli.js generate hegemon.hgm -u https://jungle4.greymass.com -j test/data/abis/hegemon.hgm.json -f test/data/contracts/mock-hegemon.hgm.ts -e .eslintrc
node lib/cli.js generate rewards.gm -u https://jungle4.greymass.com -j test/data/abis/rewards.gm.json -f test/data/contracts/mock-rewards.gm.ts -e .eslintrc
node lib/cli.js generate testing.gm -u https://jungle4.greymass.com -j test/data/abis/testing.gm.json -f test/data/contracts/mock-testing.gm.ts -e .eslintrc

.PHONY: check
check: node_modules
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"dependencies": {
"@wharfkit/antelope": "^1.0.0",
"@wharfkit/common": "^1.2.0",
"@wharfkit/contract": "^1.1.1",
"@wharfkit/contract": "^1.1.4",
"commander": "^11.0.0",
"eslint": "^8.48.0",
"node-fetch": "^2.6.1",
Expand Down
80 changes: 80 additions & 0 deletions src/commands/contract/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ export async function generateContractClass(contractName: string, abi: ABI.Def)
classMembers.push(actionMethod)
}

if (abi.action_results.length) {
const readonlyMethod = generateReadonlyMethod()
classMembers.push(readonlyMethod)
}

if (abi.tables.length) {
const tableMethod = generateTableMethod()

Expand Down Expand Up @@ -168,6 +173,81 @@ function generateActionMethod(): ts.MethodDeclaration {
)
}

function generateReadonlyMethod(): ts.MethodDeclaration {
// Create the generic type parameter 'T' with a constraint to 'ActionReturnNames'
const typeParameter = ts.factory.createTypeParameterDeclaration(
undefined,
'T',
ts.factory.createTypeReferenceNode('ActionReturnNames')
)

// Create the function parameters.
const nameParameter = ts.factory.createParameterDeclaration(
undefined,
undefined,
'name',
undefined,
ts.factory.createTypeReferenceNode('T'),
undefined
)

const dataParameter = ts.factory.createParameterDeclaration(
undefined,
undefined,
'data',
ts.factory.createToken(ts.SyntaxKind.QuestionToken),
ts.factory.createIndexedAccessTypeNode(
ts.factory.createTypeReferenceNode('ActionNameParams'),
ts.factory.createTypeReferenceNode('T')
),
undefined
)

// Generate the function body.
const methodBody = ts.factory.createBlock(
[
ts.factory.createReturnStatement(
ts.factory.createAsExpression(
ts.factory.createAsExpression(
ts.factory.createCallExpression(
ts.factory.createPropertyAccessExpression(
ts.factory.createSuper(),
'readonly'
),
undefined,
[
ts.factory.createIdentifier('name'),
ts.factory.createIdentifier('data'),
]
),
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)
),
ts.factory.createIndexedAccessTypeNode(
ts.factory.createTypeReferenceNode('ActionReturnValues'),
ts.factory.createTypeReferenceNode('T')
)
)
),
],
true
)

// Create the method declaration
return ts.factory.createMethodDeclaration(
undefined,
undefined,
'readonly',
undefined,
[typeParameter],
[nameParameter, dataParameter],
ts.factory.createIndexedAccessTypeNode(
ts.factory.createTypeReferenceNode('ActionReturnValues'),
ts.factory.createTypeReferenceNode('T')
),
methodBody
)
}

export function generateTableMethod(): ts.MethodDeclaration {
// Create the generic type parameter 'T' with a constraint to 'tables'
const typeParameter = ts.factory.createTypeParameterDeclaration(
Expand Down
12 changes: 11 additions & 1 deletion src/commands/contract/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function getCoreImports(abi: ABI.Def) {
}
}

if (abi.variants.length != 0) {
if (abi.variants.length) {
coreImports.push('Variant')
for (const variant of abi.variants) {
variant.types.forEach((typeString) => {
Expand All @@ -59,6 +59,16 @@ export function getCoreImports(abi: ABI.Def) {
}
}

if (abi.action_results.length) {
for (const actionResult of abi.action_results) {
const {type: abiType} = findAbiType(actionResult.result_type, abi)
const coreClass = findCoreClassImport(abiType)
if (coreClass) {
coreImports.push(coreClass)
}
}
}

return {
classes: coreImports.filter((value, index, self) => self.indexOf(value) === index),
types: coreTypes
Expand Down
17 changes: 16 additions & 1 deletion src/commands/contract/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,16 @@ import {abiToBlob, ContractKit} from '@wharfkit/contract'
import {log, makeClient} from '../../utils'
import {generateContractClass} from './class'
import {generateImportStatement, getCoreImports} from './helpers'
import {generateActionNamesInterface, generateActionsNamespace} from './interfaces'
import {
generateActionNamesInterface,
generateActionReturnValuesInterface,
generateActionsNamespace,
} from './interfaces'
import {generateTableMap, generateTableTypesInterface} from './maps'
import {generateNamespace} from './namespace'
import {generateStructClasses} from './structs'
import {generateActionsTypeAlias, generateRowType, generateTablesTypeAlias} from './types'
import {generateActionReturnNamesType} from './interfaces'

const printer = ts.createPrinter()

Expand Down Expand Up @@ -161,6 +166,14 @@ export async function generateContract(contractName: string, abi: ABI, eslintrc?
const actionsTypeAlias = generateActionsTypeAlias()
const rowTypeAlias = generateRowType()

let actionResultValuesInterface: ts.InterfaceDeclaration | undefined
let actionResultsNamesType: ts.TypeAliasDeclaration | undefined

if (abi.action_results.length) {
actionResultValuesInterface = generateActionReturnValuesInterface(abi)
actionResultsNamesType = generateActionReturnNamesType()
}

const sourceFile = ts.factory.createSourceFile(
[
importAntelopeTypesStatement,
Expand All @@ -178,6 +191,8 @@ export async function generateContract(contractName: string, abi: ABI, eslintrc?
rowTypeAlias,
actionsTypeAlias,
tablesTypeAlias,
...(actionResultValuesInterface ? [actionResultValuesInterface] : []),
...(actionResultsNamesType ? [actionResultsNamesType] : []),
],
ts.factory.createToken(ts.SyntaxKind.EndOfFileToken),
ts.NodeFlags.None
Expand Down
39 changes: 38 additions & 1 deletion src/commands/contract/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {ABI} from '@wharfkit/antelope'
import ts from 'typescript'
import {parseType, removeCommas, removeDuplicateInterfaces} from './helpers'
import {getActionFieldFromAbi} from './structs'
import {findFieldTypeString, getActionFieldFromAbi} from './structs'
import {findAbiStruct, findExternalType, findTypeFromAlias, findVariant} from './finders'

export function generateActionNamesInterface(abi: ABI.Def): ts.InterfaceDeclaration {
Expand Down Expand Up @@ -157,3 +157,40 @@ function findParamTypeString(typeString: string, namespace = '', abi: ABI.Def):

return parseType(fieldType)
}

export function generateActionReturnValuesInterface(abi: ABI.Def): ts.InterfaceDeclaration {
const actionResults: ABI.ActionResult[] = abi.action_results
const members = actionResults.map((result) => {
return ts.factory.createPropertySignature(
undefined,
String(result.name),
undefined,
ts.factory.createTypeReferenceNode(
findFieldTypeString(result.result_type, 'Types.', abi),
undefined
)
)
})

return ts.factory.createInterfaceDeclaration(
undefined,
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
'ActionReturnValues',
undefined,
undefined,
members
)
}

export function generateActionReturnNamesType(): ts.TypeAliasDeclaration {
return ts.factory.createTypeAliasDeclaration(
undefined,
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
'ActionReturnNames',
undefined,
ts.factory.createTypeOperatorNode(
ts.SyntaxKind.KeyOfKeyword,
ts.factory.createTypeReferenceNode('ActionReturnValues', undefined)
)
)
}
14 changes: 5 additions & 9 deletions src/commands/contract/structs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,7 @@ export function generateVariant(variant, abi: any, isExport = false): ts.ClassDe
ts.factory.createUnionTypeNode(
variant.fields.map((field) => {
return ts.factory.createTypeReferenceNode(
ts.factory.createIdentifier(
findFieldStructTypeString(field.type, undefined, abi)
),
ts.factory.createIdentifier(findFieldTypeString(field.type, undefined, abi)),
undefined
)
})
Expand Down Expand Up @@ -178,7 +176,7 @@ export function generateField(
findFieldStructType(field.type, namespace, abi),
]

const structTypeString = findFieldStructTypeString(field.type, namespace, abi)
const structTypeString = findFieldTypeString(field.type, namespace, abi)
isArray = isArray || structTypeString.endsWith('[]')

// Build the options object if needed
Expand Down Expand Up @@ -304,7 +302,7 @@ function findVariantStructType(
namespace: string | undefined,
abi: ABI.Def
): ts.Identifier | ts.StringLiteral | ts.ObjectLiteralExpression {
const variantTypeString = findFieldStructTypeString(typeString, namespace, abi)
const variantTypeString = findFieldTypeString(typeString, namespace, abi)

if (['string', 'string[]', 'boolean', 'boolean[]'].includes(variantTypeString.toLowerCase())) {
return ts.factory.createStringLiteral(formatFieldString(variantTypeString))
Expand Down Expand Up @@ -337,9 +335,7 @@ function findFieldStructType(
namespace: string | undefined,
abi: ABI.Def
): ts.Identifier | ts.StringLiteral {
const fieldTypeString = extractDecorator(
findFieldStructTypeString(typeString, namespace, abi)
).type
const fieldTypeString = extractDecorator(findFieldTypeString(typeString, namespace, abi)).type

if (['string', 'string[]', 'boolean', 'boolean[]'].includes(fieldTypeString.toLowerCase())) {
return ts.factory.createStringLiteral(formatFieldString(fieldTypeString))
Expand All @@ -348,7 +344,7 @@ function findFieldStructType(
return ts.factory.createIdentifier(fieldTypeString)
}

function findFieldStructTypeString(
export function findFieldTypeString(
typeString: string,
namespace: string | undefined,
abi: ABI.Def
Expand Down
Loading

0 comments on commit 54fb057

Please sign in to comment.