Skip to content

Commit

Permalink
enhancement: handling variants in action type interface generation
Browse files Browse the repository at this point in the history
  • Loading branch information
dafuga committed Dec 12, 2023
1 parent 773f9b2 commit 1b0f3e8
Show file tree
Hide file tree
Showing 6 changed files with 699 additions and 647 deletions.
123 changes: 123 additions & 0 deletions src/commands/contract/finders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import * as Antelope from '@wharfkit/antelope'
import { ABI } from "@wharfkit/antelope"
import { capitalize, extractDecorator, formatInternalType, parseType, trim } from "./helpers"
import { formatClassName } from "../../utils"

const ANTELOPE_CLASSES: string[] = []
Object.keys(Antelope).map((key) => {
if (Antelope[key].abiName) {
ANTELOPE_CLASSES.push(key)
}
})

export const ANTELOPE_CLASS_MAPPINGS = {
block_timestamp_type: 'BlockTimestamp',
}

export function findAliasType(typeString: string, abi: ABI.Def): string | undefined {
const {type: typeStringWithoutDecorator, decorator} = extractDecorator(typeString)
const alias = abi.types.find((type) => type.new_type_name === typeStringWithoutDecorator)

return alias?.type && `${alias?.type}${decorator || ''}`
}

export function findAbiStruct(
type: string,
abi: ABI.Def,
): ABI.Struct | undefined {
const extractDecoratorResponse = extractDecorator(type)
const typeString = extractDecoratorResponse.type

const aliasType = findAliasType(typeString, abi)

let abiStruct = abi.structs.find(
(abiType) => abiType.name === typeString || abiType.name === aliasType
)

return abiStruct
}

export function findVariant(
typeString: string,
abi: ABI.Def
): ABI.Variant | undefined {
const {type: typeStringWithoutDecorator, decorator} = extractDecorator(typeString)

const aliastype = findAliasType(typeStringWithoutDecorator, abi)

return abi.variants.find(
(variant) => variant.name === typeStringWithoutDecorator || variant.name === aliastype
)
}

export function findAbiType(
type: string,
abi: ABI.Def,
typeNamespace = ''
): {type: string; decorator?: string} {
let typeString = parseType(trim(type))

const aliasType = findAliasType(typeString, abi)

if (aliasType) {
typeString = aliasType
}

const extractDecoratorResponse = extractDecorator(typeString)
typeString = extractDecoratorResponse.type
const decorator = extractDecoratorResponse.decorator

const abiType = [...abi.structs, ...abi.variants].find(
(abiType) => abiType.name === typeString
)?.name

if (abiType) {
return {type: `${typeNamespace}${formatClassName(abiType)}`, decorator}
}

return {type: typeString, decorator}
}

export function findExternalType(type: string, typeNamespace = '', abi: ABI.Def): string {
const {type: typeString, decorator} = findType(type, abi, typeNamespace)

return `${findCoreType(typeString) || capitalize(typeString)}${decorator === '[]' ? '[]' : ''}`
}

function findType(type: string, abi: ABI.Def, typeNamespace?: string) {
return findAbiType(type, abi, typeNamespace)
}

export function findCoreType(type: string): string | undefined {
const coreType = findCoreClass(type)

if (coreType) {
return `${coreType}Type`
}
}

export function findCoreClass(type: string): string | undefined {
if (ANTELOPE_CLASS_MAPPINGS[type]) {
return ANTELOPE_CLASS_MAPPINGS[type]
}

const parsedType = parseType(trim(type)).split('_').join('').toLowerCase()

return (
ANTELOPE_CLASSES.find((antelopeClass) => parsedType === antelopeClass.toLowerCase()) ||
ANTELOPE_CLASSES.find(
(antelopeClass) => parsedType.replace(/[0-9]/g, '') === antelopeClass.toLowerCase()
)
)
}

export function findInternalType(
type: string,
typeNamespace: string | undefined,
abi: ABI.Def
): 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)
}
114 changes: 5 additions & 109 deletions src/commands/contract/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,8 @@
import * as Antelope from '@wharfkit/antelope'
import type {ABI} from '@wharfkit/antelope'
import * as ts from 'typescript'
import {formatClassName} from '../../utils'

