From fc0bb0e9f25d2e8cc40abf88944dd15299a65b39 Mon Sep 17 00:00:00 2001 From: sinclairzx81 Date: Wed, 6 Sep 2023 00:46:25 +0900 Subject: [PATCH] Revision 0.31.11 (#575) * Resolve for Single String Enum * Version * Update TEnum to accept TEnumRecord as Generic Argument * Use TEnum as Assert --- package-lock.json | 4 +-- package.json | 2 +- src/typebox.ts | 14 +++++----- test/runtime/type/guard/enum.ts | 47 ++++++++++++++++++++++++--------- test/static/enum.ts | 4 +-- 5 files changed, 47 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 12d0df7..db3a526 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.31.10", + "version": "0.31.11", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.31.10", + "version": "0.31.11", "license": "MIT", "devDependencies": { "@sinclair/hammer": "^0.17.1", diff --git a/package.json b/package.json index 3e0138c..e3d9b89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.31.10", + "version": "0.31.11", "description": "JSONSchema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/src/typebox.ts b/src/typebox.ts index f8f5b94..78539e4 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -340,10 +340,10 @@ export interface TDate extends TSchema, DateOptions { export type TEnumRecord = Record export type TEnumValue = string | number export type TEnumKey = string -export type TEnumToLiteralUnion = T extends TEnumValue ? TLiteral : never +export type TEnumToLiteralUnion = T extends TEnumValue ? TLiteral : never // Note: Empty enums infer as TLiteral export type TEnumToLiteralTuple = UnionToTuple> -export type TEnumToUnion>>> = R extends TLiteralString ? TNever : R // Note: Empty enum evaluates as TLiteralString -export type TEnum = Ensure> +export type TEnumToUnion = UnionType>> +export type TEnum = TEnumToUnion // -------------------------------------------------------------------------- // TExtends // -------------------------------------------------------------------------- @@ -2897,13 +2897,13 @@ export class JsonTypeBuilder extends TypeBuilder { return Type.Object(properties, options) as TComposite } /** `[Json]` Creates a Enum type */ - public Enum>(item: T, options: SchemaOptions = {}): TEnum { - if (ValueGuard.IsUndefined(item)) return this.Union([], options) as TEnum + public Enum>(item: T, options: SchemaOptions = {}): TEnum { + if (ValueGuard.IsUndefined(item)) return this.Never(options) as TEnum // prettier-ignore const values1 = Object.getOwnPropertyNames(item).filter((key) => isNaN(key as any)).map((key) => item[key]) as T[keyof T][] - const values2 = [...new Set(values1)] // distinct + const values2 = [...new Set(values1)] const anyOf = values2.map((value) => Type.Literal(value)) - return this.Union(anyOf, options) as TEnum + return this.Union(anyOf, options) as TEnum } /** `[Json]` Creates a Conditional type */ public Extends(left: L, right: R, trueType: T, falseType: U, options: SchemaOptions = {}): TExtends { diff --git a/test/runtime/type/guard/enum.ts b/test/runtime/type/guard/enum.ts index 81a415a..fd4ee99 100644 --- a/test/runtime/type/guard/enum.ts +++ b/test/runtime/type/guard/enum.ts @@ -42,10 +42,24 @@ describe('type/guard/TEnum', () => { const T = Type.Enum(E) Assert.IsTrue(TypeGuard.TNever(T)) }) + // ---------------------------------------------------------------- // Enum // ---------------------------------------------------------------- + it('Should guard for TEnum Enum 0', () => { + enum E {} + const T = Type.Enum(E) + Assert.IsTrue(TypeGuard.TNever(T)) + }) it('Should guard for TEnum Enum 1', () => { + enum E { + A, + } + const T = Type.Enum(E) + Assert.IsTrue(TypeGuard.TLiteral(T)) + Assert.IsEqual(T.const, E.A) + }) + it('Should guard for TEnum Enum 2', () => { enum E { A = 1, B = 2, @@ -53,11 +67,11 @@ describe('type/guard/TEnum', () => { } const T = Type.Enum(E) Assert.IsTrue(TypeGuard.TUnion(T)) - Assert.IsEqual(T.anyOf[0].const, 1) - Assert.IsEqual(T.anyOf[1].const, 2) - Assert.IsEqual(T.anyOf[2].const, 3) + Assert.IsEqual(T.anyOf[0].const, E.A) + Assert.IsEqual(T.anyOf[1].const, E.B) + Assert.IsEqual(T.anyOf[2].const, E.C) }) - it('Should guard for TEnum Enum 2', () => { + it('Should guard for TEnum Enum 3', () => { enum E { A = 'X', B = 'Y', @@ -65,11 +79,11 @@ describe('type/guard/TEnum', () => { } const T = Type.Enum(E) Assert.IsTrue(TypeGuard.TUnion(T)) - Assert.IsEqual(T.anyOf[0].const, 'X') - Assert.IsEqual(T.anyOf[1].const, 'Y') - Assert.IsEqual(T.anyOf[2].const, 'Z') + Assert.IsEqual(T.anyOf[0].const, E.A) + Assert.IsEqual(T.anyOf[1].const, E.B) + Assert.IsEqual(T.anyOf[2].const, E.C) }) - it('Should guard for TEnum Enum 3', () => { + it('Should guard for TEnum Enum 4', () => { enum E { A = 'X', B = 'Y', @@ -77,14 +91,23 @@ describe('type/guard/TEnum', () => { } const T = Type.Enum(E) Assert.IsTrue(TypeGuard.TUnion(T)) - Assert.IsEqual(T.anyOf[0].const, 'X') - Assert.IsEqual(T.anyOf[1].const, 'Y') + Assert.IsEqual(T.anyOf[0].const, E.A) + Assert.IsEqual(T.anyOf[1].const, E.B) Assert.IsEqual(T.anyOf.length, 2) }) // ---------------------------------------------------------------- // Object Literal // ---------------------------------------------------------------- + it('Should guard for TEnum Object Literal 0', () => { + const T = Type.Enum({}) + Assert.IsTrue(TypeGuard.TNever(T)) + }) it('Should guard for TEnum Object Literal 1', () => { + const T = Type.Enum({ A: 1 }) + Assert.IsTrue(TypeGuard.TLiteral(T)) + Assert.IsEqual(T.const, 1) + }) + it('Should guard for TEnum Object Literal 2', () => { const T = Type.Enum({ A: 1, B: 2, @@ -95,7 +118,7 @@ describe('type/guard/TEnum', () => { Assert.IsEqual(T.anyOf[1].const, 2) Assert.IsEqual(T.anyOf[2].const, 3) }) - it('Should guard for TEnum Object Literal 2', () => { + it('Should guard for TEnum Object Literal 3', () => { const T = Type.Enum({ A: 'X', B: 'Y', @@ -106,7 +129,7 @@ describe('type/guard/TEnum', () => { Assert.IsEqual(T.anyOf[1].const, 'Y') Assert.IsEqual(T.anyOf[2].const, 'Z') }) - it('Should guard for TEnum Object Literal 3', () => { + it('Should guard for TEnum Object Literal 4', () => { const T = Type.Enum({ A: 'X', B: 'Y', diff --git a/test/static/enum.ts b/test/static/enum.ts index 456e60e..9818acf 100644 --- a/test/static/enum.ts +++ b/test/static/enum.ts @@ -30,10 +30,10 @@ import { Type } from '@sinclair/typebox' Expect(T).ToStatic<1 | 2>() } { - // expect empty enum to be never + // expect empty enum to be string (as empty enums T[keyof T] evaluates as string) enum E {} const T = Type.Enum(E) - Expect(T).ToStaticNever() + Expect(T).ToStatic() } { // expect empty enum to be never