From 2b83dc6ad26bd20e3f0ff3991b8ac487bbfc61dc Mon Sep 17 00:00:00 2001 From: sinclairzx81 Date: Sun, 3 Sep 2023 23:47:17 +0900 Subject: [PATCH] Revision 0.31.9 (#570) --- src/typebox.ts | 16 ++++++-------- test/runtime/type/guard/enum.ts | 39 +++++++++++++++++++++++++++++++++ test/static/enum.ts | 32 ++++++++++++++++++++++++++- test/static/index.ts | 4 ++-- 4 files changed, 79 insertions(+), 12 deletions(-) diff --git a/src/typebox.ts b/src/typebox.ts index edca58f..4934b2b 100644 --- a/src/typebox.ts +++ b/src/typebox.ts @@ -342,7 +342,8 @@ export type TEnumValue = string | number export type TEnumKey = string export type TEnumToLiteralUnion = T extends TEnumValue ? TLiteral : never export type TEnumToLiteralTuple = UnionToTuple> -export type TEnum = Ensure>>> +export type TEnumToUnion>>> = R extends TLiteralString ? TNever : R // Note: Empty enum evaluates as TLiteralString +export type TEnum = Ensure> // -------------------------------------------------------------------------- // TExtends // -------------------------------------------------------------------------- @@ -2896,16 +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 { + public Enum>(item: T, options: SchemaOptions = {}): TEnum { + if (item === undefined) return this.Union([], 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)] // ensure distinct - // prettier-ignore - const anyOf = values2.map((value) => ValueGuard.IsString(value) - ? { [Kind]: 'Literal', type: 'string' as const, const: value } - : { [Kind]: 'Literal', type: 'number' as const, const: value } - ) - return this.Create({ ...options, [Kind]: 'Union', anyOf }) + const values2 = [...new Set(values1)] // distinct + const anyOf = values2.map((value) => Type.Literal(value)) + 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 9294dde..81a415a 100644 --- a/test/runtime/type/guard/enum.ts +++ b/test/runtime/type/guard/enum.ts @@ -3,6 +3,45 @@ import { Type } from '@sinclair/typebox' import { Assert } from '../../assert/index' describe('type/guard/TEnum', () => { + // ---------------------------------------------------------------- + // Options + // ---------------------------------------------------------------- + it('Should guard for Options 1', () => { + const T = Type.Enum({ x: 1 }, { extra: 'hello', $id: 'T' }) + Assert.IsEqual(T.extra, 'hello') + Assert.IsEqual(T.$id, 'T') + }) + it('Should guard for Options 2', () => { + enum E { + x, + } + const T = Type.Enum(E, { extra: 'hello', $id: 'T' }) + Assert.IsEqual(T.extra, 'hello') + Assert.IsEqual(T.$id, 'T') + }) + it('Should guard for Options 3', () => { + enum E {} + const T = Type.Enum(E, { extra: 'hello', $id: 'T' }) + Assert.IsEqual(T.extra, 'hello') + Assert.IsEqual(T.$id, 'T') + }) + it('Should guard for Options 4', () => { + const T = Type.Enum({}, { extra: 'hello', $id: 'T' }) + Assert.IsEqual(T.extra, 'hello') + Assert.IsEqual(T.$id, 'T') + }) + // ---------------------------------------------------------------- + // Empty + // ---------------------------------------------------------------- + it('Should guard for Empty 1', () => { + const T = Type.Enum({}) + Assert.IsTrue(TypeGuard.TNever(T)) + }) + it('Should guard for Empty 2', () => { + enum E {} + const T = Type.Enum(E) + Assert.IsTrue(TypeGuard.TNever(T)) + }) // ---------------------------------------------------------------- // Enum // ---------------------------------------------------------------- diff --git a/test/static/enum.ts b/test/static/enum.ts index 6d754e2..456e60e 100644 --- a/test/static/enum.ts +++ b/test/static/enum.ts @@ -1,7 +1,8 @@ import { Expect } from './assert' -import { Type, Static } from '@sinclair/typebox' +import { Type } from '@sinclair/typebox' { + // expect all variants enum E { A, B = 'hello', @@ -10,3 +11,32 @@ import { Type, Static } from '@sinclair/typebox' const T = Type.Enum(E) Expect(T).ToStatic() } +{ + // expect all variants + const T = Type.Enum({ + A: 1, + B: 2, + C: 3, + }) + Expect(T).ToStatic<1 | 2 | 3>() +} +{ + // expect variant overlap to reduce + const T = Type.Enum({ + A: 1, + B: 2, + C: 2, // overlap + }) + Expect(T).ToStatic<1 | 2>() +} +{ + // expect empty enum to be never + enum E {} + const T = Type.Enum(E) + Expect(T).ToStaticNever() +} +{ + // expect empty enum to be never + const T = Type.Enum({}) + Expect(T).ToStaticNever() +} diff --git a/test/static/index.ts b/test/static/index.ts index d1c757f..911f5ec 100644 --- a/test/static/index.ts +++ b/test/static/index.ts @@ -6,10 +6,10 @@ import './bigint' import './boolean' import './capitalize' import './composite' -import './date' import './constructor-parameters' import './constructor' -import './emum' +import './date' +import './enum' import './extract' import './exclude' import './function'