const ANTELOPE_CLASSES: string[] = []
Object.keys(Antelope).map((key) => {
if (Antelope[key].abiName) {
ANTELOPE_CLASSES.push(key)
}
})

export const ANTELOPE_CLASS_MAPPINGS = {
block_timestamp_type: 'BlockTimestamp',
}
import {findAbiType, findCoreClass, findCoreType} from './finders'
import { TypeInterfaceDeclaration } from './interfaces'

export function getCoreImports(abi: ABI.Def) {
const coreImports: string[] = []
Expand Down Expand Up @@ -151,41 +141,7 @@ export function generateInterface(
)
}

export function findCoreClass(type: string): string | undefined {
if (ANTELOPE_CLASS_MAPPINGS[type]) {
return ANTELOPE_CLASS_MAPPINGS[type]
}

const parsedType = parseType(trim(type)).split('_').join('').toLowerCase()

return (
ANTELOPE_CLASSES.find((antelopeClass) => parsedType === antelopeClass.toLowerCase()) ||
ANTELOPE_CLASSES.find(
(antelopeClass) => parsedType.replace(/[0-9]/g, '') === antelopeClass.toLowerCase()
)
)
}

export function findCoreType(type: string): string | undefined {
const coreType = findCoreClass(type)

if (coreType) {
return `${coreType}Type`
}
}

export function findInternalType(
type: string,
typeNamespace: string | undefined,
abi: ABI.Def
): 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)
}

