From dcf48f3232a93ea7d8c8224a2ba0a8789e9a8cf7 Mon Sep 17 00:00:00 2001 From: sinclairzx81 Date: Sat, 13 Jan 2024 00:01:44 +0900 Subject: [PATCH] Revision 0.32.9 (#731) * Optimize Composite * Set and Composite Tests * Version --- example/typedef/typedef.ts | 2 +- package-lock.json | 4 +- package.json | 2 +- src/index.ts | 33 ++++- src/type/composite/composite.ts | 117 ++++++++++++----- src/type/helpers/helpers.ts | 6 +- src/type/keyof/keyof-property-keys.ts | 20 +-- src/type/sets/set.ts | 50 +++++--- src/type/type/json.ts | 4 +- test/runtime/compiler-ajv/composite.ts | 17 +++ test/runtime/compiler/composite.ts | 17 +++ test/runtime/type/guard/composite.ts | 111 +++++++++++++++- test/runtime/type/index.ts | 1 + test/runtime/type/sets/index.ts | 1 + test/runtime/type/sets/sets.ts | 167 +++++++++++++++++++++++++ test/static/composite.ts | 59 ++++++++- 16 files changed, 536 insertions(+), 75 deletions(-) create mode 100644 test/runtime/type/sets/index.ts create mode 100644 test/runtime/type/sets/sets.ts diff --git a/example/typedef/typedef.ts b/example/typedef/typedef.ts index 7a86b77..6682e5e 100644 --- a/example/typedef/typedef.ts +++ b/example/typedef/typedef.ts @@ -56,7 +56,7 @@ export interface TBoolean extends Types.TSchema { // -------------------------------------------------------------------------- export type InferUnion = T extends [infer L extends TStruct, ...infer R extends TStruct[]] - ? Types.Evaluate<{ [_ in D]: Index } & Types.Static> | InferUnion>> + ? Types.Evaluate<{ [_ in D]: Index } & Types.Static> | InferUnion>> : never export interface TUnion extends Types.TSchema { diff --git a/package-lock.json b/package-lock.json index 2fabd94..79f2607 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@sinclair/typebox", - "version": "0.32.8", + "version": "0.32.9", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@sinclair/typebox", - "version": "0.32.8", + "version": "0.32.9", "license": "MIT", "devDependencies": { "@arethetypeswrong/cli": "^0.13.2", diff --git a/package.json b/package.json index b291d5e..6a27860 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@sinclair/typebox", - "version": "0.32.8", + "version": "0.32.9", "description": "Json Schema Type Builder with Static Type Resolution for TypeScript", "keywords": [ "typescript", diff --git a/src/index.ts b/src/index.ts index b1b6ea7..cfcd03a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -34,9 +34,37 @@ export { PatternBoolean, PatternBooleanExact, PatternNumber, PatternNumberExact, export { TypeRegistry, FormatRegistry } from './type/registry/index' export { TypeGuard, ValueGuard } from './type/guard/index' export { CloneType, CloneRest } from './type/clone/type' +// ------------------------------------------------------------------ +// Error +// ------------------------------------------------------------------ export { TypeBoxError } from './type/error/index' // ------------------------------------------------------------------ -// Type +// Sets +// ------------------------------------------------------------------ +export { + SetComplement, + SetDistinct, + SetIncludes, + SetIntersect, + SetIntersectMany, + SetIsSubset, + SetUnion, + SetUnionMany, + type TSetComplement, + type TSetDistinct, + type TSetIncludes, + type TSetIntersect, + type TSetIntersectMany, + type TSetIsSubset, + type TSetUnion, + type TSetUnionMany, +} from './type/sets/index' +// ------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------ +export { Increment, type TIncrement, type Assert, type AssertType, type AssertRest, type AssertProperties, type Ensure, type Evaluate, type TupleToIntersect, type TupleToUnion, type UnionToTuple } from './type/helpers/index' +// ------------------------------------------------------------------ +// Types // ------------------------------------------------------------------ export { Any, type TAny } from './type/any/index' export { Array, type TArray, type ArrayOptions } from './type/array/index' @@ -55,7 +83,6 @@ export { Exclude, type TExclude, type TExcludeFromMappedResult } from './type/ex export { Extends, ExtendsCheck, ExtendsResult, ExtendsUndefinedCheck, type TExtends, type ExtendsFromMappedResult, type ExtendsFromMappedKey } from './type/extends/index' export { Extract, type TExtract, type TExtractFromMappedResult } from './type/extract/index' export { Function, type TFunction } from './type/function/index' -export { Increment, type Assert, type AssertType, type AssertRest, type AssertProperties, type Ensure, type Evaluate, type TupleToIntersect, type TupleToUnion, type UnionToTuple } from './type/helpers/index' export { Index, IndexPropertyKeys, @@ -102,7 +129,7 @@ export { type TSchema, type TKind, type SchemaOptions, type TAnySchema } from '. export { type Static, type StaticDecode, type StaticEncode, type TDecodeType, type TDecodeRest, type TDecodeProperties } from './type/static/index' export { Strict } from './type/strict/index' export { String, type TString, type StringOptions, type StringFormatOption, type StringContentEncodingOption } from './type/string/index' -export { Symbol, type TSymbol, type TSymbolValue as SymbolValue } from './type/symbol/index' +export { Symbol, type TSymbol, type TSymbolValue } from './type/symbol/index' export { TemplateLiteral, TemplateLiteralSyntax, diff --git a/src/type/composite/composite.ts b/src/type/composite/composite.ts index b4dc847..b37ccb6 100644 --- a/src/type/composite/composite.ts +++ b/src/type/composite/composite.ts @@ -27,44 +27,95 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import type { TSchema } from '../schema/index' -import type { UnionToTuple, Assert, Ensure, Evaluate } from '../helpers/index' +import type { Evaluate } from '../helpers/index' +import { IntersectEvaluated, type TIntersectEvaluated } from '../intersect/index' +import { IndexFromPropertyKeys, type TIndexFromPropertyKeys } from '../indexed/index' +import { KeyOfPropertyKeys, type TKeyOfPropertyKeys } from '../keyof/index' +import { type TNever } from '../never/index' import { Object, type TObject, type TProperties, type ObjectOptions } from '../object/index' -import { Intersect, type TIntersect } from '../intersect/index' -import { Index, type TIndex } from '../indexed/index' -import { KeyOfPropertyKeys } from '../keyof/index' +import { SetDistinct, TSetDistinct } from '../sets/index' // ------------------------------------------------------------------ -// TCompositeKeys +// TypeGuard +// ------------------------------------------------------------------ +import { IsNever } from '../guard/type' +// ------------------------------------------------------------------ +// CompositeKeys // ------------------------------------------------------------------ // prettier-ignore -type TCompositeKeys = - T extends [infer L extends TObject, ...infer R extends TObject[]] - ? TCompositeKeys +type TCompositeKeys = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? TCompositeKeys]>> : Acc -// ------------------------------------------------------------------ -// TCompositeIndex -// ------------------------------------------------------------------ +) // prettier-ignore -type TCompositeIndex, K extends string[], Acc extends TProperties = {}> = - K extends [infer L extends string, ...infer R extends string[]] - ? TCompositeIndex }> - : Acc -// prettier-ignore -type TCompositeReduce = UnionToTuple> extends infer K - ? Evaluate, Assert>> - : {} // ^ indexed via intersection of T -// ------------------------------------------------------------------ -// TComposite -// ------------------------------------------------------------------ -// prettier-ignore -export type TComposite = TIntersect extends TIntersect - ? Ensure>> - : Ensure> - -/** `[Json]` Creates a Composite object type */ -export function Composite(T: [...T], options?: ObjectOptions): TComposite { - const intersect: TSchema = Intersect(T, {}) - const keys = KeyOfPropertyKeys(intersect) as string[] - const properties = keys.reduce((acc, key) => ({ ...acc, [key]: Index(intersect, [key]) }), {} as TProperties) - return Object(properties, options) as TComposite +function CompositeKeys(T: [...T]): TCompositeKeys { + return T.reduce((Acc, L) => { + return SetDistinct([...Acc, ...KeyOfPropertyKeys(L)]) as never + }, []) as never +} +// ------------------------------------------------------------------ +// FilterNever +// ------------------------------------------------------------------ +// prettier-ignore +type TFilterNever = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? L extends TNever + ? Acc + : TFilterNever + : Acc +) +// prettier-ignore +function FilterNever(T: [...T]): TFilterNever { + return T.filter(L => !IsNever(L)) as never +} +// ------------------------------------------------------------------ +// CompositeProperty +// ------------------------------------------------------------------ +// prettier-ignore +type TCompositeProperty = ( + T extends [infer L extends TSchema, ...infer R extends TSchema[]] + ? TCompositeProperty]>> + : Acc +) +// prettier-ignore +function CompositeProperty(T: [...T], K: K): TCompositeProperty { + return T.reduce((Acc, L) => { + return FilterNever([...Acc, ...IndexFromPropertyKeys(L, [K])]) + }, []) as never +} +// ------------------------------------------------------------------ +// CompositeProperties +// ------------------------------------------------------------------ +// prettier-ignore +type TCompositeProperties = ( + K extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] + ? TCompositeProperties> }> + : Acc +) +// prettier-ignore +function CompositeProperties(T: [...T], K: [...K]): TCompositeProperties { + return K.reduce((Acc, L) => { + return { ...Acc, [L]: IntersectEvaluated(CompositeProperty(T, L)) } + }, {}) as never +} +// ------------------------------------------------------------------ +// Composite +// ------------------------------------------------------------------ +// prettier-ignore +type TCompositeEvaluate< + T extends TSchema[], + K extends PropertyKey[] = TCompositeKeys, + P extends TProperties = Evaluate>, + R extends TObject = TObject

+> = R +// prettier-ignore +export type TComposite = TCompositeEvaluate + +// prettier-ignore +export function Composite(T: [...T], options: ObjectOptions = {}): TComposite { + const K = CompositeKeys(T) + const P = CompositeProperties(T, K) + const R = Object(P, options) + return R as TComposite } diff --git a/src/type/helpers/helpers.ts b/src/type/helpers/helpers.ts index c241ef3..807e10c 100644 --- a/src/type/helpers/helpers.ts +++ b/src/type/helpers/helpers.ts @@ -56,10 +56,10 @@ type IncrementStep = T extends IncrementBase['m'] : `${IncrementTake}${R}` : never type IncrementReverse = T extends `${infer L}${infer R}` ? `${IncrementReverse}${L}` : T -export type Increment = IncrementReverse>> +export type TIncrement = IncrementReverse>> /** Increments the given string value + 1 */ -export function Increment(T: T): Increment { - return (parseInt(T) + 1).toString() as Increment +export function Increment(T: T): TIncrement { + return (parseInt(T) + 1).toString() as TIncrement } // ------------------------------------------------------------------ // Helper: Type Asserts diff --git a/src/type/keyof/keyof-property-keys.ts b/src/type/keyof/keyof-property-keys.ts index 34a1695..8f7c17e 100644 --- a/src/type/keyof/keyof-property-keys.ts +++ b/src/type/keyof/keyof-property-keys.ts @@ -27,7 +27,7 @@ THE SOFTWARE. ---------------------------------------------------------------------------*/ import type { TSchema } from '../schema/index' -import { type ZeroString, type UnionToTuple, Increment } from '../helpers/index' +import { type ZeroString, type UnionToTuple, type TIncrement } from '../helpers/index' import type { TRecursive } from '../recursive/index' import type { TIntersect } from '../intersect/index' import type { TUnion } from '../union/index' @@ -59,31 +59,31 @@ function FromRest(T: [...T]): TFromRest { // FromIntersect // ------------------------------------------------------------------ // prettier-ignore -type FromIntersect< +type TFromIntersect< T extends TSchema[], C extends PropertyKey[][] = TFromRest, R extends PropertyKey[] = TSetUnionMany > = R // prettier-ignore -function FromIntersect(T: [...T]): FromIntersect { +function FromIntersect(T: [...T]): TFromIntersect { const C = FromRest(T) as PropertyKey[][] const R = SetUnionMany(C) - return R as FromIntersect + return R as TFromIntersect } // ------------------------------------------------------------------ // FromUnion // ------------------------------------------------------------------ // prettier-ignore -type FromUnion< +type TFromUnion< T extends TSchema[], C extends PropertyKey[][] = TFromRest, R extends PropertyKey[] = TSetIntersectMany > = R // prettier-ignore -function FromUnion(T: [...T]): FromUnion { +function FromUnion(T: [...T]): TFromUnion { const C = FromRest(T) as PropertyKey[][] const R = SetIntersectMany(C) - return R as FromUnion + return R as TFromUnion } // ------------------------------------------------------------------ // FromTuple @@ -91,7 +91,7 @@ function FromUnion(T: [...T]): FromUnion { // prettier-ignore type TFromTuple = T extends [infer _ extends TSchema, ...infer R extends TSchema[]] - ? TFromTuple, [...Acc, I]> + ? TFromTuple, [...Acc, I]> : Acc // prettier-ignore function FromTuple(T: [...T]): TFromTuple { @@ -142,8 +142,8 @@ function FromPatternProperties(patternProperties: Record): // prettier-ignore export type TKeyOfPropertyKeys = ( T extends TRecursive ? TKeyOfPropertyKeys : - T extends TIntersect ? FromIntersect : - T extends TUnion ? FromUnion : + T extends TIntersect ? TFromIntersect : + T extends TUnion ? TFromUnion : T extends TTuple ? TFromTuple : T extends TArray ? TFromArray : T extends TObject ? TFromProperties : diff --git a/src/type/sets/set.ts b/src/type/sets/set.ts index f2456a7..70b1e02 100644 --- a/src/type/sets/set.ts +++ b/src/type/sets/set.ts @@ -37,7 +37,7 @@ export type TSetIncludes = ( : TSetIncludes : false ) -/** Returns true if element S is in the set of T */ +/** Returns true if element right is in the set of left */ // prettier-ignore export function SetIncludes(T: [...T], S: S): TSetIncludes { return T.includes(S) as TSetIncludes @@ -46,16 +46,16 @@ export function SetIncludes(T: [ // SetIsSubset // ------------------------------------------------------------------ // prettier-ignore -export type SetIsSubset = ( +export type TSetIsSubset = ( T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] ? TSetIncludes extends true - ? SetIsSubset + ? TSetIsSubset : false : true ) -/** Returns true if T is a subset of S */ -export function SetIsSubset(T: [...T], S: [...S]): SetIsSubset { - return T.every((L) => SetIncludes(S, L)) as SetIsSubset +/** Returns true if left is a subset of right */ +export function SetIsSubset(T: [...T], S: [...S]): TSetIsSubset { + return T.every((L) => SetIncludes(S, L)) as TSetIsSubset } // ------------------------------------------------------------------ // SetDistinct @@ -78,7 +78,7 @@ export function SetDistinct(T: [...T]): TSetDistinct export type TSetIntersect = ( T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] ? TSetIncludes extends true - ? TSetIntersect + ? TSetIntersect : TSetIntersect : Acc ) @@ -90,14 +90,12 @@ export function SetIntersect(T // SetUnion // ------------------------------------------------------------------ // prettier-ignore -export type TSetUnion = ( - T extends [infer L extends PropertyKey, ...infer R extends PropertyKey[]] - ? TSetUnion - : Acc +export type TSetUnion = ( + [...T, ...S] ) /** Returns the Union of the given sets */ export function SetUnion(T: [...T], S: [...S]): TSetUnion { - return [...T, ...S] as never + return [...T, ...S] } // ------------------------------------------------------------------ // SetComplement @@ -119,17 +117,35 @@ export function SetComplement( // SetIntersectMany // ------------------------------------------------------------------ // prettier-ignore -export type TSetIntersectMany = ( - T extends [infer L extends PropertyKey[]] ? L : +type TSetIntersectManyResolve = ( T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]] - ? TSetIntersectMany> + ? TSetIntersectManyResolve> : Acc ) -/** Returns the Intersect of multiple sets */ +// prettier-ignore +function SetIntersectManyResolve(T: [...T], Init: Acc): TSetIntersectManyResolve { + return T.reduce((Acc: PropertyKey[], L: PropertyKey[]) => { + return SetIntersect(Acc, L) + }, Init) as never +} +// prettier-ignore +export type TSetIntersectMany = ( + T extends [infer L extends PropertyKey[]] + ? L + // Use left to initialize the accumulator for resolve + : T extends [infer L extends PropertyKey[], ...infer R extends PropertyKey[][]] + ? TSetIntersectManyResolve + : [] +) // prettier-ignore export function SetIntersectMany(T: [...T]): TSetIntersectMany { return ( - T.length === 1 ? T[0] : T.reduce((Acc, L) => [...SetIntersect(Acc, L)], []) + T.length === 1 + ? T[0] + // Use left to initialize the accumulator for resolve + : T.length > 1 + ? SetIntersectManyResolve(T.slice(1), T[0]) + : [] ) as TSetIntersectMany } // ------------------------------------------------------------------ diff --git a/src/type/type/json.ts b/src/type/type/json.ts index 145d93e..772ff0e 100644 --- a/src/type/type/json.ts +++ b/src/type/type/json.ts @@ -130,8 +130,8 @@ export class JsonTypeBuilder { return Capitalize(schema, options) } /** `[Json]` Creates a Composite object type */ - public Composite(objects: [...T], options?: ObjectOptions): TComposite { - return Composite(objects, options) as any // (error) TS 5.4.0-dev - review TComposite implementation + public Composite(schemas: [...T], options?: ObjectOptions): TComposite { + return Composite(schemas, options) // (error) TS 5.4.0-dev - review TComposite implementation } /** `[JavaScript]` Creates a readonly const type from the given value. */ public Const(value: T, options: SchemaOptions = {}): TConst { diff --git a/test/runtime/compiler-ajv/composite.ts b/test/runtime/compiler-ajv/composite.ts index 41e8a8a..3d1d41a 100644 --- a/test/runtime/compiler-ajv/composite.ts +++ b/test/runtime/compiler-ajv/composite.ts @@ -91,4 +91,21 @@ describe('compiler-ajv/Composite', () => { const B = Type.Composite([T]) Assert.IsEqual(A, B) }) + // prettier-ignore + it('Should composite intersection', () => { + const T = Type.Composite([ + Type.Intersect([ + Type.Object({ x: Type.Number() }) + ]), + Type.Intersect([ + Type.Object({ y: Type.Number() }) + ]), + Type.Intersect([ + Type.Object({ z: Type.Number() }) + ]), + ]) + Ok(T, { x: 1, y: 2, z: 3 }) + Fail(T, { x: 1, y: 2, z: '3' }) + Fail(T, { x: 1, y: 2 }) + }) }) diff --git a/test/runtime/compiler/composite.ts b/test/runtime/compiler/composite.ts index 3186c83..960a1d7 100644 --- a/test/runtime/compiler/composite.ts +++ b/test/runtime/compiler/composite.ts @@ -81,4 +81,21 @@ describe('compiler/Composite', () => { Fail(T, { x: { x: '1' }, y: { x: '' } }) Fail(T, { x: { x: 1 }, y: { x: 1 } }) }) + // prettier-ignore + it('Should composite intersection', () => { + const T = Type.Composite([ + Type.Intersect([ + Type.Object({ x: Type.Number() }) + ]), + Type.Intersect([ + Type.Object({ y: Type.Number() }) + ]), + Type.Intersect([ + Type.Object({ z: Type.Number() }) + ]), + ]) + Ok(T, { x: 1, y: 2, z: 3 }) + Fail(T, { x: 1, y: 2, z: '3' }) + Fail(T, { x: 1, y: 2 }) + }) }) diff --git a/test/runtime/type/guard/composite.ts b/test/runtime/type/guard/composite.ts index e8a3f21..d3e9557 100644 --- a/test/runtime/type/guard/composite.ts +++ b/test/runtime/type/guard/composite.ts @@ -36,8 +36,8 @@ describe('type/guard/TComposite', () => { Assert.IsTrue(TypeGuard.IsOptional(T.properties.x)) Assert.IsEqual(T.required, undefined) }) + // prettier-ignore it('Should produce required property if some composited properties are not optional', () => { - // prettier-ignore const T = Type.Composite([ Type.Object({ x: Type.Optional(Type.Number()) }), Type.Object({ x: Type.Number() }) @@ -45,12 +45,119 @@ describe('type/guard/TComposite', () => { Assert.IsFalse(TypeGuard.IsOptional(T.properties.x)) Assert.IsTrue(T.required!.includes('x')) }) + // prettier-ignore it('Should preserve single optional property', () => { - // prettier-ignore const T = Type.Composite([ Type.Object({ x: Type.Optional(Type.Number()) }), ]) Assert.IsTrue(TypeGuard.IsOptional(T.properties.x)) Assert.IsEqual(T.required, undefined) }) + // ---------------------------------------------------------------- + // Intersect + // ---------------------------------------------------------------- + // prettier-ignore + it('Should composite Intersect 1', () => { + const T = Type.Composite([ + Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number() }), + ]), + Type.Intersect([ + Type.Object({ z: Type.Number() }), + ]) + ]) + Assert.IsEqual(T, Type.Object({ + x: Type.Number(), + y: Type.Number(), + z: Type.Number() + })) + }) + // prettier-ignore + it('Should composite Intersect 2', () => { + const T = Type.Composite([ + Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ x: Type.Number() }), + ]), + Type.Intersect([ + Type.Object({ x: Type.Number() }), + ]) + ]) + Assert.IsEqual(T, Type.Object({ + x: Type.Intersect([Type.Intersect([Type.Number(), Type.Number()]), Type.Number()]) + })) + }) + // prettier-ignore + it('Should composite Intersect 3', () => { + const T = Type.Composite([ + Type.Number(), + Type.Boolean() + ]) + Assert.IsEqual(T, Type.Object({})) + }) + // prettier-ignore + it('Should composite Intersect 4', () => { + const T = Type.Composite([ + Type.Number(), + Type.Boolean(), + Type.Object({ x: Type.String() }) + ]) + Assert.IsEqual(T, Type.Object({ + x: Type.String() + })) + }) + // prettier-ignore + it('Should composite Intersect 5', () => { + const T = Type.Composite([ + Type.Object({ x: Type.Optional(Type.String()) }), + Type.Object({ x: Type.String() }) + ]) + Assert.IsEqual(T, Type.Object({ + x: Type.Intersect([Type.String(), Type.String()]) + })) + }) + // prettier-ignore + it('Should composite Intersect 6', () => { + const T = Type.Composite([ + Type.Object({ x: Type.Optional(Type.String()) }), + Type.Object({ x: Type.Optional(Type.String()) }) + ]) + Assert.IsEqual(T, Type.Object({ + x: Type.Optional(Type.Intersect([Type.String(), Type.String()])) + })) + }) + // ---------------------------------------------------------------- + // Union + // ---------------------------------------------------------------- + // prettier-ignore + it('Should composite Union 1', () => { + const T = Type.Composite([ + Type.Union([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number() }), + ]), + Type.Union([ + Type.Object({ z: Type.Number() }), + ]) + ]) + Assert.IsEqual(T, Type.Object({ + z: Type.Intersect([Type.Union([Type.Never(), Type.Never()]), Type.Number()]) + })) + }) + // prettier-ignore + it('Should composite Union 2', () => { + const T = Type.Composite([ + Type.Union([ + Type.Object({ x: Type.Number() }), + Type.Object({ x: Type.Number() }), + ]), + Type.Union([ + Type.Object({ x: Type.Number() }), + ]) + ]) + Assert.IsEqual(T, Type.Object({ + x: Type.Intersect([Type.Union([Type.Number(), Type.Number()]), Type.Number()]) + })) + }) }) diff --git a/test/runtime/type/index.ts b/test/runtime/type/index.ts index f5cfcd7..d055284 100644 --- a/test/runtime/type/index.ts +++ b/test/runtime/type/index.ts @@ -4,5 +4,6 @@ import './guard/index' import './intrinsic/index' import './normalize/index' import './registry/index' +import './sets/index' import './template-literal/index' import './value/index' diff --git a/test/runtime/type/sets/index.ts b/test/runtime/type/sets/index.ts new file mode 100644 index 0000000..2b35257 --- /dev/null +++ b/test/runtime/type/sets/index.ts @@ -0,0 +1 @@ +import './sets' diff --git a/test/runtime/type/sets/sets.ts b/test/runtime/type/sets/sets.ts new file mode 100644 index 0000000..6bfef3c --- /dev/null +++ b/test/runtime/type/sets/sets.ts @@ -0,0 +1,167 @@ +import * as Type from '@sinclair/typebox' +import { Assert } from '../../assert' + +describe('type/sets', () => { + // ---------------------------------------------------------------- + // Distinct + // ---------------------------------------------------------------- + it('Should Distinct', () => { + const R = Type.SetDistinct([1, 1, 2, 2]) + Assert.IsEqual(R, [1, 2]) + }) + // ---------------------------------------------------------------- + // Includes + // ---------------------------------------------------------------- + it('Should Includes 1', () => { + const R = Type.SetIncludes([1, 2, 3, 4], 1) + Assert.IsTrue(R) + }) + it('Should Includes 2', () => { + const R = Type.SetIncludes([1, 2, 3, 4], 7) + Assert.IsFalse(R) + }) + // ---------------------------------------------------------------- + // IsSubset + // ---------------------------------------------------------------- + it('Should IsSubset 1', () => { + const R = Type.SetIsSubset([1, 2], [1, 2]) + Assert.IsTrue(R) + }) + it('Should IsSubset 2', () => { + const R = Type.SetIsSubset([1, 2], [1, 2, 3]) + Assert.IsTrue(R) + }) + it('Should IsSubset 3', () => { + const R = Type.SetIsSubset([1, 2], [1]) + Assert.IsFalse(R) + }) + // ---------------------------------------------------------------- + // Intersect + // ---------------------------------------------------------------- + it('Should Intersect 1', () => { + const R = Type.SetIntersect([1, 2], [1, 2]) + Assert.IsEqual(R, [1, 2]) + }) + it('Should Intersect 2', () => { + const R = Type.SetIntersect([1], [1, 2]) + Assert.IsEqual(R, [1]) + }) + it('Should Intersect 3', () => { + const R = Type.SetIntersect([1, 2], [1]) + Assert.IsEqual(R, [1]) + }) + it('Should Intersect 4', () => { + const R = Type.SetIntersect([], [1]) + Assert.IsEqual(R, []) + }) + it('Should Intersect 5', () => { + const R = Type.SetIntersect([1], []) + Assert.IsEqual(R, []) + }) + it('Should Intersect 6', () => { + const R = Type.SetIntersect([1], [2]) + Assert.IsEqual(R, []) + }) + // ---------------------------------------------------------------- + // Union + // ---------------------------------------------------------------- + it('Should Union 1', () => { + const R = Type.SetUnion([1, 2], [1, 2]) + Assert.IsEqual(R, [1, 2, 1, 2]) + }) + it('Should Union 2', () => { + const R = Type.SetUnion([1], [1, 2]) + Assert.IsEqual(R, [1, 1, 2]) + }) + it('Should Union 3', () => { + const R = Type.SetUnion([1, 2], [1]) + Assert.IsEqual(R, [1, 2, 1]) + }) + it('Should Union 4', () => { + const R = Type.SetUnion([], [1]) + Assert.IsEqual(R, [1]) + }) + it('Should Union 5', () => { + const R = Type.SetUnion([1], []) + Assert.IsEqual(R, [1]) + }) + // ---------------------------------------------------------------- + // Complement + // ---------------------------------------------------------------- + it('Should Complement 1', () => { + const R = Type.SetComplement([1, 2, 3, 4], [2, 3]) + Assert.IsEqual(R, [1, 4]) + }) + it('Should Complement 2', () => { + const R = Type.SetComplement([2, 3], [1, 2, 3, 4]) + Assert.IsEqual(R, []) + }) + // ---------------------------------------------------------------- + // IntersectMany + // ---------------------------------------------------------------- + it('Should IntersectMany 1', () => { + const R = Type.SetIntersectMany([[1, 2, 3], [1, 2], [1]] as const) + Assert.IsEqual(R, [1]) + }) + it('Should IntersectMany 2', () => { + const R = Type.SetIntersectMany([[1], [1, 2], [1, 2, 3]] as const) + Assert.IsEqual(R, [1]) + }) + it('Should IntersectMany 3', () => { + const R = Type.SetIntersectMany([ + [1, 2], + [1, 2], + ] as const) + Assert.IsEqual(R, [1, 2]) + }) + it('Should IntersectMany 4', () => { + const R = Type.SetIntersectMany([[1], [2]] as const) + Assert.IsEqual(R, []) + }) + it('Should IntersectMany 5', () => { + const R = Type.SetIntersectMany([[1], []] as const) + Assert.IsEqual(R, []) + }) + it('Should IntersectMany 6', () => { + const R = Type.SetIntersectMany([[], [1]] as const) + Assert.IsEqual(R, []) + }) + it('Should IntersectMany 7', () => { + const R = Type.SetIntersectMany([[1], [1], [1], [1], []] as const) + Assert.IsEqual(R, []) + }) + it('Should IntersectMany 8', () => { + const R = Type.SetIntersectMany([[], [1], [1], [1], [1]] as const) + Assert.IsEqual(R, []) + }) + // ---------------------------------------------------------------- + // UnionMany + // ---------------------------------------------------------------- + it('Should UnionMany 1', () => { + const R = Type.SetUnionMany([[1, 2, 3], [1, 2], [1]] as const) + Assert.IsEqual(R, [1, 2, 3, 1, 2, 1]) + }) + it('Should UnionMany 2', () => { + const R = Type.SetUnionMany([[1], [1, 2], [1, 2, 3]] as const) + Assert.IsEqual(R, [1, 1, 2, 1, 2, 3]) + }) + it('Should UnionMany 3', () => { + const R = Type.SetUnionMany([ + [1, 2], + [1, 2], + ] as const) + Assert.IsEqual(R, [1, 2, 1, 2]) + }) + it('Should UnionMany 4', () => { + const R = Type.SetUnionMany([[1], [2]] as const) + Assert.IsEqual(R, [1, 2]) + }) + it('Should UnionMany 5', () => { + const R = Type.SetUnionMany([[1], []] as const) + Assert.IsEqual(R, [1]) + }) + it('Should UnionMany 6', () => { + const R = Type.SetUnionMany([[], [1]] as const) + Assert.IsEqual(R, [1]) + }) +}) diff --git a/test/static/composite.ts b/test/static/composite.ts index 7075161..e2b440f 100644 --- a/test/static/composite.ts +++ b/test/static/composite.ts @@ -1,5 +1,5 @@ import { Expect } from './assert' -import { Type, TObject, TIntersect, TNumber, TBoolean } from '@sinclair/typebox' +import { Type, TOptional, TObject, TIntersect, TNumber, TBoolean } from '@sinclair/typebox' // ---------------------------------------------------------------------------- // Overlapping - Non Varying @@ -127,3 +127,60 @@ import { Type, TObject, TIntersect, TNumber, TBoolean } from '@sinclair/typebox' Type.Object({ x: Type.Boolean() }) ]) } +// ------------------------------------------------------------------ +// Intersect +// ------------------------------------------------------------------ +// prettier-ignore +{ + const T: TObject<{ + x: TNumber; + y: TNumber; + z: TNumber; + }> = Type.Composite([ + Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number() }), + ]), + Type.Intersect([ + Type.Object({ z: Type.Number() }) + ]) + ]) +} +// prettier-ignore +{ + const T: TObject<{ + x: TIntersect<[TNumber, TNumber]>; + y: TIntersect<[TNumber, TNumber]>; + }> = Type.Composite([ + Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number() }), + ]), + Type.Intersect([ + Type.Object({ x: Type.Number() }), + Type.Object({ y: Type.Number() }), + ]) + ]) +} +// prettier-ignore +{ + const T: TObject<{ + x: TIntersect<[TNumber, TNumber]>; + }> = Type.Composite([ + Type.Intersect([ + Type.Object({ x: Type.Optional(Type.Number()) }), + Type.Object({ x: Type.Number() }), + ]) + ]) +} +// prettier-ignore +{ + const T: TObject<{ + x: TOptional>; + }> = Type.Composite([ + Type.Intersect([ + Type.Object({ x: Type.Optional(Type.Number()) }), + Type.Object({ x: Type.Optional(Type.Number()) }), + ]) + ]) +}