From ab1401be43010679f19ec2cbce0aa0b9db9b8682 Mon Sep 17 00:00:00 2001 From: Tom Meagher Date: Mon, 4 Nov 2024 17:47:14 -0500 Subject: [PATCH] refactor(types): abi --- scripts/docgen/render/apiFunction.ts | 28 +++++------ src/Abi.ts | 29 ++++++----- src/_test/Abi.test-d.ts | 74 ++++++++++++++++++++++++++++ src/_test/Abi.test.ts | 15 ++++-- src/internal/abi.ts | 11 +++++ 5 files changed, 127 insertions(+), 30 deletions(-) create mode 100644 src/_test/Abi.test-d.ts create mode 100644 src/internal/abi.ts diff --git a/scripts/docgen/render/apiFunction.ts b/scripts/docgen/render/apiFunction.ts index 0552d83a..b91f6e17 100644 --- a/scripts/docgen/render/apiFunction.ts +++ b/scripts/docgen/render/apiFunction.ts @@ -355,20 +355,20 @@ function resolveReturnTypeForOverloads(options: { overloads: string[] returnType: Data['returnType'] }) { - const { dataLookup, overloads, returnType } = options - - if (overloads.length && returnType) { - const overload = overloads.find( - (x) => dataLookup[x]?.returnType?.type !== returnType.type, - ) - if (overload) - return ( - dataLookup[overload]?.returnType?.type.replace( - /(<.*>)$/, // remove type params from type - '', - ) ?? returnType.type - ) - } + const { returnType } = options + + // if (overloads.length && returnType) { + // const overload = overloads.find( + // (x) => dataLookup[x]?.returnType?.type !== returnType.type, + // ) + // if (overload) + // return ( + // dataLookup[overload]?.returnType?.type.replace( + // /(<.*>)$/, // remove type params from type + // '', + // ) ?? returnType.type + // ) + // } return returnType?.type } diff --git a/src/Abi.ts b/src/Abi.ts index dce29798..e464df1d 100644 --- a/src/Abi.ts +++ b/src/Abi.ts @@ -1,10 +1,12 @@ import * as abitype from 'abitype' import type * as Errors from './Errors.js' +import * as internal from './internal/abi.js' import type * as AbiItem_internal from './internal/abiItem.js' /** Root type for an ABI. */ export type Abi = abitype.Abi +export function format(abi: abi): format.ReturnType /** * Formats an {@link ox#Abi.Abi} into a **Human Readable ABI**. * @@ -33,18 +35,21 @@ export type Abi = abitype.Abi * // ^? * * + * * ``` * * @param abi - The ABI to format. * @returns The formatted ABI. */ -export function format( - abi: abi | Abi | readonly unknown[], -): abitype.FormatAbi { +export function format(abi: Abi | readonly unknown[]): readonly string[] +export function format(abi: Abi | readonly unknown[]): readonly string[] { return abitype.formatAbi(abi) as never } export declare namespace format { + type ReturnType = + abitype.FormatAbi + type ErrorType = Errors.GlobalErrorType } @@ -52,6 +57,12 @@ format.parseError = (error: unknown) => /* v8 ignore next */ error as format.ErrorType +export function from( + abi: abi & + (abi extends readonly string[] + ? AbiItem_internal.Signatures + : unknown), +): from.ReturnType /** * Parses an arbitrary **JSON ABI** or **Human Readable ABI** into a typed {@link ox#Abi.Abi}. * @@ -129,14 +140,10 @@ format.parseError = (error: unknown) => * @param abi - The ABI to parse. * @returns The typed ABI. */ -export function from< - const abi extends Abi | readonly string[] | readonly unknown[], ->( - abi: (abi | Abi | readonly string[] | readonly unknown[]) & - (abi extends readonly string[] ? AbiItem_internal.Signatures : Abi), -): from.ReturnType { - if (typeof abi[0] === 'string') return abitype.parseAbi(abi as never) - return abi as never +export function from(abi: Abi | readonly string[]): Abi +export function from(abi: Abi | readonly string[]): Abi { + if (internal.isSignatures(abi)) return abitype.parseAbi(abi) + return abi } export declare namespace from { diff --git a/src/_test/Abi.test-d.ts b/src/_test/Abi.test-d.ts new file mode 100644 index 00000000..6de25d48 --- /dev/null +++ b/src/_test/Abi.test-d.ts @@ -0,0 +1,74 @@ +import { Abi } from 'ox' +import { describe, expectTypeOf, test } from 'vitest' + +describe('format', () => { + test('infers abi', () => { + const formatted = Abi.format(value) + expectTypeOf(formatted).toEqualTypeOf([ + 'function approve(address spender, uint256 amount) returns (bool)', + ] as const) + }) + + test('not narrowable', () => { + const abi = {} as Abi.Abi + const formatted = Abi.format(abi) + expectTypeOf(formatted).toEqualTypeOf() + }) + + const value = [ + { + type: 'function', + name: 'approve', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'spender', + type: 'address', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + outputs: [{ type: 'bool' }], + }, + ] as const +}) + +describe('from', () => { + test('infers abi', () => { + const abi = Abi.from(value) + expectTypeOf(abi).toEqualTypeOf(value) + }) + + test('from signatures', () => { + const abi = Abi.from([ + 'function approve(address spender, uint256 amount) returns (bool)', + ]) + expectTypeOf(abi).toEqualTypeOf(value) + }) + + test('not narrowable', () => { + const abi = Abi.from({} as Abi.Abi) + expectTypeOf(abi).toEqualTypeOf() + }) + + const value = [ + { + type: 'function', + name: 'approve', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'spender', + type: 'address', + }, + { + name: 'amount', + type: 'uint256', + }, + ], + outputs: [{ type: 'bool' }], + }, + ] as const +}) diff --git a/src/_test/Abi.test.ts b/src/_test/Abi.test.ts index 75a1ab2d..5b2e9386 100644 --- a/src/_test/Abi.test.ts +++ b/src/_test/Abi.test.ts @@ -1,5 +1,5 @@ import { Abi } from 'ox' -import { describe, expect, test } from 'vitest' +import { describe, expect, expectTypeOf, test } from 'vitest' describe('format', () => { test('default', () => { @@ -23,10 +23,15 @@ describe('format', () => { ]) const formatted = Abi.format(abi) expect(formatted).toMatchInlineSnapshot(` - [ - "function approve(address spender, uint256 amount) returns (bool)", - ] - `) + [ + "function approve(address spender, uint256 amount) returns (bool)", + ] + `) + expectTypeOf(formatted).toEqualTypeOf< + readonly [ + 'function approve(address spender, uint256 amount) returns (bool)', + ] + >() }) }) diff --git a/src/internal/abi.ts b/src/internal/abi.ts new file mode 100644 index 00000000..276b6148 --- /dev/null +++ b/src/internal/abi.ts @@ -0,0 +1,11 @@ +import type * as Abi from '../Abi.js' + +/** @internal */ +export function isSignatures( + value: Abi.Abi | readonly string[], +): value is readonly string[] { + for (const item of value) { + if (typeof item !== 'string') return false + } + return true +}