function formatInternalType(
export function formatInternalType(
typeString: string,
namespace = '',
abi: ABI.Def,
Expand All @@ -204,66 +160,6 @@ function formatInternalType(
return `${type}${decorator}`
}

function findAliasType(typeString: string, abi: ABI.Def): string | undefined {
const {type: typeStringWithoutDecorator, decorator} = extractDecorator(typeString)
const alias = abi.types.find((type) => type.new_type_name === typeStringWithoutDecorator)

return alias?.type && `${alias?.type}${decorator || ''}`
}

export function findAbiStruct(
type: string,
abi: ABI.Def,
): ABI.Struct | undefined {
const extractDecoratorResponse = extractDecorator(type)
const typeString = extractDecoratorResponse.type
const decorator = extractDecoratorResponse.decorator

const abiStruct = abi.structs.find(
(abiType) => abiType.name === typeString
)

return abiStruct
}

export function findAbiType(
type: string,
abi: ABI.Def,
typeNamespace = ''
): {type: string; decorator?: string} {
let typeString = parseType(trim(type))

const aliasType = findAliasType(typeString, abi)

if (aliasType) {
typeString = aliasType
}

const extractDecoratorResponse = extractDecorator(typeString)
typeString = extractDecoratorResponse.type
const decorator = extractDecoratorResponse.decorator

const abiType = [...abi.structs, ...abi.variants].find(
(abiType) => abiType.name === typeString
)?.name

if (abiType) {
return {type: `${typeNamespace}${formatClassName(abiType)}`, decorator}
}

return {type: typeString, decorator}
}

export function findExternalType(type: string, typeNamespace = '', abi: ABI.Def): string {
const {type: typeString, decorator} = findType(type, abi, typeNamespace)

return `${findCoreType(typeString) || capitalize(typeString)}${decorator === '[]' ? '[]' : ''}`
}

function findType(type: string, abi: ABI.Def, typeNamespace?: string) {
return findAbiType(type, abi, typeNamespace)
}

const decorators = ['?', '[]']
export function extractDecorator(type: string): {type: string; decorator?: string} {
for (const decorator of decorators) {
Expand Down Expand Up @@ -303,7 +199,7 @@ export function parseType(type: string): string {
return type
}

function trim(string: string) {
export function trim(string: string) {
return string.replace(/\s/g, '')
}

Expand All @@ -315,7 +211,7 @@ export function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}

export function removeDuplicateInterfaces(interfaces: ts.InterfaceDeclaration[]): ts.InterfaceDeclaration[] {
export function removeDuplicateInterfaces(interfaces: TypeInterfaceDeclaration[]): TypeInterfaceDeclaration[] {
const seen: string[] = [];

return interfaces.filter(interfaceDeclaration => {
Expand Down
54 changes: 38 additions & 16 deletions src/commands/contract/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type {ABI} from '@wharfkit/antelope'
import ts from 'typescript'
import {findAbiStruct, findExternalType, parseType, removeDuplicateInterfaces} from './helpers'
import {parseType, removeDuplicateInterfaces} from './helpers'
import {getActionFieldFromAbi} from './structs'
import { findAbiStruct, findExternalType, findVariant } from './finders'

export function generateActionNamesInterface(abi: ABI.Def): ts.InterfaceDeclaration {
// Generate property signatures for each action
Expand All @@ -26,29 +27,50 @@ export function generateActionNamesInterface(abi: ABI.Def): ts.InterfaceDeclarat
)
}

export function generateActionInterface(struct, abi): { actionInterface: ts.InterfaceDeclaration, typeInterfaces: ts.InterfaceDeclaration[] } {
const typeInterfaces: ts.InterfaceDeclaration[] = []
export type TypeInterfaceDeclaration = ts.InterfaceDeclaration | ts.TypeAliasDeclaration

export function generateActionInterface(struct, abi): { actionInterface: ts.InterfaceDeclaration, typeInterfaces: TypeInterfaceDeclaration[] } {
const typeInterfaces: TypeInterfaceDeclaration[] = []

const members = struct.fields.map((field) => {
const typeReferenceNode = ts.factory.createTypeReferenceNode(
findParamTypeString(field.type, 'Types.', abi)
)

// We need to check for types and variants. We also need to add core types that may be used in structs that are
// used in action params (the check will have to recursivly look for those structs). Optionally, we can add all
// core types and have eslint remove them.
if (field.type === 'blockchain_parameters_v1') {
console.log({type: field.type})
}
const abiVariant = findVariant(field.type, abi)

const typeStruct = findAbiStruct(field.type, abi)
let types

if (typeStruct) {
const interfaces = generateActionInterface(typeStruct, abi)

typeInterfaces.push(interfaces.actionInterface, ...interfaces.typeInterfaces)
if (abiVariant) {
types = abiVariant.types

const variantTypeName = `${struct.structName || struct.name}_${field.name}_Variant`;
const variantTypeNodes = types.map((type) =>
ts.factory.createTypeReferenceNode(findExternalType(type, 'Types.', abi))
);
const variantTypeAlias = ts.factory.createTypeAliasDeclaration(
undefined,
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)],
variantTypeName,
undefined,
ts.factory.createUnionTypeNode(variantTypeNodes)
);

typeInterfaces.push(variantTypeAlias)
} else {
types = [field.type]
}

types.forEach((type) => {
const typeStruct = findAbiStruct(type, abi)

if (typeStruct) {
const interfaces = generateActionInterface(typeStruct, abi)

typeInterfaces.push(interfaces.actionInterface, ...interfaces.typeInterfaces)
}
})

return ts.factory.createPropertySignature(
undefined,
field.name,
Expand All @@ -71,11 +93,11 @@ export function generateActionInterface(struct, abi): { actionInterface: ts.Inte
export function generateActionsNamespace(abi: ABI.Def): ts.ModuleDeclaration {
const actionStructsWithFields = getActionFieldFromAbi(abi)

const typeInterfaces: ts.InterfaceDeclaration[] = []
const typeInterfaces: TypeInterfaceDeclaration[] = []

const interfaces = abi.actions.map((action) => {
const actionStruct = actionStructsWithFields.find(
(actionStructWithField) => actionStructWithField.structName === action.type
(actionStructWithField) => actionStructWithField.name === action.type
)

const interfaces = generateActionInterface(actionStruct, abi)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/contract/maps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type {ABI} from '@wharfkit/antelope'
import * as ts from 'typescript'
import {findAbiType} from './helpers'
import {findAbiType} from './finders'

export function generateTableMap(abi: ABI.Def): ts.VariableStatement {
// Map over tables to create the object properties
Expand Down
Loading

0 comments on commit 1b0f3e8

Please sign in to comment.