From c98025ee409905a89bd11c1da15befae4203390a Mon Sep 17 00:00:00 2001 From: Alisue Date: Wed, 31 Jul 2024 02:50:31 +0900 Subject: [PATCH 01/22] :boom: Remove deprecated features --- __snapshots__/is_test.ts.snap | 389 ++++++++++++++-------------------- is.ts | 212 +----------------- is_bench.ts | 124 ----------- is_test.ts | 338 ----------------------------- 4 files changed, 158 insertions(+), 905 deletions(-) diff --git a/__snapshots__/is_test.ts.snap b/__snapshots__/is_test.ts.snap index 9da812f..c46aab9 100644 --- a/__snapshots__/is_test.ts.snap +++ b/__snapshots__/is_test.ts.snap @@ -1,228 +1,105 @@ export const snapshot = {}; -snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; +snapshot[`isObjectOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isString, + c: isBoolean +})" +`; -snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(isNumber)"`; +snapshot[`isObjectOf > returns properly named function 2`] = `"isObjectOf({a: a})"`; -snapshot[`isUnwrapOptionalOf > returns properly named function 1`] = `"isNumber"`; +snapshot[`isObjectOf > returns properly named function 3`] = ` +"isObjectOf({ + a: isObjectOf({ + b: isObjectOf({c: isBoolean}) + }) +})" +`; -snapshot[`isUnwrapOptionalOf > returns properly named function 2`] = `"isNumber"`; +snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; -snapshot[`isUnwrapOptionalOf > returns properly named function 3`] = `"isNumber"`; +snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; -snapshot[`isReadonlyOf > returns properly named function 1`] = `"isReadonlyOf(isNumber)"`; +snapshot[`isPickOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" +`; -snapshot[`isReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isReadonlyOf(isNumber))"`; +snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; -snapshot[`isUnwrapReadonlyOf > returns properly named function 1`] = `"isNumber"`; +snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`; -snapshot[`isUnwrapReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isNumber)"`; +snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; -snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; +snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; -snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; -snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; -snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; +snapshot[`isRequiredOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isUnionOf([ + isString, + isUndefined + ]), + c: isBoolean +})" +`; -snapshot[`isTupleOf > returns properly named function 1`] = ` -"isTupleOf([ - isNumber, - isString, - isBoolean -])" +snapshot[`isRequiredOf > returns properly named function 2`] = ` +"isObjectOf({ + a: isNumber, + b: isUnionOf([ + isString, + isUndefined + ]), + c: isBoolean +})" `; -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; +snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; -snapshot[`isTupleOf > returns properly named function 3`] = ` -"isTupleOf([ - isTupleOf([ - isTupleOf([ - isNumber, - isString, - isBoolean - ]) - ]) -])" -`; +snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; -snapshot[`isTupleOf > returns properly named function 1`] = ` +snapshot[`isTupleOf > returns properly named function 1`] = ` "isTupleOf([ isNumber, isString, isBoolean -], isArray)" +])" `; -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; -snapshot[`isTupleOf > returns properly named function 3`] = ` +snapshot[`isTupleOf > returns properly named function 3`] = ` "isTupleOf([ isTupleOf([ isTupleOf([ isNumber, isString, isBoolean - ], isArray) - ], isArray) -])" -`; - -snapshot[`isParametersOf > returns properly named function 1`] = ` -"isParametersOf([ - isNumber, - isString, - isOptionalOf(isBoolean) -])" -`; - -snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)])"`; - -snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([])"`; - -snapshot[`isParametersOf > returns properly named function 4`] = ` -"isParametersOf([ - isParametersOf([ - isParametersOf([ - isNumber, - isString, - isOptionalOf(isBoolean) ]) ]) ])" `; -snapshot[`isParametersOf > returns properly named function 1`] = ` -"isParametersOf([ - isNumber, - isString, - isOptionalOf(isBoolean) -], isArray)" -`; - -snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)], isArrayOf(isString))"`; - -snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([], isArrayOf(isString))"`; - -snapshot[`isParametersOf > returns properly named function 4`] = ` -"isParametersOf([ - isParametersOf([ - isParametersOf([ - isNumber, - isString, - isOptionalOf(isBoolean) - ], isArray) - ], isArray) -])" -`; - -snapshot[`isReadonlyTupleOf > returns properly named function 1`] = ` -"isReadonlyOf(isTupleOf([ - isNumber, - isString, - isBoolean -]))" -`; - -snapshot[`isReadonlyTupleOf > returns properly named function 2`] = `"isReadonlyOf(isTupleOf([(anonymous)]))"`; - -snapshot[`isReadonlyTupleOf > returns properly named function 3`] = ` -"isReadonlyOf(isTupleOf([ - isReadonlyOf(isTupleOf([ - isReadonlyOf(isTupleOf([ - isNumber, - isString, - isBoolean - ])) - ])) -]))" -`; - -snapshot[`isReadonlyTupleOf > returns properly named function 1`] = ` -"isReadonlyOf(isTupleOf([ - isNumber, - isString, - isBoolean -], isArray))" -`; - -snapshot[`isReadonlyTupleOf > returns properly named function 2`] = `"isReadonlyOf(isTupleOf([(anonymous)], isArrayOf(isString)))"`; - -snapshot[`isReadonlyTupleOf > returns properly named function 3`] = ` -"isReadonlyOf(isTupleOf([ - isReadonlyOf(isTupleOf([ - isReadonlyOf(isTupleOf([ - isNumber, - isString, - isBoolean - ], isArray)) - ], isArray)) -], isArray))" -`; - -snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`; - -snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; - -snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; - -snapshot[`isReadonlyUniformTupleOf > returns properly named function 1`] = `"isReadonlyOf(isUniformTupleOf(3, isAny))"`; - -snapshot[`isReadonlyUniformTupleOf > returns properly named function 2`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`; - -snapshot[`isReadonlyUniformTupleOf > returns properly named function 3`] = `"isReadonlyOf(isUniformTupleOf(3, (anonymous)))"`; - -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; - -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; - -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; - -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; - snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; - -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; - -snapshot[`isRecordLikeOf > returns properly named function 1`] = `"isRecordLikeOf(isNumber, undefined)"`; - -snapshot[`isRecordLikeOf > returns properly named function 2`] = `"isRecordLikeOf((anonymous), undefined)"`; - -snapshot[`isRecordLikeOf > returns properly named function 1`] = `"isRecordLikeOf(isNumber, isString)"`; - -snapshot[`isRecordLikeOf > returns properly named function 2`] = `"isRecordLikeOf((anonymous), isString)"`; - -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; - -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; - -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; - -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; +snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; -snapshot[`isObjectOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString, - c: isBoolean -})" -`; +snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(isNumber)"`; -snapshot[`isObjectOf > returns properly named function 2`] = `"isObjectOf({a: a})"`; +snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; -snapshot[`isObjectOf > returns properly named function 3`] = ` -"isObjectOf({ - a: isObjectOf({ - b: isObjectOf({c: isBoolean}) - }) -})" -`; +snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; snapshot[`isStrictOf > returns properly named function 1`] = ` "isStrictOf(isObjectOf({ @@ -242,10 +119,6 @@ snapshot[`isStrictOf > returns properly named function 3`] = ` }))" `; -snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; - -snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; - snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; @@ -260,56 +133,65 @@ snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(u snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; -snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; +snapshot[`isReadonlyOf > returns properly named function 1`] = `"isReadonlyOf(isNumber)"`; -snapshot[`isUnionOf > returns properly named function 1`] = ` -"isUnionOf([ +snapshot[`isReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isReadonlyOf(isNumber))"`; + +snapshot[`isParametersOf > returns properly named function 1`] = ` +"isParametersOf([ isNumber, isString, - isBoolean + isOptionalOf(isBoolean) ])" `; -snapshot[`isIntersectionOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString -})" -`; +snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)])"`; -snapshot[`isIntersectionOf > returns properly named function 2`] = ` -"isString" -`; +snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([])"`; -snapshot[`isIntersectionOf > returns properly named function 3`] = ` -"isIntersectionOf([ - isFunction, - isObjectOf({b: isString}) +snapshot[`isParametersOf > returns properly named function 4`] = ` +"isParametersOf([ + isParametersOf([ + isParametersOf([ + isNumber, + isString, + isOptionalOf(isBoolean) + ]) + ]) ])" `; -snapshot[`isRequiredOf > returns properly named function 1`] = ` +snapshot[`isOmitOf > returns properly named function 1`] = ` "isObjectOf({ a: isNumber, - b: isUnionOf([ - isString, - isUndefined - ]), c: isBoolean })" `; -snapshot[`isRequiredOf > returns properly named function 2`] = ` +snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; + +snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; + +snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; + +snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; + +snapshot[`isIntersectionOf > returns properly named function 1`] = ` "isObjectOf({ a: isNumber, - b: isUnionOf([ - isString, - isUndefined - ]), - c: isBoolean + b: isString })" `; +snapshot[`isIntersectionOf > returns properly named function 2`] = `"isString"`; + +snapshot[`isIntersectionOf > returns properly named function 3`] = ` +"isIntersectionOf([ + isFunction, + isObjectOf({b: isString}) +])" +`; + snapshot[`isPartialOf > returns properly named function 1`] = ` "isObjectOf({ a: isOptionalOf(isNumber), @@ -332,25 +214,67 @@ snapshot[`isPartialOf > returns properly named function 2`] = ` })" `; -snapshot[`isPickOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; + +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; + +snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; + +snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; + +snapshot[`isTupleOf > returns properly named function 1`] = ` +"isTupleOf([ + isNumber, + isString, + isBoolean +], isArray)" `; -snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; -snapshot[`isOmitOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" +snapshot[`isTupleOf > returns properly named function 3`] = ` +"isTupleOf([ + isTupleOf([ + isTupleOf([ + isNumber, + isString, + isBoolean + ], isArray) + ], isArray) +])" `; -snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; +snapshot[`isUnwrapOptionalOf > returns properly named function 1`] = `"isNumber"`; + +snapshot[`isUnwrapOptionalOf > returns properly named function 2`] = `"isNumber"`; + +snapshot[`isUnwrapOptionalOf > returns properly named function 3`] = `"isNumber"`; + +snapshot[`isParametersOf > returns properly named function 1`] = ` +"isParametersOf([ + isNumber, + isString, + isOptionalOf(isBoolean) +], isArray)" +`; + +snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)], isArrayOf(isString))"`; + +snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([], isArrayOf(isString))"`; + +snapshot[`isParametersOf > returns properly named function 4`] = ` +"isParametersOf([ + isParametersOf([ + isParametersOf([ + isNumber, + isString, + isOptionalOf(isBoolean) + ], isArray) + ], isArray) +])" +`; -snapshot[`isOneOf > returns properly named function 1`] = ` +snapshot[`isUnionOf > returns properly named function 1`] = ` "isUnionOf([ isNumber, isString, @@ -358,9 +282,10 @@ snapshot[`isOneOf > returns properly named function 1`] = ` ])" `; -snapshot[`isAllOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString -})" -`; +snapshot[`isUnwrapReadonlyOf > returns properly named function 1`] = `"isNumber"`; + +snapshot[`isUnwrapReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isNumber)"`; + +snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; + +snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; diff --git a/is.ts b/is.ts index df38b38..b598d13 100644 --- a/is.ts +++ b/is.ts @@ -237,18 +237,6 @@ export function isRecord( return x != null && !Array.isArray(x) && typeof x === "object"; } -/** - * Return `true` if the type of `x` is like `Record`. - * - * @deprecated Use `is.Record` instead. - * ``` - */ -export function isRecordLike( - x: unknown, -): x is Record { - return x != null && !Array.isArray(x) && typeof x === "object"; -} - /** * Return `true` if the type of `x` is `Map`. * @@ -883,88 +871,6 @@ type IsParametersOfMetadata = { ]; }; -/** - * Return a type predicate function that returns `true` if the type of `x` is `Readonly>`. - * - * @deprecated Use `is.ReadonlyOf(is.TupleOf(...))` instead. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.ReadonlyTupleOf([is.Number, is.String, is.Boolean]); - * const a: unknown = [0, "a", true]; - * if (isMyType(a)) { - * // a is narrowed to readonly [number, string, boolean] - * const _: readonly [number, string, boolean] = a; - * } - * ``` - * - * With `predElse`: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.ReadonlyTupleOf( - * [is.Number, is.String, is.Boolean], - * is.ArrayOf(is.Number), - * ); - * const a: unknown = [0, "a", true, 0, 1, 2]; - * if (isMyType(a)) { - * // a is narrowed to readonly [number, string, boolean, ...number[]] - * const _: readonly [number, string, boolean, ...number[]] = a; - * } - * ``` - * - * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array - * used as `predTup`. If a type error occurs, try adding `as const` as follows: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const predTup = [is.Number, is.String, is.Boolean] as const; - * const isMyType = is.ReadonlyTupleOf(predTup); - * const a: unknown = [0, "a", true]; - * if (isMyType(a)) { - * // a is narrowed to readonly [number, string, boolean] - * const _: readonly [number, string, boolean] = a; - * } - * ``` - */ -export function isReadonlyTupleOf< - T extends readonly [Predicate, ...Predicate[]], ->( - predTup: T, -): Predicate>> & WithMetadata; -export function isReadonlyTupleOf< - T extends readonly [Predicate, ...Predicate[]], - E extends Predicate, ->( - predTup: T, - predElse: E, -): - & Predicate, ...PredicateType]>> - & WithMetadata; -export function isReadonlyTupleOf< - T extends readonly [Predicate, ...Predicate[]], - E extends Predicate, ->( - predTup: T, - predElse?: E, -): - & Predicate< - | Readonly> - | Readonly<[...TupleOf, ...PredicateType]> - > - & WithMetadata { - if (!predElse) { - return isReadonlyOf(isTupleOf(predTup)); - } else { - return isReadonlyOf(isTupleOf(predTup, predElse)); - } -} - /** * Return a type predicate function that returns `true` if the type of `x` is `UniformTupleOf`. * @@ -1021,46 +927,6 @@ type IsUniformTupleOfMetadata = { args: Parameters; }; -/** - * Return a type predicate function that returns `true` if the type of `x` is `Readonly>`. - * - * @deprecated Use `is.ReadonlyOf(is.UniformTupleOf(...))` instead. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.ReadonlyUniformTupleOf(5); - * const a: unknown = [0, 1, 2, 3, 4]; - * if (isMyType(a)) { - * // a is narrowed to readonly [unknown, unknown, unknown, unknown, unknown] - * const _: readonly [unknown, unknown, unknown, unknown, unknown] = a; - * } - * ``` - * - * With predicate function: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.ReadonlyUniformTupleOf(5, is.Number); - * const a: unknown = [0, 1, 2, 3, 4]; - * if (isMyType(a)) { - * // a is narrowed to readonly [number, number, number, number, number] - * const _: readonly [number, number, number, number, number] = a; - * } - * ``` - */ -export function isReadonlyUniformTupleOf( - n: N, - pred: Predicate = isAny, -): - & Predicate>> - & WithMetadata { - return isReadonlyOf(isUniformTupleOf(n, pred)); -} - /** * Return a type predicate function that returns `true` if the type of `x` is an Object instance that satisfies `Record`. * @@ -1166,26 +1032,6 @@ type IsRecordOfMetadata = { args: Parameters; }; -/** - * Return a type predicate function that returns `true` if the type of `x` satisfies `Record`. - * - * @deprecated Use `is.RecordOf()` instead - */ -export function isRecordLikeOf( - pred: Predicate, - predKey?: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata(isRecordOf(pred, predKey), { - name: "isRecordLikeOf", - args: [pred, predKey], - }); -} - -type IsRecordLikeOfMetadata = { - name: "isRecordLikeOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `Map`. * @@ -1268,25 +1114,11 @@ export function isObjectOf< >( predObj: T, ): Predicate> & WithMetadata; -/** - * @deprecated The `option.strict` is deprecated. Use `isStrictOf()` instead. - */ -export function isObjectOf< - T extends Record>, ->( - predObj: T, - options: { strict?: boolean }, -): Predicate> & WithMetadata; export function isObjectOf< T extends Record>, >( predObj: T, - options?: { strict?: boolean }, ): Predicate> & WithMetadata { - if (options?.strict) { - // deno-lint-ignore no-explicit-any - return isStrictOf(isObjectOf(predObj)) as any; - } return setPredicateFactoryMetadata( (x: unknown): x is ObjectOf => { if ( @@ -1333,8 +1165,7 @@ type IsObjectOfMetadata = { * * If `is.OptionalOf()` is specified in the predicate function, the property becomes optional. * - * The number of keys of `x` must be equal to the number of non optional keys of `predObj`. This is equivalent to - * the deprecated `options.strict` in `isObjectOf()`. + * The number of keys of `x` must be equal to the number of non optional keys of `predObj`. * * ```ts * import { is } from "@core/unknownutil"; @@ -1761,43 +1592,7 @@ export function isOmitOf< & WithMetadata; } -/** - * Return a type predicate function that returns `true` if the type of `x` is `UnionOf`. - * - * @deprecated Use `isUnionOf` instead. - */ -export function isOneOf< - T extends readonly [Predicate, ...Predicate[]], ->( - preds: T, -): Predicate> { - return isUnionOf(preds); -} - -type OneOf = T extends readonly [Predicate, ...infer R] - ? U | OneOf - : never; - -/** - * Return a type predicate function that returns `true` if the type of `x` is `IntersectionOf`. - * - * @deprecated Use `isIntersectionOf` instead. - */ -export function isAllOf< - T extends readonly [ - Predicate & WithMetadata, - ...(Predicate & WithMetadata)[], - ], ->( - preds: T, -): Predicate> { - return isIntersectionOf(preds); -} - -type AllOf = IntersectionOf; - export const is = { - AllOf: isAllOf, Any: isAny, Array: isArray, ArrayOf: isArrayOf, @@ -1816,7 +1611,6 @@ export const is = { Number: isNumber, ObjectOf: isObjectOf, OmitOf: isOmitOf, - OneOf: isOneOf, Optional: isOptional, OptionalOf: isOptionalOf, ParametersOf: isParametersOf, @@ -1825,11 +1619,7 @@ export const is = { Primitive: isPrimitive, Readonly: isReadonly, ReadonlyOf: isReadonlyOf, - ReadonlyTupleOf: isReadonlyTupleOf, - ReadonlyUniformTupleOf: isReadonlyUniformTupleOf, Record: isRecord, - RecordLike: isRecordLike, - RecordLikeOf: isRecordLikeOf, RecordObject: isRecordObject, RecordObjectOf: isRecordObjectOf, RecordOf: isRecordOf, diff --git a/is_bench.ts b/is_bench.ts index 14f1750..1367232 100644 --- a/is_bench.ts +++ b/is_bench.ts @@ -176,46 +176,6 @@ Deno.bench({ }, }); -Deno.bench({ - name: "is.ReadonlyTupleOf", - fn: () => { - const pred = is.ReadonlyTupleOf(predTup); - for (const c of cs) { - pred(c); - } - }, -}); - -const isReadonlyTupleOfPred = is.ReadonlyTupleOf(predTup); -Deno.bench({ - name: "is.ReadonlyTupleOf (pre)", - fn: () => { - for (const c of cs) { - isReadonlyTupleOfPred(c); - } - }, -}); - -Deno.bench({ - name: "is.ReadonlyTupleOf", - fn: () => { - const pred = is.ReadonlyTupleOf(predTup, is.Array); - for (const c of cs) { - pred(c); - } - }, -}); - -const isReadonlyTupleOfElsePred = is.ReadonlyTupleOf(predTup, is.Array); -Deno.bench({ - name: "is.ReadonlyTupleOf (pre)", - fn: () => { - for (const c of cs) { - isReadonlyTupleOfElsePred(c); - } - }, -}); - Deno.bench({ name: "is.UniformTupleOf", fn: () => { @@ -236,26 +196,6 @@ Deno.bench({ }, }); -Deno.bench({ - name: "is.ReadonlyUniformTupleOf", - fn: () => { - const pred = is.ReadonlyUniformTupleOf(3, is.String); - for (const c of cs) { - pred(c); - } - }, -}); - -const isReadonlyUniformTupleOfPred = is.ReadonlyUniformTupleOf(3, is.String); -Deno.bench({ - name: "is.ReadonlyUniformTupleOf (pre)", - fn: () => { - for (const c of cs) { - isReadonlyUniformTupleOfPred(c); - } - }, -}); - Deno.bench({ name: "is.Record", fn: () => { @@ -328,15 +268,6 @@ Deno.bench({ } }, }); -Deno.bench({ - name: "is.ObjectOf (strict)", - fn: () => { - const pred = is.ObjectOf(predObj, { strict: true }); - for (const c of cs) { - pred(c); - } - }, -}); const isObjectOfPred = is.ObjectOf(predObj); Deno.bench({ @@ -348,16 +279,6 @@ Deno.bench({ }, }); -const isObjectOfStrictPred = is.ObjectOf(predObj, { strict: true }); -Deno.bench({ - name: "is.ObjectOf (pre, strict)", - fn: () => { - for (const c of cs) { - isObjectOfStrictPred(c); - } - }, -}); - Deno.bench({ name: "is.Function", fn: () => { @@ -492,51 +413,6 @@ Deno.bench({ }, }); -const predsOne = [is.String, is.Number, is.Boolean] as const; -Deno.bench({ - name: "is.OneOf", - fn: () => { - const pred = is.OneOf(predsOne); - for (const c of cs) { - pred(c); - } - }, -}); - -const isOneOfPred = is.OneOf(predsOne); -Deno.bench({ - name: "is.OneOf (pre)", - fn: () => { - for (const c of cs) { - isOneOfPred(c); - } - }, -}); - -const predsAll = [ - is.ObjectOf({ a: is.Number }), - is.ObjectOf({ b: is.String }), -] as const; -Deno.bench({ - name: "is.AllOf", - fn: () => { - const pred = is.AllOf(predsAll); - for (const c of cs) { - pred(c); - } - }, -}); - -const isAllOfPred = is.AllOf(predsAll); -Deno.bench({ - name: "is.AllOf (pre)", - fn: () => { - for (const c of cs) { - isAllOfPred(c); - } - }, -}); - Deno.bench({ name: "is.OptionalOf", fn: () => { diff --git a/is_test.ts b/is_test.ts index baf3e53..48e8cd5 100644 --- a/is_test.ts +++ b/is_test.ts @@ -5,7 +5,6 @@ import { type Equal, stringify } from "./_testutil.ts"; import type { Predicate, PredicateType } from "./is.ts"; import { is, - isAllOf, isAny, isArray, isArrayOf, @@ -24,18 +23,13 @@ import { isNumber, isObjectOf, isOmitOf, - isOneOf, isOptionalOf, isParametersOf, isPartialOf, isPickOf, isPrimitive, isReadonlyOf, - isReadonlyTupleOf, - isReadonlyUniformTupleOf, isRecord, - isRecordLike, - isRecordLikeOf, isRecordObject, isRecordObjectOf, isRecordOf, @@ -183,12 +177,6 @@ Deno.test("isRecord", async (t) => { }); }); -Deno.test("isRecordLike", async (t) => { - await testWithExamples(t, isRecordLike, { - validExamples: ["record", "date", "promise", "set", "map"], - }); -}); - Deno.test("isMap", async (t) => { await testWithExamples(t, isMap, { validExamples: ["map"], @@ -780,122 +768,6 @@ Deno.test("isParametersOf", async (t) => { ); }); -Deno.test("isReadonlyTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isReadonlyTupleOf([isNumber, isString, isBoolean]).name, - ); - await assertSnapshot( - t, - isReadonlyTupleOf([(_x): _x is string => false]).name, - ); - // Nested - await assertSnapshot( - t, - isReadonlyTupleOf([ - isReadonlyTupleOf([isReadonlyTupleOf([isNumber, isString, isBoolean])]), - ]).name, - ); - }); - await t.step("returns proper type predicate", () => { - const predTup = [isNumber, isString, isBoolean] as const; - const a: unknown = [0, "a", true]; - if (isReadonlyTupleOf(predTup)(a)) { - assertType>(true); - } - }); - await t.step("returns true on T tuple", () => { - const predTup = [isNumber, isString, isBoolean] as const; - assertEquals(isReadonlyTupleOf(predTup)([0, "a", true]), true); - }); - await t.step("returns false on non T tuple", () => { - const predTup = [isNumber, isString, isBoolean] as const; - assertEquals(isReadonlyTupleOf(predTup)([0, 1, 2]), false); - assertEquals(isReadonlyTupleOf(predTup)([0, "a"]), false); - assertEquals(isReadonlyTupleOf(predTup)([0, "a", true, 0]), false); - }); - await testWithExamples( - t, - isReadonlyTupleOf([(_: unknown): _ is unknown => true]), - { - excludeExamples: ["array"], - }, - ); -}); - -Deno.test("isReadonlyTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isReadonlyTupleOf([isNumber, isString, isBoolean], isArray) - .name, - ); - await assertSnapshot( - t, - isReadonlyTupleOf( - [(_x): _x is string => false], - isArrayOf(isString), - ).name, - ); - // Nested - await assertSnapshot( - t, - isReadonlyTupleOf([ - isReadonlyTupleOf([ - isReadonlyTupleOf([isNumber, isString, isBoolean], isArray), - ], isArray), - ], isArray).name, - ); - }); - await t.step("returns proper type predicate", () => { - const predTup = [isNumber, isString, isBoolean] as const; - const predElse = isArrayOf(isNumber); - const a: unknown = [0, "a", true, 0, 1, 2]; - if (isReadonlyTupleOf(predTup, predElse)(a)) { - assertType< - Equal - >(true); - } - }); - await t.step("returns true on T tuple", () => { - const predTup = [isNumber, isString, isBoolean] as const; - const predElse = isArrayOf(isNumber); - assertEquals( - isReadonlyTupleOf(predTup, predElse)([0, "a", true, 0, 1, 2]), - true, - ); - }); - await t.step("returns false on non T tuple", () => { - const predTup = [isNumber, isString, isBoolean] as const; - const predElse = isArrayOf(isString); - assertEquals( - isReadonlyTupleOf(predTup, predElse)([0, 1, 2, 0, 1, 2]), - false, - ); - assertEquals( - isReadonlyTupleOf(predTup, predElse)([0, "a", 0, 1, 2]), - false, - ); - assertEquals( - isReadonlyTupleOf(predTup, predElse)([0, "a", true, 0, 0, 1, 2]), - false, - ); - assertEquals( - isReadonlyTupleOf(predTup, predElse)([0, "a", true, 0, 1, 2]), - false, - ); - }); - const predElse = isArray; - await testWithExamples( - t, - isReadonlyTupleOf([(_: unknown): _ is unknown => true], predElse), - { - excludeExamples: ["array"], - }, - ); -}); - Deno.test("isUniformTupleOf", async (t) => { await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isUniformTupleOf(3).name); @@ -933,49 +805,6 @@ Deno.test("isUniformTupleOf", async (t) => { }); }); -Deno.test("isReadonlyUniformTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isReadonlyUniformTupleOf(3).name); - await assertSnapshot(t, isReadonlyUniformTupleOf(3, isNumber).name); - await assertSnapshot( - t, - isReadonlyUniformTupleOf(3, (_x): _x is string => false).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = [0, 1, 2, 3, 4]; - if (isReadonlyUniformTupleOf(5)(a)) { - assertType< - Equal< - typeof a, - readonly [unknown, unknown, unknown, unknown, unknown] - > - >(true); - } - - if (isReadonlyUniformTupleOf(5, isNumber)(a)) { - assertType< - Equal - >(true); - } - }); - await t.step("returns true on mono-typed T tuple", () => { - assertEquals(isReadonlyUniformTupleOf(3)([0, 1, 2]), true); - assertEquals(isReadonlyUniformTupleOf(3, isNumber)([0, 1, 2]), true); - }); - await t.step("returns false on non mono-typed T tuple", () => { - assertEquals(isReadonlyUniformTupleOf(4)([0, 1, 2]), false); - assertEquals(isReadonlyUniformTupleOf(4)([0, 1, 2, 3, 4]), false); - assertEquals( - isReadonlyUniformTupleOf(3, isNumber)(["a", "b", "c"]), - false, - ); - }); - await testWithExamples(t, isReadonlyUniformTupleOf(4), { - excludeExamples: ["array"], - }); -}); - Deno.test("isRecordObjectOf", async (t) => { await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isRecordObjectOf(isNumber).name); @@ -1112,74 +941,6 @@ Deno.test("isRecordOf", async (t) => { ); }); -Deno.test("isRecordLikeOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isRecordLikeOf(isNumber).name); - await assertSnapshot(t, isRecordLikeOf((_x): _x is string => false).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordLikeOf(isNumber)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T record", () => { - assertEquals(isRecordLikeOf(isNumber)({ a: 0 }), true); - assertEquals(isRecordLikeOf(isString)({ a: "a" }), true); - assertEquals(isRecordLikeOf(isBoolean)({ a: true }), true); - }); - await t.step("returns false on non T record", () => { - assertEquals(isRecordLikeOf(isString)({ a: 0 }), false); - assertEquals(isRecordLikeOf(isNumber)({ a: "a" }), false); - assertEquals(isRecordLikeOf(isString)({ a: true }), false); - }); - await testWithExamples( - t, - isRecordLikeOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record", "date", "promise", "set", "map"], - }, - ); -}); - -Deno.test("isRecordLikeOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isRecordLikeOf(isNumber, isString).name); - await assertSnapshot( - t, - isRecordLikeOf((_x): _x is string => false, isString).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordLikeOf(isNumber, isString)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T record", () => { - assertEquals(isRecordLikeOf(isNumber, isString)({ a: 0 }), true); - assertEquals(isRecordLikeOf(isString, isString)({ a: "a" }), true); - assertEquals(isRecordLikeOf(isBoolean, isString)({ a: true }), true); - }); - await t.step("returns false on non T record", () => { - assertEquals(isRecordLikeOf(isString, isString)({ a: 0 }), false); - assertEquals(isRecordLikeOf(isNumber, isString)({ a: "a" }), false); - assertEquals(isRecordLikeOf(isString, isString)({ a: true }), false); - }); - await t.step("returns false on non K record", () => { - assertEquals(isRecordLikeOf(isNumber, isNumber)({ a: 0 }), false); - assertEquals(isRecordLikeOf(isString, isNumber)({ a: "a" }), false); - assertEquals(isRecordLikeOf(isBoolean, isNumber)({ a: true }), false); - }); - await testWithExamples( - t, - isRecordLikeOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record", "date", "promise", "set", "map"], - }, - ); -}); - Deno.test("isMapOf", async (t) => { await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isMapOf(isNumber).name); @@ -1274,11 +1035,6 @@ Deno.test("isObjectOf", async (t) => { c: isBoolean, }; assertEquals(isObjectOf(predObj)({ a: 0, b: "a", c: true }), true); - assertEquals( - isObjectOf(predObj, { strict: true })({ a: 0, b: "a", c: true }), - true, - "Specify `{ strict: true }`", - ); assertEquals( isObjectOf(predObj)({ a: 0, b: "a", c: true, d: "ignored" }), true, @@ -1309,16 +1065,6 @@ Deno.test("isObjectOf", async (t) => { false, "Object does not have one property", ); - assertEquals( - isObjectOf(predObj, { strict: true })({ - a: 0, - b: "a", - c: true, - d: "invalid", - }), - false, - "Specify `{ strict: true }` and object have an unknown property", - ); assertEquals( isObjectOf({ 0: isString })(["a"]), false, @@ -1888,90 +1634,6 @@ Deno.test("isOmitOf", async (t) => { }); }); -Deno.test("isOneOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isOneOf([isNumber, isString, isBoolean]).name); - }); - await t.step("returns proper type predicate", () => { - const preds = [isNumber, isString, isBoolean] as const; - const a: unknown = [0, "a", true]; - if (isOneOf(preds)(a)) { - assertType>(true); - } - }); - await t.step("returns proper type predicate (#49)", () => { - const isFoo = isObjectOf({ foo: isString }); - const isBar = isObjectOf({ foo: isString, bar: isNumber }); - type Foo = PredicateType; - type Bar = PredicateType; - const preds = [isFoo, isBar] as const; - const a: unknown = [0, "a", true]; - if (isOneOf(preds)(a)) { - assertType>(true); - } - }); - await t.step("returns true on one of T", () => { - const preds = [isNumber, isString, isBoolean] as const; - assertEquals(isOneOf(preds)(0), true); - assertEquals(isOneOf(preds)("a"), true); - assertEquals(isOneOf(preds)(true), true); - }); - await t.step("returns false on non of T", async (t) => { - const preds = [isNumber, isString, isBoolean] as const; - await testWithExamples(t, isOneOf(preds), { - excludeExamples: ["number", "string", "boolean"], - }); - }); -}); - -Deno.test("isAllOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isAllOf([ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ]).name, - ); - }); - await t.step("returns proper type predicate", () => { - const preds = [ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ] as const; - const a: unknown = { a: 0, b: "a" }; - if (isAllOf(preds)(a)) { - assertType>(true); - } - }); - await t.step("returns true on all of T", () => { - const preds = [ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ] as const; - assertEquals(isAllOf(preds)({ a: 0, b: "a" }), true); - }); - await t.step("returns false on non of T", async (t) => { - const preds = [ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ] as const; - assertEquals( - isAllOf(preds)({ a: 0, b: 0 }), - false, - "Some properties has wrong type", - ); - assertEquals( - isAllOf(preds)({ a: 0 }), - false, - "Some properties does not exists", - ); - await testWithExamples(t, isAllOf(preds), { - excludeExamples: ["record"], - }); - }); -}); - Deno.test("is", async (t) => { const mod = await import("./is.ts"); const casesOfAliasAndIsFunction = Object.entries(mod) From 8e0659d7f3c50c95a5197ef608d4e93230013cdc Mon Sep 17 00:00:00 2001 From: Alisue Date: Thu, 1 Aug 2024 02:47:41 +0900 Subject: [PATCH 02/22] :boom: Remove unstable features - `isReadonly` - `isReadonlyOf` - `isUnwrapReadonlyOf` --- __snapshots__/is_test.ts.snap | 292 +++++++++++++++++----------------- _typeutil.ts | 2 - is.ts | 88 +--------- is_test.ts | 65 -------- 4 files changed, 143 insertions(+), 304 deletions(-) diff --git a/__snapshots__/is_test.ts.snap b/__snapshots__/is_test.ts.snap index c46aab9..3e9f149 100644 --- a/__snapshots__/is_test.ts.snap +++ b/__snapshots__/is_test.ts.snap @@ -1,5 +1,70 @@ export const snapshot = {}; +snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`; + +snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; + +snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; + +snapshot[`isUnionOf > returns properly named function 1`] = ` +"isUnionOf([ + isNumber, + isString, + isBoolean +])" +`; + +snapshot[`isOmitOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" +`; + +snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; + +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; + +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; + +snapshot[`isUnwrapOptionalOf > returns properly named function 1`] = `"isNumber"`; + +snapshot[`isUnwrapOptionalOf > returns properly named function 2`] = `"isNumber"`; + +snapshot[`isUnwrapOptionalOf > returns properly named function 3`] = `"isNumber"`; + +snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; + +snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; + +snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; + +snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; + +snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; + +snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; + +snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; + +snapshot[`isStrictOf > returns properly named function 1`] = ` +"isStrictOf(isObjectOf({ + a: isNumber, + b: isString, + c: isBoolean +}))" +`; + +snapshot[`isStrictOf > returns properly named function 2`] = `"isStrictOf(isObjectOf({a: a}))"`; + +snapshot[`isStrictOf > returns properly named function 3`] = ` +"isStrictOf(isObjectOf({ + a: isStrictOf(isObjectOf({ + b: isStrictOf(isObjectOf({c: isBoolean})) + })) +}))" +`; + snapshot[`isObjectOf > returns properly named function 1`] = ` "isObjectOf({ a: isNumber, @@ -18,28 +83,37 @@ snapshot[`isObjectOf > returns properly named function 3`] = ` })" `; -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; - -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; - -snapshot[`isPickOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" +snapshot[`isParametersOf > returns properly named function 1`] = ` +"isParametersOf([ + isNumber, + isString, + isOptionalOf(isBoolean) +], isArray)" `; -snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; +snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)], isArrayOf(isString))"`; -snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`; +snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([], isArrayOf(isString))"`; -snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; +snapshot[`isParametersOf > returns properly named function 4`] = ` +"isParametersOf([ + isParametersOf([ + isParametersOf([ + isNumber, + isString, + isOptionalOf(isBoolean) + ], isArray) + ], isArray) +])" +`; -snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; +snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; +snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; +snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; + +snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; snapshot[`isRequiredOf > returns properly named function 1`] = ` "isObjectOf({ @@ -63,10 +137,33 @@ snapshot[`isRequiredOf > returns properly named function 2`] = ` })" `; +snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; + +snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; + +snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; + +snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(isNumber)"`; + +snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; + +snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; + snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; +snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; + +snapshot[`isPickOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" +`; + +snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; + snapshot[`isTupleOf > returns properly named function 1`] = ` "isTupleOf([ isNumber, @@ -89,54 +186,6 @@ snapshot[`isTupleOf > returns properly named function 3`] = ` ])" `; -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; - -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; - -snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; - -snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(isNumber)"`; - -snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; - -snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; - -snapshot[`isStrictOf > returns properly named function 1`] = ` -"isStrictOf(isObjectOf({ - a: isNumber, - b: isString, - c: isBoolean -}))" -`; - -snapshot[`isStrictOf > returns properly named function 2`] = `"isStrictOf(isObjectOf({a: a}))"`; - -snapshot[`isStrictOf > returns properly named function 3`] = ` -"isStrictOf(isObjectOf({ - a: isStrictOf(isObjectOf({ - b: isStrictOf(isObjectOf({c: isBoolean})) - })) -}))" -`; - -snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; - -snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; - -snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; - -snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; - -snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; - -snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; - -snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; - -snapshot[`isReadonlyOf > returns properly named function 1`] = `"isReadonlyOf(isNumber)"`; - -snapshot[`isReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isReadonlyOf(isNumber))"`; - snapshot[`isParametersOf > returns properly named function 1`] = ` "isParametersOf([ isNumber, @@ -161,34 +210,33 @@ snapshot[`isParametersOf > returns properly named function 4`] = ` ])" `; -snapshot[`isOmitOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" -`; - -snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; - -snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; - snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; -snapshot[`isIntersectionOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString -})" +snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; + +snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; + +snapshot[`isTupleOf > returns properly named function 1`] = ` +"isTupleOf([ + isNumber, + isString, + isBoolean +], isArray)" `; -snapshot[`isIntersectionOf > returns properly named function 2`] = `"isString"`; +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; -snapshot[`isIntersectionOf > returns properly named function 3`] = ` -"isIntersectionOf([ - isFunction, - isObjectOf({b: isString}) +snapshot[`isTupleOf > returns properly named function 3`] = ` +"isTupleOf([ + isTupleOf([ + isTupleOf([ + isNumber, + isString, + isBoolean + ], isArray) + ], isArray) ])" `; @@ -218,74 +266,18 @@ snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumb snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; - -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; - -snapshot[`isTupleOf > returns properly named function 1`] = ` -"isTupleOf([ - isNumber, - isString, - isBoolean -], isArray)" -`; - -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; - -snapshot[`isTupleOf > returns properly named function 3`] = ` -"isTupleOf([ - isTupleOf([ - isTupleOf([ - isNumber, - isString, - isBoolean - ], isArray) - ], isArray) -])" -`; - -snapshot[`isUnwrapOptionalOf > returns properly named function 1`] = `"isNumber"`; - -snapshot[`isUnwrapOptionalOf > returns properly named function 2`] = `"isNumber"`; - -snapshot[`isUnwrapOptionalOf > returns properly named function 3`] = `"isNumber"`; - -snapshot[`isParametersOf > returns properly named function 1`] = ` -"isParametersOf([ - isNumber, - isString, - isOptionalOf(isBoolean) -], isArray)" +snapshot[`isIntersectionOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isString +})" `; -snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)], isArrayOf(isString))"`; - -snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([], isArrayOf(isString))"`; - -snapshot[`isParametersOf > returns properly named function 4`] = ` -"isParametersOf([ - isParametersOf([ - isParametersOf([ - isNumber, - isString, - isOptionalOf(isBoolean) - ], isArray) - ], isArray) -])" -`; +snapshot[`isIntersectionOf > returns properly named function 2`] = `"isString"`; -snapshot[`isUnionOf > returns properly named function 1`] = ` -"isUnionOf([ - isNumber, - isString, - isBoolean +snapshot[`isIntersectionOf > returns properly named function 3`] = ` +"isIntersectionOf([ + isFunction, + isObjectOf({b: isString}) ])" `; - -snapshot[`isUnwrapReadonlyOf > returns properly named function 1`] = `"isNumber"`; - -snapshot[`isUnwrapReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isNumber)"`; - -snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; - -snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; diff --git a/_typeutil.ts b/_typeutil.ts index de0b13f..36e7437 100644 --- a/_typeutil.ts +++ b/_typeutil.ts @@ -6,5 +6,3 @@ export type TupleToIntersection = T extends readonly [] ? never : T extends readonly [infer U] ? U : T extends readonly [infer U, ...infer R] ? U & TupleToIntersection : never; - -export type Writable = { -readonly [P in keyof T]: T[P] }; diff --git a/is.ts b/is.ts index b598d13..049e3aa 100644 --- a/is.ts +++ b/is.ts @@ -1,4 +1,4 @@ -import type { FlatType, TupleToIntersection, Writable } from "./_typeutil.ts"; +import type { FlatType, TupleToIntersection } from "./_typeutil.ts"; import { type GetMetadata, getMetadata, @@ -486,89 +486,6 @@ type UnwrapOptionalOf = T extends : T extends Predicate ? T : never; -/** - * Return `true` if the type of predicate function `x` is annotated as `Readonly` - * - * **This is unstable and may be removed in the future.** - */ -export function isReadonly

>( - x: P, -): x is P & WithMetadata { - const m = getMetadata(x); - if (m == null) return false; - return (m as PredicateFactoryMetadata).name === "isReadonlyOf"; -} - -/** - * Return an `Readonly` annotated type predicate function that returns `true` if the type of `x` is `T`. - * - * **This is unstable and may be removed in the future.** - * - * Note that this function does nothing but annotate the predicate function as `Readonly`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.ReadonlyOf(is.TupleOf([is.String, is.Number])); - * const a: unknown = ["a", 1]; - * if (isMyType(a)) { - * // a is narrowed to readonly [string, number] - * const _: readonly [string, number] = a; - * } - * ``` - */ -export function isReadonlyOf( - pred: Predicate, -): - & Predicate> - & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is Readonly => pred(x), - { name: "isReadonlyOf", args: [pred] }, - ) as - & Predicate> - & WithMetadata; -} - -type IsReadonlyOfMetadata = { - name: "isReadonlyOf"; - args: Parameters; -}; - -/** - * Return an `Readonly` un-annotated type predicate function that returns `true` if the type of `x` is `T`. - * - * **This is unstable and may be removed in the future.** - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.UnwrapReadonlyOf(is.ReadonlyOf(is.TupleOf([is.String, is.Number]))); - * const a: unknown = ["a", 1]; - * if (isMyType(a)) { - * // a is narrowed to [string, number] - * const _: [string, number] = a; - * } - * ``` - */ -export function isUnwrapReadonlyOf

>( - pred: P, -): UnwrapReadonlyOf

{ - if (!isReadonly(pred)) return pred as UnwrapReadonlyOf

; - const { args } = getPredicateFactoryMetadata(pred); - return args[0] as UnwrapReadonlyOf

; -} - -type UnwrapReadonlyOf = T extends - Predicate & WithMetadata - ? Predicate> - : T extends Predicate ? T - : never; - /** * Return a type predicate function that returns `true` if the type of `x` is `T[]`. * @@ -1617,8 +1534,6 @@ export const is = { PartialOf: isPartialOf, PickOf: isPickOf, Primitive: isPrimitive, - Readonly: isReadonly, - ReadonlyOf: isReadonlyOf, Record: isRecord, RecordObject: isRecordObject, RecordObjectOf: isRecordObjectOf, @@ -1636,5 +1551,4 @@ export const is = { UnionOf: isUnionOf, Unknown: isUnknown, UnwrapOptionalOf: isUnwrapOptionalOf, - UnwrapReadonlyOf: isUnwrapReadonlyOf, }; diff --git a/is_test.ts b/is_test.ts index 48e8cd5..374ed7a 100644 --- a/is_test.ts +++ b/is_test.ts @@ -28,7 +28,6 @@ import { isPartialOf, isPickOf, isPrimitive, - isReadonlyOf, isRecord, isRecordObject, isRecordObjectOf, @@ -46,7 +45,6 @@ import { isUnionOf, isUnknown, isUnwrapOptionalOf, - isUnwrapReadonlyOf, } from "./is.ts"; const examples = { @@ -411,69 +409,6 @@ Deno.test("isUnwrapOptionalOf", async (t) => { }); }); -Deno.test("isReadonlyOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isReadonlyOf(isNumber).name); - // Nesting does nothing - await assertSnapshot(t, isReadonlyOf(isReadonlyOf(isNumber)).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = undefined; - if (isReadonlyOf(isNumber)(a)) { - assertType>>(true); - } - if (isReadonlyOf(isTupleOf([isString, isNumber, isBoolean]))(a)) { - assertType>>(true); - } - if (isReadonlyOf(isUniformTupleOf(3, isString))(a)) { - assertType>>(true); - } - if ( - isReadonlyOf(isObjectOf({ a: isString, b: isNumber, c: isBoolean }))(a) - ) { - assertType< - Equal> - >(true); - } - }); -}); - -Deno.test("isUnwrapReadonlyOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isUnwrapReadonlyOf(isReadonlyOf(isNumber)).name); - // Nesting does nothing - await assertSnapshot( - t, - isUnwrapReadonlyOf(isReadonlyOf(isReadonlyOf(isNumber))).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = undefined; - if (isUnwrapReadonlyOf(isReadonlyOf(isNumber))(a)) { - assertType>(true); - } - if ( - isUnwrapReadonlyOf( - isReadonlyOf(isTupleOf([isString, isNumber, isBoolean])), - )(a) - ) { - assertType>(true); - } - if (isUnwrapReadonlyOf(isReadonlyOf(isUniformTupleOf(3, isString)))(a)) { - assertType>(true); - } - if ( - isUnwrapReadonlyOf( - isReadonlyOf(isObjectOf({ a: isString, b: isNumber, c: isBoolean })), - )(a) - ) { - assertType< - Equal - >(true); - } - }); -}); - Deno.test("isArrayOf", async (t) => { await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isArrayOf(isNumber).name); From a2886a06e7d6f4aea5afbf15f26d19b977b9d522 Mon Sep 17 00:00:00 2001 From: Alisue Date: Thu, 1 Aug 2024 02:48:31 +0900 Subject: [PATCH 03/22] :+1: Add `as.Optional` and `as.Unoptional` --- _annotation.ts | 46 +++++ _funcutil.ts | 21 +++ as/__snapshots__/optional_test.ts.snap | 11 ++ as/mod.ts | 6 + as/optional.ts | 89 ++++++++++ as/optional_test.ts | 233 +++++++++++++++++++++++++ mod.ts | 1 + 7 files changed, 407 insertions(+) create mode 100644 _annotation.ts create mode 100644 _funcutil.ts create mode 100644 as/__snapshots__/optional_test.ts.snap create mode 100644 as/mod.ts create mode 100644 as/optional.ts create mode 100644 as/optional_test.ts diff --git a/_annotation.ts b/_annotation.ts new file mode 100644 index 0000000..fa1da1d --- /dev/null +++ b/_annotation.ts @@ -0,0 +1,46 @@ +import type { Predicate } from "./is.ts"; + +export type Fn = (...args: unknown[]) => unknown; + +export function annotate( + fn: F, + name: N, + value: V, +): F & { [K in N]: V } { + return Object.defineProperties(fn, { + [name]: { + value, + }, + }) as F & { [K in N]: V }; +} + +export function unannotate( + fn: F & { [K in N]: V }, + name: N, +): V { + return fn[name]; +} + +export function hasAnnotation( + fn: F, + name: N, +): fn is F & { [K in N]: unknown } { + // deno-lint-ignore no-explicit-any + return !!(fn as any)[name]; +} + +/** + * Annotation for readonly. + */ +export type WithReadonly

> = { + readonly: P; +}; + +export type PredObj = Record>; + +/** + * Annotation for predObj. + */ +export type WithPredObj = { + predObj: T; +}; diff --git a/_funcutil.ts b/_funcutil.ts new file mode 100644 index 0000000..dcb79f6 --- /dev/null +++ b/_funcutil.ts @@ -0,0 +1,21 @@ +import { inspect } from "./inspect.ts"; + +/** + * Rewrite the function name. + */ +export function rewriteName unknown>( + fn: F, + name: string, + ...args: unknown[] +): F { + let cachedName: string | undefined; + return Object.defineProperties(fn, { + name: { + get: () => { + if (cachedName) return cachedName; + cachedName = `${name}(${args.map((v) => inspect(v)).join(", ")})`; + return cachedName; + }, + }, + }); +} diff --git a/as/__snapshots__/optional_test.ts.snap b/as/__snapshots__/optional_test.ts.snap new file mode 100644 index 0000000..0cbc43d --- /dev/null +++ b/as/__snapshots__/optional_test.ts.snap @@ -0,0 +1,11 @@ +export const snapshot = {}; + +snapshot[`asOptional > returns properly named function 1`] = `"asOptional(isNumber)"`; + +snapshot[`asOptional > returns properly named function 2`] = `"asOptional(isNumber)"`; + +snapshot[`asUnoptional > returns properly named function 1`] = `"isNumber"`; + +snapshot[`asUnoptional > returns properly named function 2`] = `"isNumber"`; + +snapshot[`asUnoptional > returns properly named function 3`] = `"isNumber"`; diff --git a/as/mod.ts b/as/mod.ts new file mode 100644 index 0000000..2afeab2 --- /dev/null +++ b/as/mod.ts @@ -0,0 +1,6 @@ +import { asOptional, asUnoptional } from "./optional.ts"; + +export const as = { + Optional: asOptional, + Unoptional: asUnoptional, +}; diff --git a/as/optional.ts b/as/optional.ts new file mode 100644 index 0000000..698c2b6 --- /dev/null +++ b/as/optional.ts @@ -0,0 +1,89 @@ +import type { Predicate } from "../is.ts"; +import { rewriteName } from "../_funcutil.ts"; +import { annotate, hasAnnotation, unannotate } from "../_annotation.ts"; + +/** + * Annotation for optional. + */ +export type WithOptional = { + optional: Predicate; +}; + +/** + * Return an `Optional` annotated type predicate function that returns `true` if the type of `x` is `T` or `undefined`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.ObjectOf({ + * foo: as.Optional(is.String), + * }); + * const a: unknown = {}; + * if (isMyType(a)) { + * // a is narrowed to {foo?: string} + * const _: {foo?: string} = a; + * } + * ``` + */ +export function asOptional( + pred: Predicate, +): Predicate & WithOptional { + if (hasAnnotation(pred, "optional")) { + return pred as Predicate & WithOptional; + } + return rewriteName( + annotate( + (x: unknown): x is T | undefined => x === undefined || pred(x), + "optional", + pred, + ), + "asOptional", + pred, + ) as Predicate & WithOptional; +} + +/** + * Return an `Optional` un-annotated type predicate function that returns `true` if the type of `x` is `T`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.ObjectOf({ + * foo: as.Unoptional(as.Optional(is.String)), + * }); + * const a: unknown = {foo: "a"}; + * if (isMyType(a)) { + * // a is narrowed to {foo: string} + * const _: {foo: string} = a; + * } + * ``` + */ +export function asUnoptional< + P extends Predicate, + T extends P extends Predicate ? T + : P extends Predicate ? T + : never, +>(pred: P): Predicate { + if (!hasAnnotation(pred, "optional")) { + return pred as Predicate; + } + return unannotate(pred, "optional") as Predicate; +} + +/** + * Check if the given type predicate has optional annotation. + */ +export function hasOptional< + P extends Predicate, + T extends P extends Predicate ? T + : P extends Predicate ? T + : never, +>( + pred: P, +): pred is P & WithOptional { + return hasAnnotation(pred, "optional"); +} diff --git a/as/optional_test.ts b/as/optional_test.ts new file mode 100644 index 0000000..884f82a --- /dev/null +++ b/as/optional_test.ts @@ -0,0 +1,233 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, stringify } from "../_testutil.ts"; +import type { Predicate } from "../is.ts"; +import { is } from "../is.ts"; +import { asOptional, asUnoptional } from "./optional.ts"; + +const examples = { + string: ["", "Hello world"], + number: [0, 1234567890], + bigint: [0n, 1234567890n], + boolean: [true, false], + array: [[], [0, 1, 2], ["a", "b", "c"], [0, "a", true]], + set: [new Set(), new Set([0, 1, 2]), new Set(["a", "b", "c"])], + record: [{}, { a: 0, b: 1, c: 2 }, { a: "a", b: "b", c: "c" }], + map: [ + new Map(), + new Map([["a", 0], ["b", 1], ["c", 2]]), + new Map([["a", "a"], ["b", "b"], ["c", "c"]]), + ], + syncFunction: [function a() {}, () => {}], + asyncFunction: [async function b() {}, async () => {}], + null: [null], + undefined: [undefined], + symbol: [Symbol("a"), Symbol("b"), Symbol("c")], + date: [new Date(1690248225000), new Date(0)], + promise: [new Promise(() => {})], +} as const; + +async function testWithExamples( + t: Deno.TestContext, + pred: Predicate, + opts?: { + validExamples?: (keyof typeof examples)[]; + excludeExamples?: (keyof typeof examples)[]; + }, +): Promise { + const { validExamples = [], excludeExamples = [] } = opts ?? {}; + const exampleEntries = (Object.entries(examples) as unknown as [ + name: keyof typeof examples, + example: unknown[], + ][]).filter(([k]) => !excludeExamples.includes(k)); + for (const [name, example] of exampleEntries) { + const expect = validExamples.includes(name); + for (const v of example) { + await t.step( + `returns ${expect} on ${stringify(v)}`, + () => { + assertEquals(pred(v), expect); + }, + ); + } + } +} + +Deno.test("asOptional", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, asOptional(is.Number).name); + // Nesting does nothing + await assertSnapshot(t, asOptional(asOptional(is.Number)).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = undefined; + if (asOptional(is.Number)(a)) { + assertType>(true); + } + }); + await t.step("with is.String", async (t) => { + await testWithExamples(t, asOptional(is.String), { + validExamples: ["string", "undefined"], + }); + }); + await t.step("with is.Number", async (t) => { + await testWithExamples(t, asOptional(is.Number), { + validExamples: ["number", "undefined"], + }); + }); + await t.step("with is.BigInt", async (t) => { + await testWithExamples(t, asOptional(is.BigInt), { + validExamples: ["bigint", "undefined"], + }); + }); + await t.step("with is.Boolean", async (t) => { + await testWithExamples(t, asOptional(is.Boolean), { + validExamples: ["boolean", "undefined"], + }); + }); + await t.step("with is.Array", async (t) => { + await testWithExamples(t, asOptional(is.Array), { + validExamples: ["array", "undefined"], + }); + }); + await t.step("with is.Set", async (t) => { + await testWithExamples(t, asOptional(is.Set), { + validExamples: ["set", "undefined"], + }); + }); + await t.step("with is.RecordObject", async (t) => { + await testWithExamples(t, asOptional(is.RecordObject), { + validExamples: ["record", "undefined"], + }); + }); + await t.step("with is.Function", async (t) => { + await testWithExamples(t, asOptional(is.Function), { + validExamples: ["syncFunction", "asyncFunction", "undefined"], + }); + }); + await t.step("with is.SyncFunction", async (t) => { + await testWithExamples(t, asOptional(is.SyncFunction), { + validExamples: ["syncFunction", "undefined"], + }); + }); + await t.step("with is.AsyncFunction", async (t) => { + await testWithExamples(t, asOptional(is.AsyncFunction), { + validExamples: ["asyncFunction", "undefined"], + }); + }); + await t.step("with is.Null", async (t) => { + await testWithExamples(t, asOptional(is.Null), { + validExamples: ["null", "undefined"], + }); + }); + await t.step("with is.Undefined", async (t) => { + await testWithExamples(t, asOptional(is.Undefined), { + validExamples: ["undefined"], + }); + }); + await t.step("with is.Symbol", async (t) => { + await testWithExamples(t, asOptional(is.Symbol), { + validExamples: ["symbol", "undefined"], + }); + }); +}); + +Deno.test("asUnoptional", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, asUnoptional(asOptional(is.Number)).name); + // Non optional does nothing + await assertSnapshot(t, asUnoptional(is.Number).name); + // Nesting does nothing + await assertSnapshot( + t, + asUnoptional(asUnoptional(asOptional(is.Number))).name, + ); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = undefined; + if (asUnoptional(asOptional(is.Number))(a)) { + assertType>(true); + } + if (asUnoptional(is.Number)(a)) { + assertType>(true); + } + }); + await t.step("with is.String", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.String)), { + validExamples: ["string"], + }); + }); + await t.step("with is.Number", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Number)), { + validExamples: ["number"], + }); + }); + await t.step("with is.BigInt", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.BigInt)), { + validExamples: ["bigint"], + }); + }); + await t.step("with is.Boolean", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Boolean)), { + validExamples: ["boolean"], + }); + }); + await t.step("with is.Array", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Array)), { + validExamples: ["array"], + }); + }); + await t.step("with is.Set", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Set)), { + validExamples: ["set"], + }); + }); + await t.step("with is.RecordObject", async (t) => { + await testWithExamples( + t, + asUnoptional(asOptional(is.RecordObject)), + { + validExamples: ["record"], + }, + ); + }); + await t.step("with is.Function", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Function)), { + validExamples: ["syncFunction", "asyncFunction"], + }); + }); + await t.step("with is.SyncFunction", async (t) => { + await testWithExamples( + t, + asUnoptional(asOptional(is.SyncFunction)), + { + validExamples: ["syncFunction"], + }, + ); + }); + await t.step("with is.AsyncFunction", async (t) => { + await testWithExamples( + t, + asUnoptional(asOptional(is.AsyncFunction)), + { + validExamples: ["asyncFunction"], + }, + ); + }); + await t.step("with is.Null", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Null)), { + validExamples: ["null"], + }); + }); + await t.step("with is.Undefined", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Undefined)), { + validExamples: ["undefined"], + }); + }); + await t.step("with is.Symbol", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Symbol)), { + validExamples: ["symbol"], + }); + }); +}); diff --git a/mod.ts b/mod.ts index 362dc5e..2539508 100644 --- a/mod.ts +++ b/mod.ts @@ -239,6 +239,7 @@ * @module */ +export * from "./as/mod.ts"; export * from "./is.ts"; export * from "./metadata.ts"; export * from "./util.ts"; From 665407787ebf264610978a960fa99fe5a35d15ca Mon Sep 17 00:00:00 2001 From: Alisue Date: Thu, 1 Aug 2024 02:59:17 +0900 Subject: [PATCH 04/22] :boom: Replace `OptionalOf/UnwrapOptinalOf` to `Optional/Unoptional` - `is.OptionalOf` -> `as.Optional` - `is.UnwrapOptionalOf` -> `as.Unoptional` --- README.md | 16 +- __snapshots__/is_test.ts.snap | 272 ++++++++++++------------- as/__snapshots__/optional_test.ts.snap | 8 +- as/mod_test.ts | 34 ++++ is.ts | 169 ++++----------- is_bench.ts | 9 +- is_test.ts | 219 ++------------------ mod.ts | 15 +- 8 files changed, 252 insertions(+), 490 deletions(-) create mode 100644 as/mod_test.ts diff --git a/README.md b/README.md index 411c0f2..daefa40 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ if (is.String(a)) { For more complex types, you can use `is*Of` (or `is.*Of`) functions like: ```typescript -import { is, PredicateType } from "@core/unknownutil"; +import { as, is, PredicateType } from "@core/unknownutil"; const isArticle = is.ObjectOf({ title: is.String, @@ -62,8 +62,8 @@ const isArticle = is.ObjectOf({ }), ]), ), - createTime: is.OptionalOf(is.InstanceOf(Date)), - updateTime: is.OptionalOf(is.InstanceOf(Date)), + createTime: as.Optional(is.InstanceOf(Date)), + updateTime: as.Optional(is.InstanceOf(Date)), }); // Infer the type of `Article` from the definition of `isArticle` @@ -94,7 +94,7 @@ Additionally, you can manipulate the predicate function returned from similar to TypeScript's `Pick`, `Omit`, `Partial`, `Required` utility types. ```typescript -import { is } from "@core/unknownutil"; +import { as, is } from "@core/unknownutil"; const isArticle = is.ObjectOf({ title: is.String, @@ -108,8 +108,8 @@ const isArticle = is.ObjectOf({ }), ]), ), - createTime: is.OptionalOf(is.InstanceOf(Date)), - updateTime: is.OptionalOf(is.InstanceOf(Date)), + createTime: as.Optional(is.InstanceOf(Date)), + updateTime: as.Optional(is.InstanceOf(Date)), }); const isArticleCreateParams = is.PickOf(isArticle, ["title", "body", "refs"]); @@ -146,8 +146,8 @@ const isArticleUpdateParams = is.OmitOf(isArticleCreateParams, ["title"]); const isArticlePatchParams = is.PartialOf(isArticleUpdateParams); // is equivalent to //const isArticlePatchParams = is.ObjectOf({ -// body: is.OptionalOf(is.String), -// refs: is.OptionalOf(is.ArrayOf( +// body: as.Optional(is.String), +// refs: as.Optional(is.ArrayOf( // is.UnionOf([ // is.String, // is.ObjectOf({ diff --git a/__snapshots__/is_test.ts.snap b/__snapshots__/is_test.ts.snap index 3e9f149..b089385 100644 --- a/__snapshots__/is_test.ts.snap +++ b/__snapshots__/is_test.ts.snap @@ -1,51 +1,56 @@ export const snapshot = {}; +snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; + +snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; + snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`; snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; -snapshot[`isUnionOf > returns properly named function 1`] = ` -"isUnionOf([ - isNumber, - isString, - isBoolean -])" -`; - -snapshot[`isOmitOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" -`; - -snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; - -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; +snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; +snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; -snapshot[`isUnwrapOptionalOf > returns properly named function 1`] = `"isNumber"`; +snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; -snapshot[`isUnwrapOptionalOf > returns properly named function 2`] = `"isNumber"`; +snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; -snapshot[`isUnwrapOptionalOf > returns properly named function 3`] = `"isNumber"`; +snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; -snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; +snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; -snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; -snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; -snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; +snapshot[`isPartialOf > returns properly named function 1`] = ` +"isObjectOf({ + a: asOptional(isNumber), + b: asOptional(isUnionOf([ + isString, + isUndefined + ])), + c: asOptional(isBoolean) +})" +`; -snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; +snapshot[`isPartialOf > returns properly named function 2`] = ` +"isObjectOf({ + a: asOptional(isNumber), + b: asOptional(isUnionOf([ + isString, + isUndefined + ])), + c: asOptional(isBoolean) +})" +`; -snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; -snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; snapshot[`isStrictOf > returns properly named function 1`] = ` "isStrictOf(isObjectOf({ @@ -65,29 +70,13 @@ snapshot[`isStrictOf > returns properly named function 3`] = ` }))" `; -snapshot[`isObjectOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString, - c: isBoolean -})" -`; - -snapshot[`isObjectOf > returns properly named function 2`] = `"isObjectOf({a: a})"`; - -snapshot[`isObjectOf > returns properly named function 3`] = ` -"isObjectOf({ - a: isObjectOf({ - b: isObjectOf({c: isBoolean}) - }) -})" -`; +snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; snapshot[`isParametersOf > returns properly named function 1`] = ` "isParametersOf([ isNumber, isString, - isOptionalOf(isBoolean) + asOptional(isBoolean) ], isArray)" `; @@ -101,96 +90,60 @@ snapshot[`isParametersOf > returns properly named function 4`] = ` isParametersOf([ isNumber, isString, - isOptionalOf(isBoolean) + asOptional(isBoolean) ], isArray) ], isArray) ])" `; -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; - -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; - -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; - -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; - -snapshot[`isRequiredOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isUnionOf([ - isString, - isUndefined - ]), - c: isBoolean -})" -`; - -snapshot[`isRequiredOf > returns properly named function 2`] = ` +snapshot[`isOmitOf > returns properly named function 1`] = ` "isObjectOf({ a: isNumber, - b: isUnionOf([ - isString, - isUndefined - ]), c: isBoolean })" `; -snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; - -snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; - -snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; - -snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(isNumber)"`; - -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; - -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; - -snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; - -snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; - -snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; +snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; -snapshot[`isPickOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" +snapshot[`isUnionOf > returns properly named function 1`] = ` +"isUnionOf([ + isNumber, + isString, + isBoolean +])" `; -snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; - -snapshot[`isTupleOf > returns properly named function 1`] = ` +snapshot[`isTupleOf > returns properly named function 1`] = ` "isTupleOf([ isNumber, isString, isBoolean -])" +], isArray)" `; -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; -snapshot[`isTupleOf > returns properly named function 3`] = ` +snapshot[`isTupleOf > returns properly named function 3`] = ` "isTupleOf([ isTupleOf([ isTupleOf([ isNumber, isString, isBoolean - ]) - ]) + ], isArray) + ], isArray) ])" `; +snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; + +snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; + snapshot[`isParametersOf > returns properly named function 1`] = ` "isParametersOf([ isNumber, isString, - isOptionalOf(isBoolean) + asOptional(isBoolean) ])" `; @@ -204,68 +157,52 @@ snapshot[`isParametersOf > returns properly named function 4`] = ` isParametersOf([ isNumber, isString, - isOptionalOf(isBoolean) + asOptional(isBoolean) ]) ]) ])" `; -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; +snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; +snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; -snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; +snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; -snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; +snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; -snapshot[`isTupleOf > returns properly named function 1`] = ` -"isTupleOf([ - isNumber, - isString, - isBoolean -], isArray)" -`; +snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; +snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; -snapshot[`isTupleOf > returns properly named function 3`] = ` -"isTupleOf([ - isTupleOf([ - isTupleOf([ - isNumber, - isString, - isBoolean - ], isArray) - ], isArray) -])" -`; +snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; -snapshot[`isPartialOf > returns properly named function 1`] = ` +snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; + +snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; + +snapshot[`isRequiredOf > returns properly named function 1`] = ` "isObjectOf({ - a: isOptionalOf(isNumber), - b: isOptionalOf(isUnionOf([ + a: isNumber, + b: isUnionOf([ isString, isUndefined - ])), - c: isOptionalOf(isBoolean) + ]), + c: isBoolean })" `; -snapshot[`isPartialOf > returns properly named function 2`] = ` +snapshot[`isRequiredOf > returns properly named function 2`] = ` "isObjectOf({ - a: isOptionalOf(isNumber), - b: isOptionalOf(isUnionOf([ + a: isNumber, + b: isUnionOf([ isString, isUndefined - ])), - c: isOptionalOf(isBoolean) + ]), + c: isBoolean })" `; -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; - -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; - snapshot[`isIntersectionOf > returns properly named function 1`] = ` "isObjectOf({ a: isNumber, @@ -281,3 +218,56 @@ snapshot[`isIntersectionOf > returns properly named function 3`] = ` isObjectOf({b: isString}) ])" `; + +snapshot[`isObjectOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isString, + c: isBoolean +})" +`; + +snapshot[`isObjectOf > returns properly named function 2`] = `"isObjectOf({a: a})"`; + +snapshot[`isObjectOf > returns properly named function 3`] = ` +"isObjectOf({ + a: isObjectOf({ + b: isObjectOf({c: isBoolean}) + }) +})" +`; + +snapshot[`isPickOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" +`; + +snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; + +snapshot[`isTupleOf > returns properly named function 1`] = ` +"isTupleOf([ + isNumber, + isString, + isBoolean +])" +`; + +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; + +snapshot[`isTupleOf > returns properly named function 3`] = ` +"isTupleOf([ + isTupleOf([ + isTupleOf([ + isNumber, + isString, + isBoolean + ]) + ]) +])" +`; + +snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; + +snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; diff --git a/as/__snapshots__/optional_test.ts.snap b/as/__snapshots__/optional_test.ts.snap index 0cbc43d..9bc9f6b 100644 --- a/as/__snapshots__/optional_test.ts.snap +++ b/as/__snapshots__/optional_test.ts.snap @@ -1,11 +1,11 @@ export const snapshot = {}; -snapshot[`asOptional > returns properly named function 1`] = `"asOptional(isNumber)"`; - -snapshot[`asOptional > returns properly named function 2`] = `"asOptional(isNumber)"`; - snapshot[`asUnoptional > returns properly named function 1`] = `"isNumber"`; snapshot[`asUnoptional > returns properly named function 2`] = `"isNumber"`; snapshot[`asUnoptional > returns properly named function 3`] = `"isNumber"`; + +snapshot[`asOptional > returns properly named function 1`] = `"asOptional(isNumber)"`; + +snapshot[`asOptional > returns properly named function 2`] = `"asOptional(isNumber)"`; diff --git a/as/mod_test.ts b/as/mod_test.ts new file mode 100644 index 0000000..b93e4aa --- /dev/null +++ b/as/mod_test.ts @@ -0,0 +1,34 @@ +import { assertEquals } from "@std/assert"; +import { globToRegExp } from "@std/path"; +import { as } from "./mod.ts"; + +const excludes = [ + "mod.ts", + "*_test.ts", +]; + +Deno.test("as", async (t) => { + // List all files under the directory + const names = await listAsFunctions(); + await t.step( + "must have all `as*` function aliases as entries", + () => { + assertEquals(Object.keys(as).sort(), names); + }, + ); +}); + +async function listAsFunctions(): Promise { + const patterns = excludes.map((p) => globToRegExp(p)); + const names: string[] = []; + for await (const entry of Deno.readDir(import.meta.dirname!)) { + if (!entry.isFile || !entry.name.endsWith(".ts")) continue; + if (patterns.some((p) => p.test(entry.name))) continue; + const mod = await import(import.meta.resolve(`./${entry.name}`)); + const isFunctionNames = Object.entries(mod) + .filter(([k, _]) => k.startsWith("as")) + .map(([k, _]) => k.slice(2)); + names.push(...isFunctionNames); + } + return names.toSorted(); +} diff --git a/is.ts b/is.ts index 049e3aa..0080430 100644 --- a/is.ts +++ b/is.ts @@ -1,12 +1,16 @@ import type { FlatType, TupleToIntersection } from "./_typeutil.ts"; import { - type GetMetadata, getMetadata, getPredicateFactoryMetadata, - type PredicateFactoryMetadata, setPredicateFactoryMetadata, type WithMetadata, } from "./metadata.ts"; +import { + asOptional, + asUnoptional, + hasOptional, + type WithOptional, +} from "./as/optional.ts"; const objectToString = Object.prototype.toString; const primitiveSet = new Set([ @@ -26,12 +30,12 @@ export type Predicate = (x: unknown) => x is T; * A type predicated by Predicate. * * ```ts - * import { is, type PredicateType } from "@core/unknownutil"; + * import { as, is, type PredicateType } from "@core/unknownutil"; * * const isPerson = is.ObjectOf({ * name: is.String, * age: is.Number, - * address: is.OptionalOf(is.String), + * address: as.Optional(is.String), * }); * * type Person = PredicateType; @@ -403,89 +407,6 @@ export function isPrimitive(x: unknown): x is Primitive { return x == null || primitiveSet.has(typeof x); } -/** - * Return `true` if the type of predicate function `x` is annotated as `Optional` - */ -export function isOptional

>( - x: P, -): x is P & WithMetadata { - const m = getMetadata(x); - if (m == null) return false; - return (m as PredicateFactoryMetadata).name === "isOptionalOf"; -} - -/** - * Return an `Optional` annotated type predicate function that returns `true` if the type of `x` is `T` or `undefined`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.OptionalOf(is.String); - * const a: unknown = "a"; - * if (isMyType(a)) { - * // a is narrowed to string | undefined - * const _: string | undefined = a; - * } - * ``` - */ -export function isOptionalOf( - pred: Predicate, -): - & Predicate - & WithMetadata { - if (isOptional(pred)) { - return pred as - & Predicate - & WithMetadata; - } - return Object.defineProperties( - setPredicateFactoryMetadata( - (x: unknown): x is T | undefined => x === undefined || pred(x), - { name: "isOptionalOf", args: [pred] }, - ), - { optional: { value: true as const } }, - ) as - & Predicate - & WithMetadata; -} - -type IsOptionalOfMetadata = { - name: "isOptionalOf"; - args: Parameters; -}; - -/** - * Return an `Optional` un-annotated type predicate function that returns `true` if the type of `x` is `T`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.UnwrapOptionalOf(is.OptionalOf(is.String)); - * const a: unknown = "a"; - * if (isMyType(a)) { - * // a is narrowed to string - * const _: string = a; - * } - * ``` - */ -export function isUnwrapOptionalOf

>( - pred: P, -): UnwrapOptionalOf

{ - if (!isOptional(pred)) return pred as UnwrapOptionalOf

; - const { args } = getPredicateFactoryMetadata(pred); - return args[0] as UnwrapOptionalOf

; -} - -type UnwrapOptionalOf = T extends - Predicate & WithMetadata - ? Predicate - : T extends Predicate ? T - : never; - /** * Return a type predicate function that returns `true` if the type of `x` is `T[]`. * @@ -659,20 +580,20 @@ type IsTupleOfMetadata = { /** * Return a type predicate function that returns `true` if the type of `x` is `ParametersOf` or `ParametersOf`. * - * This is similar to `TupleOf` or `TupleOf`, but if `is.OptionalOf()` is specified at the trailing, the trailing elements becomes optional and makes variable-length tuple. + * This is similar to `TupleOf` or `TupleOf`, but if `as.Optional()` is specified at the trailing, the trailing elements becomes optional and makes variable-length tuple. * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isMyType = is.ParametersOf([ * is.Number, - * is.OptionalOf(is.String), + * as.Optional(is.String), * is.Boolean, - * is.OptionalOf(is.Number), - * is.OptionalOf(is.String), - * is.OptionalOf(is.Boolean), + * as.Optional(is.Number), + * as.Optional(is.String), + * as.Optional(is.Boolean), * ] as const); * const a: unknown = [0, undefined, "a"]; * if (isMyType(a)) { @@ -684,13 +605,13 @@ type IsTupleOfMetadata = { * With `predElse`: * * ```ts - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isMyType = is.ParametersOf( * [ * is.Number, - * is.OptionalOf(is.String), - * is.OptionalOf(is.Boolean), + * as.Optional(is.String), + * as.Optional(is.Boolean), * ] as const, * is.ArrayOf(is.Number), * ); @@ -705,9 +626,9 @@ type IsTupleOfMetadata = { * used as `predTup`. If a type error occurs, try adding `as const` as follows: * * ```ts - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * - * const predTup = [is.Number, is.String, is.OptionalOf(is.Boolean)] as const; + * const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; * const isMyType = is.ParametersOf(predTup); * const a: unknown = [0, "a"]; * if (isMyType(a)) { @@ -739,7 +660,8 @@ export function isParametersOf< ): & Predicate | [...ParametersOf, ...PredicateType]> & WithMetadata { - const requiresLength = 1 + predTup.findLastIndex((pred) => !isOptional(pred)); + const requiresLength = 1 + + predTup.findLastIndex((pred) => !hasOptional(pred)); if (!predElse) { return setPredicateFactoryMetadata( (x: unknown): x is ParametersOf => { @@ -771,7 +693,7 @@ type ParametersOf = T extends readonly [] ? [] : T extends readonly [...infer P, infer R] // Tuple of predicates ? P extends Predicate[] - ? R extends Predicate & WithMetadata + ? R extends Predicate & WithOptional // Last parameter is optional ? [...ParametersOf

, PredicateType?] // Last parameter is NOT optional @@ -1006,17 +928,17 @@ type IsMapOfMetadata = { * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * - * If `is.OptionalOf()` is specified in the predicate function, the property becomes optional. + * If `as.Optional` is specified in the predicate function, the property becomes optional. * * The number of keys of `x` must be greater than or equal to the number of keys of `predObj`. * * ```ts - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isMyType = is.ObjectOf({ * a: is.Number, * b: is.String, - * c: is.OptionalOf(is.Boolean), + * c: as.Optional(is.Boolean), * }); * const a: unknown = { a: 0, b: "a", other: "other" }; * if (isMyType(a)) { @@ -1053,20 +975,16 @@ export function isObjectOf< ); } -type WithOptional = - | WithMetadata>> - | { optional: true }; // For backward compatibility - type ObjectOf>> = FlatType< // Non optional & { - [K in keyof T as T[K] extends WithOptional ? never : K]: T[K] extends - Predicate ? U : never; + [K in keyof T as T[K] extends WithOptional ? never : K]: + T[K] extends Predicate ? U : never; } // Optional & { - [K in keyof T as T[K] extends WithOptional ? K : never]?: T[K] extends - Predicate ? U : never; + [K in keyof T as T[K] extends WithOptional ? K : never]?: + T[K] extends Predicate ? U : never; } >; @@ -1080,17 +998,17 @@ type IsObjectOfMetadata = { * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * - * If `is.OptionalOf()` is specified in the predicate function, the property becomes optional. + * If `as.Optional` is specified in the predicate function, the property becomes optional. * * The number of keys of `x` must be equal to the number of non optional keys of `predObj`. * * ```ts - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isMyType = is.StrictOf(is.ObjectOf({ * a: is.Number, * b: is.String, - * c: is.OptionalOf(is.Boolean), + * c: as.Optional(is.Boolean), * })); * const a: unknown = { a: 0, b: "a", other: "other" }; * if (isMyType(a)) { @@ -1362,12 +1280,12 @@ type IsIntersectionOfMetadata = { * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```typescript - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isMyType = is.RequiredOf(is.ObjectOf({ * a: is.Number, * b: is.UnionOf([is.String, is.Undefined]), - * c: is.OptionalOf(is.Boolean), + * c: as.Optional(is.Boolean), * })); * const a: unknown = { a: 0, b: "b", c: true, other: "other" }; * if (isMyType(a)) { @@ -1385,7 +1303,7 @@ export function isRequiredOf< & WithMetadata { const { args } = getPredicateFactoryMetadata(pred); const predObj = Object.fromEntries( - Object.entries(args[0]).map(([k, v]) => [k, isUnwrapOptionalOf(v)]), + Object.entries(args[0]).map(([k, v]) => [k, asUnoptional(v)]), ); return isObjectOf(predObj) as & Predicate>> @@ -1398,12 +1316,12 @@ export function isRequiredOf< * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```typescript - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isMyType = is.PartialOf(is.ObjectOf({ * a: is.Number, * b: is.UnionOf([is.String, is.Undefined]), - * c: is.OptionalOf(is.Boolean), + * c: as.Optional(is.Boolean), * })); * const a: unknown = { a: undefined, other: "other" }; * if (isMyType(a)) { @@ -1422,7 +1340,7 @@ export function isPartialOf< & WithMetadata { const { args } = getPredicateFactoryMetadata(pred); const predObj = Object.fromEntries( - Object.entries(args[0]).map(([k, v]) => [k, isOptionalOf(v)]), + Object.entries(args[0]).map(([k, v]) => [k, asOptional(v)]), ); return isObjectOf(predObj) as & Predicate>> @@ -1435,12 +1353,12 @@ export function isPartialOf< * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```typescript - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isMyType = is.PickOf(is.ObjectOf({ * a: is.Number, * b: is.String, - * c: is.OptionalOf(is.Boolean), + * c: as.Optional(is.Boolean), * }), ["a", "c"]); * const a: unknown = { a: 0, b: "a", other: "other" }; * if (isMyType(a)) { @@ -1475,12 +1393,12 @@ export function isPickOf< * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```typescript - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isMyType = is.OmitOf(is.ObjectOf({ * a: is.Number, * b: is.String, - * c: is.OptionalOf(is.Boolean), + * c: as.Optional(is.Boolean), * }), ["a", "c"]); * const a: unknown = { a: 0, b: "a", other: "other" }; * if (isMyType(a)) { @@ -1528,8 +1446,6 @@ export const is = { Number: isNumber, ObjectOf: isObjectOf, OmitOf: isOmitOf, - Optional: isOptional, - OptionalOf: isOptionalOf, ParametersOf: isParametersOf, PartialOf: isPartialOf, PickOf: isPickOf, @@ -1550,5 +1466,4 @@ export const is = { UniformTupleOf: isUniformTupleOf, UnionOf: isUnionOf, Unknown: isUnknown, - UnwrapOptionalOf: isUnwrapOptionalOf, }; diff --git a/is_bench.ts b/is_bench.ts index 1367232..7b77e32 100644 --- a/is_bench.ts +++ b/is_bench.ts @@ -1,3 +1,4 @@ +import { as } from "./as/mod.ts"; import { is } from "./is.ts"; const cs: unknown[] = [ @@ -414,21 +415,21 @@ Deno.bench({ }); Deno.bench({ - name: "is.OptionalOf", + name: "as.Optional", fn: () => { - const pred = is.OptionalOf(is.String); + const pred = as.Optional(is.String); for (const c of cs) { pred(c); } }, }); -const isOptionalOfPred = is.OptionalOf(is.String); +const asOptionalPred = as.Optional(is.String); Deno.bench({ name: "is.OptionalOf (pre)", fn: () => { for (const c of cs) { - isOptionalOfPred(c); + asOptionalPred(c); } }, }); diff --git a/is_test.ts b/is_test.ts index 374ed7a..bc4cde0 100644 --- a/is_test.ts +++ b/is_test.ts @@ -3,6 +3,7 @@ import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; import { type Equal, stringify } from "./_testutil.ts"; import type { Predicate, PredicateType } from "./is.ts"; +import { as } from "./as/mod.ts"; import { is, isAny, @@ -23,7 +24,6 @@ import { isNumber, isObjectOf, isOmitOf, - isOptionalOf, isParametersOf, isPartialOf, isPickOf, @@ -44,7 +44,6 @@ import { isUniformTupleOf, isUnionOf, isUnknown, - isUnwrapOptionalOf, } from "./is.ts"; const examples = { @@ -231,184 +230,6 @@ Deno.test("isPrimitive", async (t) => { }); }); -Deno.test("isOptionalOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isOptionalOf(isNumber).name); - // Nesting does nothing - await assertSnapshot(t, isOptionalOf(isOptionalOf(isNumber)).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = undefined; - if (isOptionalOf(isNumber)(a)) { - assertType>(true); - } - }); - await t.step("with isString", async (t) => { - await testWithExamples(t, isOptionalOf(isString), { - validExamples: ["string", "undefined"], - }); - }); - await t.step("with isNumber", async (t) => { - await testWithExamples(t, isOptionalOf(isNumber), { - validExamples: ["number", "undefined"], - }); - }); - await t.step("with isBigInt", async (t) => { - await testWithExamples(t, isOptionalOf(isBigInt), { - validExamples: ["bigint", "undefined"], - }); - }); - await t.step("with isBoolean", async (t) => { - await testWithExamples(t, isOptionalOf(isBoolean), { - validExamples: ["boolean", "undefined"], - }); - }); - await t.step("with isArray", async (t) => { - await testWithExamples(t, isOptionalOf(isArray), { - validExamples: ["array", "undefined"], - }); - }); - await t.step("with isSet", async (t) => { - await testWithExamples(t, isOptionalOf(isSet), { - validExamples: ["set", "undefined"], - }); - }); - await t.step("with isRecordObject", async (t) => { - await testWithExamples(t, isOptionalOf(isRecordObject), { - validExamples: ["record", "undefined"], - }); - }); - await t.step("with isFunction", async (t) => { - await testWithExamples(t, isOptionalOf(isFunction), { - validExamples: ["syncFunction", "asyncFunction", "undefined"], - }); - }); - await t.step("with isSyncFunction", async (t) => { - await testWithExamples(t, isOptionalOf(isSyncFunction), { - validExamples: ["syncFunction", "undefined"], - }); - }); - await t.step("with isAsyncFunction", async (t) => { - await testWithExamples(t, isOptionalOf(isAsyncFunction), { - validExamples: ["asyncFunction", "undefined"], - }); - }); - await t.step("with isNull", async (t) => { - await testWithExamples(t, isOptionalOf(isNull), { - validExamples: ["null", "undefined"], - }); - }); - await t.step("with isUndefined", async (t) => { - await testWithExamples(t, isOptionalOf(isUndefined), { - validExamples: ["undefined"], - }); - }); - await t.step("with isSymbol", async (t) => { - await testWithExamples(t, isOptionalOf(isSymbol), { - validExamples: ["symbol", "undefined"], - }); - }); -}); - -Deno.test("isUnwrapOptionalOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isUnwrapOptionalOf(isOptionalOf(isNumber)).name); - // Non optional does nothing - await assertSnapshot(t, isUnwrapOptionalOf(isNumber).name); - // Nesting does nothing - await assertSnapshot( - t, - isUnwrapOptionalOf(isUnwrapOptionalOf(isOptionalOf(isNumber))).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = undefined; - if (isUnwrapOptionalOf(isOptionalOf(isNumber))(a)) { - assertType>(true); - } - if (isUnwrapOptionalOf(isNumber)(a)) { - assertType>(true); - } - }); - await t.step("with isString", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isString)), { - validExamples: ["string"], - }); - }); - await t.step("with isNumber", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isNumber)), { - validExamples: ["number"], - }); - }); - await t.step("with isBigInt", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isBigInt)), { - validExamples: ["bigint"], - }); - }); - await t.step("with isBoolean", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isBoolean)), { - validExamples: ["boolean"], - }); - }); - await t.step("with isArray", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isArray)), { - validExamples: ["array"], - }); - }); - await t.step("with isSet", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isSet)), { - validExamples: ["set"], - }); - }); - await t.step("with isRecordObject", async (t) => { - await testWithExamples( - t, - isUnwrapOptionalOf(isOptionalOf(isRecordObject)), - { - validExamples: ["record"], - }, - ); - }); - await t.step("with isFunction", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isFunction)), { - validExamples: ["syncFunction", "asyncFunction"], - }); - }); - await t.step("with isSyncFunction", async (t) => { - await testWithExamples( - t, - isUnwrapOptionalOf(isOptionalOf(isSyncFunction)), - { - validExamples: ["syncFunction"], - }, - ); - }); - await t.step("with isAsyncFunction", async (t) => { - await testWithExamples( - t, - isUnwrapOptionalOf(isOptionalOf(isAsyncFunction)), - { - validExamples: ["asyncFunction"], - }, - ); - }); - await t.step("with isNull", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isNull)), { - validExamples: ["null"], - }); - }); - await t.step("with isUndefined", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isUndefined)), { - validExamples: ["undefined"], - }); - }); - await t.step("with isSymbol", async (t) => { - await testWithExamples(t, isUnwrapOptionalOf(isOptionalOf(isSymbol)), { - validExamples: ["symbol"], - }); - }); -}); - Deno.test("isArrayOf", async (t) => { await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isArrayOf(isNumber).name); @@ -561,7 +382,7 @@ Deno.test("isParametersOf", async (t) => { await t.step("returns properly named function", async (t) => { await assertSnapshot( t, - isParametersOf([isNumber, isString, isOptionalOf(isBoolean)]).name, + isParametersOf([isNumber, isString, as.Optional(isBoolean)]).name, ); await assertSnapshot( t, @@ -576,17 +397,17 @@ Deno.test("isParametersOf", async (t) => { t, isParametersOf([ isParametersOf([ - isParametersOf([isNumber, isString, isOptionalOf(isBoolean)]), + isParametersOf([isNumber, isString, as.Optional(isBoolean)]), ]), ]).name, ); }); await t.step("returns proper type predicate", () => { const predTup = [ - isOptionalOf(isNumber), + as.Optional(isNumber), isString, - isOptionalOf(isString), - isOptionalOf(isBoolean), + as.Optional(isString), + as.Optional(isBoolean), ] as const; const a: unknown = [0, "a"]; if (isParametersOf(predTup)(a)) { @@ -596,12 +417,12 @@ Deno.test("isParametersOf", async (t) => { } }); await t.step("returns true on T tuple", () => { - const predTup = [isNumber, isString, isOptionalOf(isBoolean)] as const; + const predTup = [isNumber, isString, as.Optional(isBoolean)] as const; assertEquals(isParametersOf(predTup)([0, "a", true]), true); assertEquals(isParametersOf(predTup)([0, "a"]), true); }); await t.step("returns false on non T tuple", () => { - const predTup = [isNumber, isString, isOptionalOf(isBoolean)] as const; + const predTup = [isNumber, isString, as.Optional(isBoolean)] as const; assertEquals(isParametersOf(predTup)([0, 1, 2]), false); assertEquals(isParametersOf(predTup)([0, "a", true, 0]), false); }); @@ -618,7 +439,7 @@ Deno.test("isParametersOf", async (t) => { await t.step("returns properly named function", async (t) => { await assertSnapshot( t, - isParametersOf([isNumber, isString, isOptionalOf(isBoolean)], isArray) + isParametersOf([isNumber, isString, as.Optional(isBoolean)], isArray) .name, ); await assertSnapshot( @@ -637,7 +458,7 @@ Deno.test("isParametersOf", async (t) => { isParametersOf([ isParametersOf( [isParametersOf( - [isNumber, isString, isOptionalOf(isBoolean)], + [isNumber, isString, as.Optional(isBoolean)], isArray, )], isArray, @@ -647,10 +468,10 @@ Deno.test("isParametersOf", async (t) => { }); await t.step("returns proper type predicate", () => { const predTup = [ - isOptionalOf(isNumber), + as.Optional(isNumber), isString, - isOptionalOf(isString), - isOptionalOf(isBoolean), + as.Optional(isString), + as.Optional(isBoolean), ] as const; const predElse = isArrayOf(isNumber); const a: unknown = [0, "a"]; @@ -666,7 +487,7 @@ Deno.test("isParametersOf", async (t) => { } }); await t.step("returns true on T tuple", () => { - const predTup = [isNumber, isString, isOptionalOf(isBoolean)] as const; + const predTup = [isNumber, isString, as.Optional(isBoolean)] as const; const predElse = isArrayOf(isNumber); assertEquals( isParametersOf(predTup, predElse)([0, "a", true, 0, 1, 2]), @@ -679,7 +500,7 @@ Deno.test("isParametersOf", async (t) => { assertEquals(isParametersOf(predTup, predElse)([0, "a"]), true); }); await t.step("returns false on non T tuple", () => { - const predTup = [isNumber, isString, isOptionalOf(isBoolean)] as const; + const predTup = [isNumber, isString, as.Optional(isBoolean)] as const; const predElse = isArrayOf(isString); assertEquals(isParametersOf(predTup, predElse)([0, 1, 2, 0, 1, 2]), false); assertEquals(isParametersOf(predTup, predElse)([0, "a", 0, 1, 2]), false); @@ -1101,7 +922,7 @@ Deno.test("isStrictOf", async (t) => { const predObj = { a: isNumber, b: isUnionOf([isString, isUndefined]), - c: isOptionalOf(isBoolean), + c: as.Optional(isBoolean), }; const a: unknown = { a: 0, b: "a" }; if (isStrictOf(isObjectOf(predObj))(a)) { @@ -1114,7 +935,7 @@ Deno.test("isStrictOf", async (t) => { const predObj = { a: isNumber, b: isUnionOf([isString, isUndefined]), - c: isOptionalOf(isBoolean), + c: as.Optional(isBoolean), }; assertEquals( isStrictOf(isObjectOf(predObj))({ a: 0, b: "a", c: true }), @@ -1135,7 +956,7 @@ Deno.test("isStrictOf", async (t) => { const predObj = { a: isNumber, b: isUnionOf([isString, isUndefined]), - c: isOptionalOf(isBoolean), + c: as.Optional(isBoolean), }; assertEquals( isStrictOf(isObjectOf(predObj))({ a: 0, b: "a", c: "" }), @@ -1406,7 +1227,7 @@ Deno.test("isRequiredOf", async (t) => { const pred = isObjectOf({ a: isNumber, b: isUnionOf([isString, isUndefined]), - c: isOptionalOf(isBoolean), + c: as.Optional(isBoolean), }); await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isRequiredOf(pred).name); @@ -1447,7 +1268,7 @@ Deno.test("isPartialOf", async (t) => { const pred = isObjectOf({ a: isNumber, b: isUnionOf([isString, isUndefined]), - c: isOptionalOf(isBoolean), + c: as.Optional(isBoolean), }); await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isPartialOf(pred).name); diff --git a/mod.ts b/mod.ts index 2539508..61947cc 100644 --- a/mod.ts +++ b/mod.ts @@ -25,6 +25,7 @@ * * ```typescript * import { + * as, * is, * PredicateType, * } from "@core/unknownutil"; @@ -41,8 +42,8 @@ * }), * ]), * ), - * createTime: is.OptionalOf(is.InstanceOf(Date)), - * updateTime: is.OptionalOf(is.InstanceOf(Date)), + * createTime: as.Optional(is.InstanceOf(Date)), + * updateTime: as.Optional(is.InstanceOf(Date)), * }); * * // Infer the type of `Article` from the definition of `isArticle` @@ -73,7 +74,7 @@ * similar to TypeScript's `Pick`, `Omit`, `Partial`, `Required` utility types. * * ```typescript - * import { is } from "@core/unknownutil"; + * import { as, is } from "@core/unknownutil"; * * const isArticle = is.ObjectOf({ * title: is.String, @@ -87,8 +88,8 @@ * }), * ]), * ), - * createTime: is.OptionalOf(is.InstanceOf(Date)), - * updateTime: is.OptionalOf(is.InstanceOf(Date)), + * createTime: as.Optional(is.InstanceOf(Date)), + * updateTime: as.Optional(is.InstanceOf(Date)), * }); * * const isArticleCreateParams = is.PickOf(isArticle, ["title", "body", "refs"]); @@ -125,8 +126,8 @@ * const isArticlePatchParams = is.PartialOf(isArticleUpdateParams); * // is equivalent to * //const isArticlePatchParams = is.ObjectOf({ - * // body: is.OptionalOf(is.String), - * // refs: is.OptionalOf(is.ArrayOf( + * // body: as.Optional(is.String), + * // refs: as.Optional(is.ArrayOf( * // is.UnionOf([ * // is.String, * // is.ObjectOf({ From 0d2dbb335a05f7d59c2eacf7f77c9ba58d1e17f2 Mon Sep 17 00:00:00 2001 From: Alisue Date: Thu, 1 Aug 2024 04:31:28 +0900 Subject: [PATCH 05/22] :boom: Remove `metadata` module and use `_annotation` instead --- _annotation.ts | 15 +- as/optional.ts | 16 +- as/optional_test.ts | 3 +- is.ts | 355 +++++++++++++++++++------------------------- metadata.ts | 67 --------- mod.ts | 1 - 6 files changed, 162 insertions(+), 295 deletions(-) delete mode 100644 metadata.ts diff --git a/_annotation.ts b/_annotation.ts index fa1da1d..3db8c71 100644 --- a/_annotation.ts +++ b/_annotation.ts @@ -30,17 +30,8 @@ export function hasAnnotation( } /** - * Annotation for readonly. + * Annotation for optional. */ -export type WithReadonly

> = { - readonly: P; -}; - -export type PredObj = Record>; - -/** - * Annotation for predObj. - */ -export type WithPredObj = { - predObj: T; +export type WithOptional = { + optional: Predicate; }; diff --git a/as/optional.ts b/as/optional.ts index 698c2b6..e64ad8b 100644 --- a/as/optional.ts +++ b/as/optional.ts @@ -1,13 +1,11 @@ -import type { Predicate } from "../is.ts"; import { rewriteName } from "../_funcutil.ts"; -import { annotate, hasAnnotation, unannotate } from "../_annotation.ts"; - -/** - * Annotation for optional. - */ -export type WithOptional = { - optional: Predicate; -}; +import type { Predicate } from "../is.ts"; +import { + annotate, + hasAnnotation, + unannotate, + type WithOptional, +} from "../_annotation.ts"; /** * Return an `Optional` annotated type predicate function that returns `true` if the type of `x` is `T` or `undefined`. diff --git a/as/optional_test.ts b/as/optional_test.ts index 884f82a..5e74d14 100644 --- a/as/optional_test.ts +++ b/as/optional_test.ts @@ -2,8 +2,7 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; import { type Equal, stringify } from "../_testutil.ts"; -import type { Predicate } from "../is.ts"; -import { is } from "../is.ts"; +import { is, type Predicate } from "../is.ts"; import { asOptional, asUnoptional } from "./optional.ts"; const examples = { diff --git a/is.ts b/is.ts index 0080430..50e5c2a 100644 --- a/is.ts +++ b/is.ts @@ -1,16 +1,7 @@ import type { FlatType, TupleToIntersection } from "./_typeutil.ts"; -import { - getMetadata, - getPredicateFactoryMetadata, - setPredicateFactoryMetadata, - type WithMetadata, -} from "./metadata.ts"; -import { - asOptional, - asUnoptional, - hasOptional, - type WithOptional, -} from "./as/optional.ts"; +import { rewriteName } from "./_funcutil.ts"; +import { annotate, hasAnnotation, type WithOptional } from "./_annotation.ts"; +import { asOptional, asUnoptional, hasOptional } from "./as/optional.ts"; const objectToString = Object.prototype.toString; const primitiveSet = new Set([ @@ -425,18 +416,14 @@ export function isPrimitive(x: unknown): x is Primitive { */ export function isArrayOf( pred: Predicate, -): Predicate & WithMetadata { - return setPredicateFactoryMetadata( +): Predicate { + return rewriteName( (x: unknown): x is T[] => isArray(x) && x.every(pred), - { name: "isArrayOf", args: [pred] }, + "isArrayOf", + pred, ); } -type IsArrayOfMetadata = { - name: "isArrayOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `Set`. * @@ -455,8 +442,8 @@ type IsArrayOfMetadata = { */ export function isSetOf( pred: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( +): Predicate> { + return rewriteName( (x: unknown): x is Set => { if (!isSet(x)) return false; for (const v of x.values()) { @@ -464,15 +451,11 @@ export function isSetOf( } return true; }, - { name: "isSetOf", args: [pred] }, + "isSetOf", + pred, ); } -type IsSetOfMetadata = { - name: "isSetOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `TupleOf` or `TupleOf`. * @@ -524,37 +507,34 @@ export function isTupleOf< T extends readonly [Predicate, ...Predicate[]], >( predTup: T, -): Predicate> & WithMetadata; +): Predicate>; export function isTupleOf< T extends readonly [Predicate, ...Predicate[]], E extends Predicate, >( predTup: T, predElse: E, -): - & Predicate<[...TupleOf, ...PredicateType]> - & WithMetadata; +): Predicate<[...TupleOf, ...PredicateType]>; export function isTupleOf< T extends readonly [Predicate, ...Predicate[]], E extends Predicate, >( predTup: T, predElse?: E, -): - & Predicate | [...TupleOf, ...PredicateType]> - & WithMetadata { +): Predicate | [...TupleOf, ...PredicateType]> { if (!predElse) { - return setPredicateFactoryMetadata( + return rewriteName( (x: unknown): x is TupleOf => { if (!isArray(x) || x.length !== predTup.length) { return false; } return predTup.every((pred, i) => pred(x[i])); }, - { name: "isTupleOf", args: [predTup] }, + "isTupleOf", + predTup, ); } else { - return setPredicateFactoryMetadata( + return rewriteName( (x: unknown): x is [...TupleOf, ...PredicateType] => { if (!isArray(x) || x.length < predTup.length) { return false; @@ -563,7 +543,9 @@ export function isTupleOf< const tail = x.slice(predTup.length); return predTup.every((pred, i) => pred(head[i])) && predElse(tail); }, - { name: "isTupleOf", args: [predTup, predElse] }, + "isTupleOf", + predTup, + predElse, ); } } @@ -572,11 +554,6 @@ type TupleOf = { -readonly [P in keyof T]: T[P] extends Predicate ? U : never; }; -type IsTupleOfMetadata = { - name: "isTupleOf"; - args: [Parameters[0], Parameters[1]?]; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `ParametersOf` or `ParametersOf`. * @@ -641,29 +618,25 @@ export function isParametersOf< T extends readonly [...Predicate[]], >( predTup: T, -): Predicate> & WithMetadata; +): Predicate>; export function isParametersOf< T extends readonly [...Predicate[]], E extends Predicate, >( predTup: T, predElse: E, -): - & Predicate<[...ParametersOf, ...PredicateType]> - & WithMetadata; +): Predicate<[...ParametersOf, ...PredicateType]>; export function isParametersOf< T extends readonly [...Predicate[]], E extends Predicate, >( predTup: T, predElse?: E, -): - & Predicate | [...ParametersOf, ...PredicateType]> - & WithMetadata { +): Predicate | [...ParametersOf, ...PredicateType]> { const requiresLength = 1 + predTup.findLastIndex((pred) => !hasOptional(pred)); if (!predElse) { - return setPredicateFactoryMetadata( + return rewriteName( (x: unknown): x is ParametersOf => { if ( !isArray(x) || x.length < requiresLength || x.length > predTup.length @@ -672,10 +645,11 @@ export function isParametersOf< } return predTup.every((pred, i) => pred(x[i])); }, - { name: "isParametersOf", args: [predTup] }, + "isParametersOf", + predTup, ); } else { - return setPredicateFactoryMetadata( + return rewriteName( (x: unknown): x is [...ParametersOf, ...PredicateType] => { if (!isArray(x) || x.length < requiresLength) { return false; @@ -684,7 +658,9 @@ export function isParametersOf< const tail = x.slice(predTup.length); return predTup.every((pred, i) => pred(head[i])) && predElse(tail); }, - { name: "isParametersOf", args: [predTup, predElse] }, + "isParametersOf", + predTup, + predElse, ); } } @@ -702,14 +678,6 @@ type ParametersOf = T extends readonly [] ? [] // Array of predicates : TupleOf; -type IsParametersOfMetadata = { - name: "isParametersOf"; - args: [ - Parameters[0], - Parameters[1]?, - ]; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `UniformTupleOf`. * @@ -742,15 +710,17 @@ type IsParametersOfMetadata = { export function isUniformTupleOf( n: N, pred: Predicate = isAny, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( +): Predicate> { + return rewriteName( (x: unknown): x is UniformTupleOf => { if (!isArray(x) || x.length !== n) { return false; } return x.every((v) => pred(v)); }, - { name: "isUniformTupleOf", args: [n, pred] }, + "isUniformTupleOf", + n, + pred, ); } @@ -761,11 +731,6 @@ type UniformTupleOf< R extends readonly T[] = [], > = R["length"] extends N ? R : UniformTupleOf; -type IsUniformTupleOfMetadata = { - name: "isUniformTupleOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is an Object instance that satisfies `Record`. * @@ -801,8 +766,8 @@ type IsUniformTupleOfMetadata = { export function isRecordObjectOf( pred: Predicate, predKey?: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( +): Predicate> { + return rewriteName( (x: unknown): x is Record => { if (!isRecordObject(x)) return false; for (const k in x) { @@ -811,15 +776,12 @@ export function isRecordObjectOf( } return true; }, - { name: "isRecordObjectOf", args: [pred, predKey] }, + "isRecordObjectOf", + pred, + predKey, ); } -type IsRecordObjectOfMetadata = { - name: "isRecordObjectOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` satisfies `Record`. * @@ -852,8 +814,8 @@ type IsRecordObjectOfMetadata = { export function isRecordOf( pred: Predicate, predKey?: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( +): Predicate> { + return rewriteName( (x: unknown): x is Record => { if (!isRecord(x)) return false; for (const k in x) { @@ -862,15 +824,12 @@ export function isRecordOf( } return true; }, - { name: "isRecordOf", args: [pred, predKey] }, + "isRecordOf", + pred, + predKey, ); } -type IsRecordOfMetadata = { - name: "isRecordOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `Map`. * @@ -903,8 +862,8 @@ type IsRecordOfMetadata = { export function isMapOf( pred: Predicate, predKey?: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( +): Predicate> { + return rewriteName( (x: unknown): x is Map => { if (!isMap(x)) return false; for (const entry of x.entries()) { @@ -914,15 +873,12 @@ export function isMapOf( } return true; }, - { name: "isMapOf", args: [pred, predKey] }, + "isMapOf", + pred, + predKey, ); } -type IsMapOfMetadata = { - name: "isMapOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `ObjectOf`. * @@ -950,47 +906,44 @@ type IsMapOfMetadata = { */ export function isObjectOf< T extends Record>, ->( - predObj: T, -): Predicate> & WithMetadata; -export function isObjectOf< - T extends Record>, ->( - predObj: T, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is ObjectOf => { - if ( - x == null || - typeof x !== "object" && typeof x !== "function" || - Array.isArray(x) - ) return false; - // Check each values - for (const k in predObj) { - if (!predObj[k]((x as T)[k])) return false; - } - return true; - }, - { name: "isObjectOf", args: [predObj] }, +>(predObj: T): Predicate> & WithPredObj { + return annotate( + rewriteName( + (x: unknown): x is ObjectOf => { + if ( + x == null || + typeof x !== "object" && typeof x !== "function" || + Array.isArray(x) + ) return false; + // Check each values + for (const k in predObj) { + if (!predObj[k]((x as T)[k])) return false; + } + return true; + }, + "isObjectOf", + predObj, + ), + "predObj", + predObj, ); } type ObjectOf>> = FlatType< - // Non optional + // Optional & { - [K in keyof T as T[K] extends WithOptional ? never : K]: + [K in keyof T as T[K] extends WithOptional ? K : never]?: T[K] extends Predicate ? U : never; } - // Optional + // Non optional & { - [K in keyof T as T[K] extends WithOptional ? K : never]?: + [K in keyof T as T[K] extends WithOptional ? never : K]: T[K] extends Predicate ? U : never; } >; -type IsObjectOfMetadata = { - name: "isObjectOf"; - args: [Parameters[0]]; +type WithPredObj>> = { + predObj: T; }; /** @@ -1016,31 +969,29 @@ type IsObjectOfMetadata = { * } * ``` */ -export function isStrictOf>( +export function isStrictOf< + T extends Record, + P extends Record>, +>( pred: & Predicate - & WithMetadata, + & WithPredObj

, ): & Predicate - & WithMetadata { - const { args } = getPredicateFactoryMetadata(pred); - const s = new Set(Object.keys(args[0])); - return setPredicateFactoryMetadata( + & WithPredObj

{ + const s = new Set(Object.keys(pred.predObj)); + return rewriteName( (x: unknown): x is T => { if (!pred(x)) return false; // deno-lint-ignore no-explicit-any const ks = Object.keys(x as any); return ks.length <= s.size && ks.every((k) => s.has(k)); }, - { name: "isStrictOf", args: [pred] }, - ); + "isStrictOf", + pred, + ) as Predicate & WithPredObj

; } -type IsStrictOfMetadata = { - name: "isStrictOf"; - args: Parameters; -}; - /** * Return `true` if the type of `x` is instance of `ctor`. * @@ -1060,18 +1011,14 @@ type IsStrictOfMetadata = { // deno-lint-ignore no-explicit-any export function isInstanceOf unknown>( ctor: T, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( +): Predicate> { + return rewriteName( (x: unknown): x is InstanceType => x instanceof ctor, - { name: "isInstanceOf", args: [ctor] }, + "isInstanceOf", + ctor, ); } -type IsInstanceOfMetadata = { - name: "isInstanceOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is a literal type of `pred`. * @@ -1090,18 +1037,14 @@ type IsInstanceOfMetadata = { */ export function isLiteralOf( literal: T, -): Predicate & WithMetadata { - return setPredicateFactoryMetadata( +): Predicate { + return rewriteName( (x: unknown): x is T => x === literal, - { name: "isLiteralOf", args: [literal] }, + "isLiteralOf", + literal, ); } -type IsLiteralOfMetadata = { - name: "isLiteralOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is one of literal type in `preds`. * @@ -1120,19 +1063,15 @@ type IsLiteralOfMetadata = { */ export function isLiteralOneOf( literals: T, -): Predicate & WithMetadata { +): Predicate { const s = new Set(literals); - return setPredicateFactoryMetadata( + return rewriteName( (x: unknown): x is T[number] => s.has(x as T[number]), - { name: "isLiteralOneOf", args: [literals] }, + "isLiteralOneOf", + literals, ); } -type IsLiteralOneOfMetadata = { - name: "isLiteralOneOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `UnionOf`. * @@ -1168,10 +1107,15 @@ export function isUnionOf< T extends readonly [Predicate, ...Predicate[]], >( preds: T, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is UnionOf => preds.some((pred) => pred(x)), - { name: "isUnionOf", args: [preds] }, +): Predicate> & WithUnion { + return annotate( + rewriteName( + (x: unknown): x is UnionOf => preds.some((pred) => pred(x)), + "isUnionOf", + preds, + ), + "union", + preds, ); } @@ -1179,9 +1123,10 @@ type UnionOf = T extends readonly [Predicate, ...infer R] ? U | UnionOf : never; -type IsUnionOfMetadata = { - name: "isUnionOf"; - args: Parameters; +type WithUnion< + T extends readonly [Predicate, ...Predicate[]], +> = { + union: T; }; /** @@ -1223,12 +1168,17 @@ type IsUnionOfMetadata = { */ export function isIntersectionOf< T extends readonly [ - Predicate & WithMetadata, - ...(Predicate & WithMetadata)[], + Predicate & WithPredObj>>, + ...( + & Predicate + & WithPredObj>> + )[], ], >( preds: T, -): Predicate> & WithMetadata; +): + & Predicate> + & WithPredObj>>; export function isIntersectionOf< T extends readonly [Predicate], >( @@ -1238,7 +1188,9 @@ export function isIntersectionOf< T extends readonly [Predicate, ...Predicate[]], >( preds: T, -): Predicate> & WithMetadata; +): + & Predicate> + & WithPredObj>>; export function isIntersectionOf< T extends readonly [Predicate, ...Predicate[]], >( @@ -1246,14 +1198,13 @@ export function isIntersectionOf< ): | Predicate | Predicate> - & WithMetadata { + & WithPredObj>> { const predObj = {}; const restPreds = preds.filter((pred) => { - const meta = getMetadata(pred); - if ((meta as IsObjectOfMetadata)?.name !== "isObjectOf") { + if (!hasAnnotation(pred, "predObj")) { return true; } - Object.assign(predObj, (meta as IsObjectOfMetadata).args[0]); + Object.assign(predObj, pred.predObj); }); if (restPreds.length < preds.length) { restPreds.push(isObjectOf(predObj)); @@ -1261,19 +1212,15 @@ export function isIntersectionOf< if (restPreds.length === 1) { return restPreds[0]; } - return setPredicateFactoryMetadata( + return rewriteName( (x: unknown): x is IntersectionOf => restPreds.every((pred) => pred(x)), - { name: "isIntersectionOf", args: [preds] }, + "isIntersectionOf", + preds, ); } type IntersectionOf = TupleToIntersection>; -type IsIntersectionOfMetadata = { - name: "isIntersectionOf"; - args: Parameters; -}; - /** * Return a type predicate function that returns `true` if the type of `x` is `Required>`. * @@ -1296,18 +1243,18 @@ type IsIntersectionOfMetadata = { */ export function isRequiredOf< T extends Record, + P extends Record>, >( - pred: Predicate & WithMetadata, + pred: Predicate & WithPredObj

, ): & Predicate>> - & WithMetadata { - const { args } = getPredicateFactoryMetadata(pred); + & WithPredObj

{ const predObj = Object.fromEntries( - Object.entries(args[0]).map(([k, v]) => [k, asUnoptional(v)]), + Object.entries(pred.predObj).map(([k, v]) => [k, asUnoptional(v)]), ); return isObjectOf(predObj) as & Predicate>> - & WithMetadata; + & WithPredObj

; } /** @@ -1333,18 +1280,18 @@ export function isRequiredOf< */ export function isPartialOf< T extends Record, + P extends Record>, >( - pred: Predicate & WithMetadata, + pred: Predicate & WithPredObj

, ): & Predicate>> - & WithMetadata { - const { args } = getPredicateFactoryMetadata(pred); + & WithPredObj

{ const predObj = Object.fromEntries( - Object.entries(args[0]).map(([k, v]) => [k, asOptional(v)]), - ); + Object.entries(pred.predObj).map(([k, v]) => [k, asOptional(v)]), + ) as Record>; return isObjectOf(predObj) as & Predicate>> - & WithMetadata; + & WithPredObj

; } /** @@ -1370,21 +1317,21 @@ export function isPartialOf< */ export function isPickOf< T extends Record, + P extends Record>, K extends keyof T, >( - pred: Predicate & WithMetadata, + pred: Predicate & WithPredObj

, keys: K[], ): & Predicate>> - & WithMetadata { + & WithPredObj

{ const s = new Set(keys); - const { args } = getPredicateFactoryMetadata(pred); const predObj = Object.fromEntries( - Object.entries(args[0]).filter(([k]) => s.has(k as K)), + Object.entries(pred.predObj).filter(([k]) => s.has(k as K)), ); return isObjectOf(predObj) as & Predicate>> - & WithMetadata; + & WithPredObj

; } /** @@ -1410,21 +1357,21 @@ export function isPickOf< */ export function isOmitOf< T extends Record, + P extends Record>, K extends keyof T, >( - pred: Predicate & WithMetadata, + pred: Predicate & WithPredObj

, keys: K[], ): & Predicate>> - & WithMetadata { + & WithPredObj

{ const s = new Set(keys); - const { args } = getPredicateFactoryMetadata(pred); const predObj = Object.fromEntries( - Object.entries(args[0]).filter(([k]) => !s.has(k as K)), + Object.entries(pred.predObj).filter(([k]) => !s.has(k as K)), ); return isObjectOf(predObj) as & Predicate>> - & WithMetadata; + & WithPredObj

; } export const is = { diff --git a/metadata.ts b/metadata.ts deleted file mode 100644 index 8c5da06..0000000 --- a/metadata.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { Predicate } from "./is.ts"; -import { inspect } from "./inspect.ts"; - -/** - * A type that has metadata. - */ -export type WithMetadata = { - __unknownutil_metadata: T; -}; - -/** - * Get typeof the metadata - */ -export type GetMetadata = T extends WithMetadata ? M : never; - -/** - * Get metadata from the given value - */ -export function getMetadata(v: unknown): T | undefined { - if (v == null) return undefined; - // deno-lint-ignore no-explicit-any - return (v as any).__unknownutil_metadata; -} - -/** - * Metadata of a predicate factory function. - */ -export type PredicateFactoryMetadata = { - name: string; - args: unknown[]; -}; - -/** - * Set metadata to a predicate factory function. - */ -export function setPredicateFactoryMetadata< - P extends Predicate, - M extends PredicateFactoryMetadata, ->( - pred: P, - metadata: M, -): P & WithMetadata { - let cachedName: string | undefined; - return Object.defineProperties(pred, { - __unknownutil_metadata: { - value: metadata, - configurable: true, - }, - name: { - get: () => { - if (cachedName) return cachedName; - const { name, args } = metadata; - cachedName = `${name}(${args.map((v) => inspect(v)).join(", ")})`; - return cachedName; - }, - }, - }) as P & WithMetadata; -} - -/** - * Get metadata from a predicate factory function. - */ -export function getPredicateFactoryMetadata( - v: WithMetadata, -): M { - return getMetadata(v) as M; -} diff --git a/mod.ts b/mod.ts index 61947cc..e28fc9e 100644 --- a/mod.ts +++ b/mod.ts @@ -242,5 +242,4 @@ export * from "./as/mod.ts"; export * from "./is.ts"; -export * from "./metadata.ts"; export * from "./util.ts"; From d990e4d9bf1994f5c0fbaaa0fa9d008d9ade50b1 Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 02:17:30 +0900 Subject: [PATCH 06/22] :boom: Individually define predicate functions Additionally, the following breaking changes are included in this commit - The default optional argument of `UniformTupleOf` is changed from `isAny` to `undefined` - The `isBigInt` function is renamed to `isBigint` - Some utility type functions are removed --- __snapshots__/is_test.ts.snap | 273 ---- _annotation.ts | 11 +- _testutil.ts | 51 + _typeutil.ts | 5 - as/__snapshots__/optional_test.ts.snap | 8 +- as/optional.ts | 2 +- as/optional_test.ts | 11 +- deno.jsonc | 5 +- is.ts | 1416 ----------------- is/__snapshots__/array_of_test.ts.snap | 5 + is/__snapshots__/instance_of_test.ts.snap | 5 + is/__snapshots__/intersection_of_test.ts.snap | 17 + is/__snapshots__/literal_of_test.ts.snap | 15 + is/__snapshots__/literal_one_of_test.ts.snap | 3 + is/__snapshots__/map_of_test.ts.snap | 9 + is/__snapshots__/object_of_test.ts.snap | 19 + is/__snapshots__/omit_of_test.ts.snap | 10 + is/__snapshots__/parameters_of_test.ts.snap | 49 + is/__snapshots__/partial_of_test.ts.snap | 23 + is/__snapshots__/pick_of_test.ts.snap | 10 + .../record_object_of_test.ts.snap | 9 + is/__snapshots__/record_of_test.ts.snap | 9 + is/__snapshots__/required_of_test.ts.snap | 23 + is/__snapshots__/set_of_test.ts.snap | 5 + is/__snapshots__/strict_of_test.ts.snap | 19 + is/__snapshots__/tuple_of_test.ts.snap | 45 + .../uniform_tuple_of_test.ts.snap | 7 + is/__snapshots__/union_of_test.ts.snap | 9 + is/any.ts | 17 + is/any_test.ts | 24 + is/array.ts | 18 + is/array_of.ts | 29 + is/array_of_test.ts | 32 + is/array_test.ts | 6 + is/async_function.ts | 20 + is/async_function_test.ts | 8 + is/bigint.ts | 16 + is/bigint_test.ts | 6 + is/boolean.ts | 16 + is/boolean_test.ts | 6 + is/function.ts | 16 + is/function_test.ts | 8 + is/instance_of.ts | 29 + is/instance_of_test.ts | 47 + is/intersection_of.ts | 105 ++ is/intersection_of_test.ts | 135 ++ is/literal_of.ts | 28 + is/literal_of_test.ts | 32 + is/literal_one_of.ts | 29 + is/literal_one_of_test.ts | 27 + is/map.ts | 16 + is/map_of.ts | 52 + is/map_of_test.ts | 66 + is/map_test.ts | 8 + is/mod.ts | 79 + is/mod_test.ts | 33 + is/null.ts | 16 + is/null_test.ts | 6 + is/nullish.ts | 16 + is/nullish_test.ts | 8 + is/number.ts | 16 + is/number_test.ts | 6 + is/object_of.ts | 71 + is/object_of_test.ts | 90 ++ is/omit_of.ts | 44 + is/omit_of_test.ts | 51 + is/parameters_of.ts | 131 ++ is/parameters_of_test.ts | 153 ++ is/partial_of.ts | 42 + is/partial_of_test.ts | 43 + is/pick_of.ts | 44 + is/pick_of_test.ts | 51 + is/primitive.ts | 26 + is/primitive_test.ts | 16 + is/record.ts | 26 + is/record_object.ts | 26 + is/record_object_of.ts | 54 + is/record_object_of_test.ts | 74 + is/record_object_test.ts | 8 + is/record_of.ts | 51 + is/record_of_test.ts | 74 + is/record_test.ts | 8 + is/required_of.ts | 41 + is/required_of_test.ts | 48 + is/set.ts | 16 + is/set_of.ts | 35 + is/set_of_test.ts | 32 + is/set_test.ts | 6 + is/strict_of.ts | 49 + is/strict_of_test.ts | 158 ++ is/string.ts | 16 + is/string_test.ts | 6 + is/symbol.ts | 16 + is/symbol_test.ts | 6 + is/sync_function.ts | 20 + is/sync_function_test.ts | 8 + is/tuple_of.ts | 103 ++ is/tuple_of_test.ts | 103 ++ is/undefined.ts | 16 + is/undefined_test.ts | 6 + is/uniform_tuple_of.ts | 56 + is/uniform_tuple_of_test.ts | 43 + is/union_of.ts | 54 + is/union_of_test.ts | 43 + is/unknown.ts | 16 + is/unknown_test.ts | 24 + is_bench.ts | 6 +- is_test.ts | 1410 ---------------- mod.ts | 3 +- type.ts | 38 + util.ts | 2 +- 111 files changed, 3389 insertions(+), 3123 deletions(-) delete mode 100644 __snapshots__/is_test.ts.snap delete mode 100644 is.ts create mode 100644 is/__snapshots__/array_of_test.ts.snap create mode 100644 is/__snapshots__/instance_of_test.ts.snap create mode 100644 is/__snapshots__/intersection_of_test.ts.snap create mode 100644 is/__snapshots__/literal_of_test.ts.snap create mode 100644 is/__snapshots__/literal_one_of_test.ts.snap create mode 100644 is/__snapshots__/map_of_test.ts.snap create mode 100644 is/__snapshots__/object_of_test.ts.snap create mode 100644 is/__snapshots__/omit_of_test.ts.snap create mode 100644 is/__snapshots__/parameters_of_test.ts.snap create mode 100644 is/__snapshots__/partial_of_test.ts.snap create mode 100644 is/__snapshots__/pick_of_test.ts.snap create mode 100644 is/__snapshots__/record_object_of_test.ts.snap create mode 100644 is/__snapshots__/record_of_test.ts.snap create mode 100644 is/__snapshots__/required_of_test.ts.snap create mode 100644 is/__snapshots__/set_of_test.ts.snap create mode 100644 is/__snapshots__/strict_of_test.ts.snap create mode 100644 is/__snapshots__/tuple_of_test.ts.snap create mode 100644 is/__snapshots__/uniform_tuple_of_test.ts.snap create mode 100644 is/__snapshots__/union_of_test.ts.snap create mode 100644 is/any.ts create mode 100644 is/any_test.ts create mode 100644 is/array.ts create mode 100644 is/array_of.ts create mode 100644 is/array_of_test.ts create mode 100644 is/array_test.ts create mode 100644 is/async_function.ts create mode 100644 is/async_function_test.ts create mode 100644 is/bigint.ts create mode 100644 is/bigint_test.ts create mode 100644 is/boolean.ts create mode 100644 is/boolean_test.ts create mode 100644 is/function.ts create mode 100644 is/function_test.ts create mode 100644 is/instance_of.ts create mode 100644 is/instance_of_test.ts create mode 100644 is/intersection_of.ts create mode 100644 is/intersection_of_test.ts create mode 100644 is/literal_of.ts create mode 100644 is/literal_of_test.ts create mode 100644 is/literal_one_of.ts create mode 100644 is/literal_one_of_test.ts create mode 100644 is/map.ts create mode 100644 is/map_of.ts create mode 100644 is/map_of_test.ts create mode 100644 is/map_test.ts create mode 100644 is/mod.ts create mode 100644 is/mod_test.ts create mode 100644 is/null.ts create mode 100644 is/null_test.ts create mode 100644 is/nullish.ts create mode 100644 is/nullish_test.ts create mode 100644 is/number.ts create mode 100644 is/number_test.ts create mode 100644 is/object_of.ts create mode 100644 is/object_of_test.ts create mode 100644 is/omit_of.ts create mode 100644 is/omit_of_test.ts create mode 100644 is/parameters_of.ts create mode 100644 is/parameters_of_test.ts create mode 100644 is/partial_of.ts create mode 100644 is/partial_of_test.ts create mode 100644 is/pick_of.ts create mode 100644 is/pick_of_test.ts create mode 100644 is/primitive.ts create mode 100644 is/primitive_test.ts create mode 100644 is/record.ts create mode 100644 is/record_object.ts create mode 100644 is/record_object_of.ts create mode 100644 is/record_object_of_test.ts create mode 100644 is/record_object_test.ts create mode 100644 is/record_of.ts create mode 100644 is/record_of_test.ts create mode 100644 is/record_test.ts create mode 100644 is/required_of.ts create mode 100644 is/required_of_test.ts create mode 100644 is/set.ts create mode 100644 is/set_of.ts create mode 100644 is/set_of_test.ts create mode 100644 is/set_test.ts create mode 100644 is/strict_of.ts create mode 100644 is/strict_of_test.ts create mode 100644 is/string.ts create mode 100644 is/string_test.ts create mode 100644 is/symbol.ts create mode 100644 is/symbol_test.ts create mode 100644 is/sync_function.ts create mode 100644 is/sync_function_test.ts create mode 100644 is/tuple_of.ts create mode 100644 is/tuple_of_test.ts create mode 100644 is/undefined.ts create mode 100644 is/undefined_test.ts create mode 100644 is/uniform_tuple_of.ts create mode 100644 is/uniform_tuple_of_test.ts create mode 100644 is/union_of.ts create mode 100644 is/union_of_test.ts create mode 100644 is/unknown.ts create mode 100644 is/unknown_test.ts delete mode 100644 is_test.ts create mode 100644 type.ts diff --git a/__snapshots__/is_test.ts.snap b/__snapshots__/is_test.ts.snap deleted file mode 100644 index b089385..0000000 --- a/__snapshots__/is_test.ts.snap +++ /dev/null @@ -1,273 +0,0 @@ -export const snapshot = {}; - -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; - -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; - -snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`; - -snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; - -snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; - -snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; - -snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; - -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; - -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; - -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; - -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; - -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; - -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; - -snapshot[`isPartialOf > returns properly named function 1`] = ` -"isObjectOf({ - a: asOptional(isNumber), - b: asOptional(isUnionOf([ - isString, - isUndefined - ])), - c: asOptional(isBoolean) -})" -`; - -snapshot[`isPartialOf > returns properly named function 2`] = ` -"isObjectOf({ - a: asOptional(isNumber), - b: asOptional(isUnionOf([ - isString, - isUndefined - ])), - c: asOptional(isBoolean) -})" -`; - -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; - -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; - -snapshot[`isStrictOf > returns properly named function 1`] = ` -"isStrictOf(isObjectOf({ - a: isNumber, - b: isString, - c: isBoolean -}))" -`; - -snapshot[`isStrictOf > returns properly named function 2`] = `"isStrictOf(isObjectOf({a: a}))"`; - -snapshot[`isStrictOf > returns properly named function 3`] = ` -"isStrictOf(isObjectOf({ - a: isStrictOf(isObjectOf({ - b: isStrictOf(isObjectOf({c: isBoolean})) - })) -}))" -`; - -snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; - -snapshot[`isParametersOf > returns properly named function 1`] = ` -"isParametersOf([ - isNumber, - isString, - asOptional(isBoolean) -], isArray)" -`; - -snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)], isArrayOf(isString))"`; - -snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([], isArrayOf(isString))"`; - -snapshot[`isParametersOf > returns properly named function 4`] = ` -"isParametersOf([ - isParametersOf([ - isParametersOf([ - isNumber, - isString, - asOptional(isBoolean) - ], isArray) - ], isArray) -])" -`; - -snapshot[`isOmitOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" -`; - -snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; - -snapshot[`isUnionOf > returns properly named function 1`] = ` -"isUnionOf([ - isNumber, - isString, - isBoolean -])" -`; - -snapshot[`isTupleOf > returns properly named function 1`] = ` -"isTupleOf([ - isNumber, - isString, - isBoolean -], isArray)" -`; - -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; - -snapshot[`isTupleOf > returns properly named function 3`] = ` -"isTupleOf([ - isTupleOf([ - isTupleOf([ - isNumber, - isString, - isBoolean - ], isArray) - ], isArray) -])" -`; - -snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; - -snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; - -snapshot[`isParametersOf > returns properly named function 1`] = ` -"isParametersOf([ - isNumber, - isString, - asOptional(isBoolean) -])" -`; - -snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)])"`; - -snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([])"`; - -snapshot[`isParametersOf > returns properly named function 4`] = ` -"isParametersOf([ - isParametersOf([ - isParametersOf([ - isNumber, - isString, - asOptional(isBoolean) - ]) - ]) -])" -`; - -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; - -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; - -snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; - -snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; - -snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; - -snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; - -snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; - -snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; - -snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; - -snapshot[`isRequiredOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isUnionOf([ - isString, - isUndefined - ]), - c: isBoolean -})" -`; - -snapshot[`isRequiredOf > returns properly named function 2`] = ` -"isObjectOf({ - a: isNumber, - b: isUnionOf([ - isString, - isUndefined - ]), - c: isBoolean -})" -`; - -snapshot[`isIntersectionOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString -})" -`; - -snapshot[`isIntersectionOf > returns properly named function 2`] = `"isString"`; - -snapshot[`isIntersectionOf > returns properly named function 3`] = ` -"isIntersectionOf([ - isFunction, - isObjectOf({b: isString}) -])" -`; - -snapshot[`isObjectOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString, - c: isBoolean -})" -`; - -snapshot[`isObjectOf > returns properly named function 2`] = `"isObjectOf({a: a})"`; - -snapshot[`isObjectOf > returns properly named function 3`] = ` -"isObjectOf({ - a: isObjectOf({ - b: isObjectOf({c: isBoolean}) - }) -})" -`; - -snapshot[`isPickOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" -`; - -snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; - -snapshot[`isTupleOf > returns properly named function 1`] = ` -"isTupleOf([ - isNumber, - isString, - isBoolean -])" -`; - -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; - -snapshot[`isTupleOf > returns properly named function 3`] = ` -"isTupleOf([ - isTupleOf([ - isTupleOf([ - isNumber, - isString, - isBoolean - ]) - ]) -])" -`; - -snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; - -snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; diff --git a/_annotation.ts b/_annotation.ts index 3db8c71..0a892ed 100644 --- a/_annotation.ts +++ b/_annotation.ts @@ -1,4 +1,4 @@ -import type { Predicate } from "./is.ts"; +import type { Predicate } from "./type.ts"; export type Fn = (...args: unknown[]) => unknown; @@ -32,6 +32,13 @@ export function hasAnnotation( /** * Annotation for optional. */ -export type WithOptional = { +export type WithOptional = { optional: Predicate; }; + +/** + * Annotation for predObj. + */ +export type WithPredObj>> = { + predObj: T; +}; diff --git a/_testutil.ts b/_testutil.ts index 11e9e6f..e323b78 100644 --- a/_testutil.ts +++ b/_testutil.ts @@ -1,3 +1,54 @@ +import { assertEquals } from "@std/assert"; +import type { Predicate } from "./type.ts"; + +const examples = { + string: ["", "Hello world"], + number: [0, 1234567890], + bigint: [0n, 1234567890n], + boolean: [true, false], + array: [[], [0, 1, 2], ["a", "b", "c"], [0, "a", true]], + set: [new Set(), new Set([0, 1, 2]), new Set(["a", "b", "c"])], + record: [{}, { a: 0, b: 1, c: 2 }, { a: "a", b: "b", c: "c" }], + map: [ + new Map(), + new Map([["a", 0], ["b", 1], ["c", 2]]), + new Map([["a", "a"], ["b", "b"], ["c", "c"]]), + ], + syncFunction: [function a() {}, () => {}], + asyncFunction: [async function b() {}, async () => {}], + null: [null], + undefined: [undefined], + symbol: [Symbol("a"), Symbol("b"), Symbol("c")], + date: [new Date(1690248225000), new Date(0)], + promise: [new Promise(() => {})], +} as const; + +export async function testWithExamples( + t: Deno.TestContext, + pred: Predicate, + opts?: { + validExamples?: (keyof typeof examples)[]; + excludeExamples?: (keyof typeof examples)[]; + }, +): Promise { + const { validExamples = [], excludeExamples = [] } = opts ?? {}; + const exampleEntries = (Object.entries(examples) as unknown as [ + name: keyof typeof examples, + example: unknown[], + ][]).filter(([k]) => !excludeExamples.includes(k)); + for (const [name, example] of exampleEntries) { + const expect = validExamples.includes(name); + for (const v of example) { + await t.step( + `returns ${expect} on ${stringify(v)}`, + () => { + assertEquals(pred(v), expect); + }, + ); + } + } +} + // It seems 'IsExact' in deno_std is false positive so use `Equal` in type-challenges // https://github.com/type-challenges/type-challenges/blob/e77262dba62e9254451f661cb4fe5517ffd1d933/utils/index.d.ts#L7-L9 export type Equal = (() => T extends X ? 1 : 2) extends diff --git a/_typeutil.ts b/_typeutil.ts index 36e7437..a61941f 100644 --- a/_typeutil.ts +++ b/_typeutil.ts @@ -1,8 +1,3 @@ export type FlatType = T extends Record ? { [K in keyof T]: FlatType } : T; - -export type TupleToIntersection = T extends readonly [] ? never - : T extends readonly [infer U] ? U - : T extends readonly [infer U, ...infer R] ? U & TupleToIntersection - : never; diff --git a/as/__snapshots__/optional_test.ts.snap b/as/__snapshots__/optional_test.ts.snap index 9bc9f6b..0cbc43d 100644 --- a/as/__snapshots__/optional_test.ts.snap +++ b/as/__snapshots__/optional_test.ts.snap @@ -1,11 +1,11 @@ export const snapshot = {}; +snapshot[`asOptional > returns properly named function 1`] = `"asOptional(isNumber)"`; + +snapshot[`asOptional > returns properly named function 2`] = `"asOptional(isNumber)"`; + snapshot[`asUnoptional > returns properly named function 1`] = `"isNumber"`; snapshot[`asUnoptional > returns properly named function 2`] = `"isNumber"`; snapshot[`asUnoptional > returns properly named function 3`] = `"isNumber"`; - -snapshot[`asOptional > returns properly named function 1`] = `"asOptional(isNumber)"`; - -snapshot[`asOptional > returns properly named function 2`] = `"asOptional(isNumber)"`; diff --git a/as/optional.ts b/as/optional.ts index e64ad8b..1ca182d 100644 --- a/as/optional.ts +++ b/as/optional.ts @@ -1,5 +1,5 @@ import { rewriteName } from "../_funcutil.ts"; -import type { Predicate } from "../is.ts"; +import type { Predicate } from "../type.ts"; import { annotate, hasAnnotation, diff --git a/as/optional_test.ts b/as/optional_test.ts index 5e74d14..694036d 100644 --- a/as/optional_test.ts +++ b/as/optional_test.ts @@ -2,7 +2,8 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; import { type Equal, stringify } from "../_testutil.ts"; -import { is, type Predicate } from "../is.ts"; +import type { Predicate } from "../type.ts"; +import { is } from "../is/mod.ts"; import { asOptional, asUnoptional } from "./optional.ts"; const examples = { @@ -75,8 +76,8 @@ Deno.test("asOptional", async (t) => { validExamples: ["number", "undefined"], }); }); - await t.step("with is.BigInt", async (t) => { - await testWithExamples(t, asOptional(is.BigInt), { + await t.step("with is.Bigint", async (t) => { + await testWithExamples(t, asOptional(is.Bigint), { validExamples: ["bigint", "undefined"], }); }); @@ -162,8 +163,8 @@ Deno.test("asUnoptional", async (t) => { validExamples: ["number"], }); }); - await t.step("with is.BigInt", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.BigInt)), { + await t.step("with is.Bigint", async (t) => { + await testWithExamples(t, asUnoptional(asOptional(is.Bigint)), { validExamples: ["bigint"], }); }); diff --git a/deno.jsonc b/deno.jsonc index 1ba1f1a..d509ed9 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -3,10 +3,11 @@ "version": "0.0.0", "exports": "./mod.ts", "imports": { + "@core/unknownutil": "./mod.ts", "@deno/dnt": "jsr:@deno/dnt@^0.41.1", "@std/assert": "jsr:@std/assert@^0.221.0", - "@std/testing": "jsr:@std/testing@^0.221.0", - "@core/unknownutil": "./mod.ts" + "@std/path": "jsr:@std/path@^1.0.2", + "@std/testing": "jsr:@std/testing@^0.221.0" }, "tasks": { "build-npm": "deno run -A scripts/build_npm.ts $(git describe --tags --always --dirty)", diff --git a/is.ts b/is.ts deleted file mode 100644 index 50e5c2a..0000000 --- a/is.ts +++ /dev/null @@ -1,1416 +0,0 @@ -import type { FlatType, TupleToIntersection } from "./_typeutil.ts"; -import { rewriteName } from "./_funcutil.ts"; -import { annotate, hasAnnotation, type WithOptional } from "./_annotation.ts"; -import { asOptional, asUnoptional, hasOptional } from "./as/optional.ts"; - -const objectToString = Object.prototype.toString; -const primitiveSet = new Set([ - "string", - "number", - "bigint", - "boolean", - "symbol", -]); - -/** - * A type predicate function. - */ -export type Predicate = (x: unknown) => x is T; - -/** - * A type predicated by Predicate. - * - * ```ts - * import { as, is, type PredicateType } from "@core/unknownutil"; - * - * const isPerson = is.ObjectOf({ - * name: is.String, - * age: is.Number, - * address: as.Optional(is.String), - * }); - * - * type Person = PredicateType; - * // Above is equivalent to the following type - * // type Person = { - * // name: string; - * // age: number; - * // address: string | undefined; - * // }; - */ -export type PredicateType

= P extends Predicate ? T : never; - -/** - * Assume `x is `any` and always return `true` regardless of the type of `x`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a = "a"; - * if (is.Any(a)) { - * // a is narrowed to any - * const _: any = a; - * } - * ``` - */ -// deno-lint-ignore no-explicit-any -export function isAny(_x: unknown): _x is any { - return true; -} - -/** - * Assume `x` is `unknown` and always return `true` regardless of the type of `x`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a = "a"; - * if (is.Unknown(a)) { - * // a is narrowed to unknown - * const _: unknown = a; - * } - * ``` - */ -export function isUnknown(_x: unknown): _x is unknown { - return true; -} - -/** - * Return `true` if the type of `x` is `string`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = "a"; - * if (is.String(a)) { - * // a is narrowed to string - * const _: string = a; - * } - * ``` - */ -export function isString(x: unknown): x is string { - return typeof x === "string"; -} - -/** - * Return `true` if the type of `x` is `number`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = 0; - * if (is.Number(a)) { - * // a is narrowed to number - * const _: number = a; - * } - * ``` - */ -export function isNumber(x: unknown): x is number { - return typeof x === "number"; -} - -/** - * Return `true` if the type of `x` is `bigint`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = 0n; - * if (is.BigInt(a)) { - * // a is narrowed to bigint - * const _: bigint = a; - * } - * ``` - */ -export function isBigInt(x: unknown): x is bigint { - return typeof x === "bigint"; -} - -/** - * Return `true` if the type of `x` is `boolean`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = true; - * if (is.Boolean(a)) { - * // a is narrowed to boolean - * const _: boolean = a; - * } - * ``` - */ -export function isBoolean(x: unknown): x is boolean { - return typeof x === "boolean"; -} - -/** - * Return `true` if the type of `x` is `unknown[]`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = [0, 1, 2]; - * if (is.Array(a)) { - * // a is narrowed to unknown[] - * const _: unknown[] = a; - * } - * ``` - */ -export function isArray( - x: unknown, -): x is unknown[] { - return Array.isArray(x); -} - -/** - * Return `true` if the type of `x` is `Set`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = new Set([0, 1, 2]); - * if (is.Set(a)) { - * // a is narrowed to Set - * const _: Set = a; - * } - * ``` - */ -export function isSet(x: unknown): x is Set { - return x instanceof Set; -} - -/** - * Return `true` if the type of `x` is an object instance that satisfies `Record`. - * - * Note that this function check if the `x` is an instance of `Object`. - * Use `isRecordLike` instead if you want to check if the `x` satisfies the `Record` type. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = {"a": 0, "b": 1}; - * if (is.RecordObject(a)) { - * // a is narrowed to Record - * const _: Record = a; - * } - * - * const b: unknown = new Set(); - * if (is.RecordObject(b)) { - * // b is not a raw object, so it is not narrowed - * } - * ``` - */ -export function isRecordObject( - x: unknown, -): x is Record { - return x != null && typeof x === "object" && x.constructor === Object; -} - -/** - * Return `true` if the type of `x` satisfies `Record`. - * - * Note that this function returns `true` for ambiguous instances like `Set`, `Map`, `Date`, `Promise`, etc. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = {"a": 0, "b": 1}; - * if (is.Record(a)) { - * // a is narrowed to Record - * const _: Record = a; - * } - * - * const b: unknown = new Set(); - * if (is.Record(b)) { - * // b is narrowed to Record - * const _: Record = b; - * } - * ``` - */ -export function isRecord( - x: unknown, -): x is Record { - return x != null && !Array.isArray(x) && typeof x === "object"; -} - -/** - * Return `true` if the type of `x` is `Map`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = new Map([["a", 0], ["b", 1]]); - * if (is.Map(a)) { - * // a is narrowed to Map - * const _: Map = a; - * } - * ``` - */ -export function isMap(x: unknown): x is Map { - return x instanceof Map; -} - -/** - * Return `true` if the type of `x` is `function`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = () => {}; - * if (is.Function(a)) { - * // a is narrowed to (...args: unknown[]) => unknown - * const _: ((...args: unknown[]) => unknown) = a; - * } - * ``` - */ -export function isFunction(x: unknown): x is (...args: unknown[]) => unknown { - return x instanceof Function; -} - -/** - * Return `true` if the type of `x` is `function` (non async function). - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = () => {}; - * if (is.Function(a)) { - * // a is narrowed to (...args: unknown[]) => unknown - * const _: ((...args: unknown[]) => unknown) = a; - * } - * ``` - */ -export function isSyncFunction( - x: unknown, -): x is (...args: unknown[]) => unknown { - return objectToString.call(x) === "[object Function]"; -} - -/** - * Return `true` if the type of `x` is `function` (async function). - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = async () => {}; - * if (is.Function(a)) { - * // a is narrowed to (...args: unknown[]) => Promise - * const _: ((...args: unknown[]) => unknown) = a; - * } - * ``` - */ -export function isAsyncFunction( - x: unknown, -): x is (...args: unknown[]) => Promise { - return objectToString.call(x) === "[object AsyncFunction]"; -} - -/** - * Return `true` if the type of `x` is `null`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = null; - * if (is.Null(a)) { - * // a is narrowed to null - * const _: null = a; - * } - * ``` - */ -export function isNull(x: unknown): x is null { - return x === null; -} - -/** - * Return `true` if the type of `x` is `undefined`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = undefined; - * if (is.Undefined(a)) { - * // a is narrowed to undefined - * const _: undefined = a; - * } - * ``` - */ -export function isUndefined(x: unknown): x is undefined { - return typeof x === "undefined"; -} - -/** - * Return `true` if the type of `x` is `null` or `undefined`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = null; - * if (is.Nullish(a)) { - * // a is narrowed to null | undefined - * const _: (null | undefined) = a; - * } - * ``` - */ -export function isNullish(x: unknown): x is null | undefined { - return x == null; -} - -/** - * Return `true` if the type of `x` is `symbol`. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const a: unknown = Symbol("symbol"); - * if (is.Symbol(a)) { - * // a is narrowed to symbol - * const _: symbol = a; - * } - * ``` - */ -export function isSymbol(x: unknown): x is symbol { - return typeof x === "symbol"; -} - -export type Primitive = - | string - | number - | bigint - | boolean - | null - | undefined - | symbol; - -/** - * Return `true` if the type of `x` is `Primitive`. - * - * ```ts - * import { is, Primitive } from "@core/unknownutil"; - * - * const a: unknown = 0; - * if (is.Primitive(a)) { - * // a is narrowed to Primitive - * const _: Primitive = a; - * } - * ``` - */ -export function isPrimitive(x: unknown): x is Primitive { - return x == null || primitiveSet.has(typeof x); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `T[]`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.ArrayOf(is.String); - * const a: unknown = ["a", "b", "c"]; - * if (isMyType(a)) { - * // a is narrowed to string[] - * const _: string[] = a; - * } - * ``` - */ -export function isArrayOf( - pred: Predicate, -): Predicate { - return rewriteName( - (x: unknown): x is T[] => isArray(x) && x.every(pred), - "isArrayOf", - pred, - ); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `Set`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.SetOf(is.String); - * const a: unknown = new Set(["a", "b", "c"]); - * if (isMyType(a)) { - * // a is narrowed to Set - * const _: Set = a; - * } - * ``` - */ -export function isSetOf( - pred: Predicate, -): Predicate> { - return rewriteName( - (x: unknown): x is Set => { - if (!isSet(x)) return false; - for (const v of x.values()) { - if (!pred(v)) return false; - } - return true; - }, - "isSetOf", - pred, - ); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `TupleOf` or `TupleOf`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.TupleOf([is.Number, is.String, is.Boolean]); - * const a: unknown = [0, "a", true]; - * if (isMyType(a)) { - * // a is narrowed to [number, string, boolean] - * const _: [number, string, boolean] = a; - * } - * ``` - * - * With `predElse`: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.TupleOf( - * [is.Number, is.String, is.Boolean], - * is.ArrayOf(is.Number), - * ); - * const a: unknown = [0, "a", true, 0, 1, 2]; - * if (isMyType(a)) { - * // a is narrowed to [number, string, boolean, ...number[]] - * const _: [number, string, boolean, ...number[]] = a; - * } - * ``` - * - * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array - * used as `predTup`. If a type error occurs, try adding `as const` as follows: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const predTup = [is.Number, is.String, is.Boolean] as const; - * const isMyType = is.TupleOf(predTup); - * const a: unknown = [0, "a", true]; - * if (isMyType(a)) { - * // a is narrowed to [number, string, boolean] - * const _: [number, string, boolean] = a; - * } - * ``` - */ -export function isTupleOf< - T extends readonly [Predicate, ...Predicate[]], ->( - predTup: T, -): Predicate>; -export function isTupleOf< - T extends readonly [Predicate, ...Predicate[]], - E extends Predicate, ->( - predTup: T, - predElse: E, -): Predicate<[...TupleOf, ...PredicateType]>; -export function isTupleOf< - T extends readonly [Predicate, ...Predicate[]], - E extends Predicate, ->( - predTup: T, - predElse?: E, -): Predicate | [...TupleOf, ...PredicateType]> { - if (!predElse) { - return rewriteName( - (x: unknown): x is TupleOf => { - if (!isArray(x) || x.length !== predTup.length) { - return false; - } - return predTup.every((pred, i) => pred(x[i])); - }, - "isTupleOf", - predTup, - ); - } else { - return rewriteName( - (x: unknown): x is [...TupleOf, ...PredicateType] => { - if (!isArray(x) || x.length < predTup.length) { - return false; - } - const head = x.slice(0, predTup.length); - const tail = x.slice(predTup.length); - return predTup.every((pred, i) => pred(head[i])) && predElse(tail); - }, - "isTupleOf", - predTup, - predElse, - ); - } -} - -type TupleOf = { - -readonly [P in keyof T]: T[P] extends Predicate ? U : never; -}; - -/** - * Return a type predicate function that returns `true` if the type of `x` is `ParametersOf` or `ParametersOf`. - * - * This is similar to `TupleOf` or `TupleOf`, but if `as.Optional()` is specified at the trailing, the trailing elements becomes optional and makes variable-length tuple. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { as, is } from "@core/unknownutil"; - * - * const isMyType = is.ParametersOf([ - * is.Number, - * as.Optional(is.String), - * is.Boolean, - * as.Optional(is.Number), - * as.Optional(is.String), - * as.Optional(is.Boolean), - * ] as const); - * const a: unknown = [0, undefined, "a"]; - * if (isMyType(a)) { - * // a is narrowed to [number, string | undefined, boolean, number?, string?, boolean?] - * const _: [number, string | undefined, boolean, number?, string?, boolean?] = a; - * } - * ``` - * - * With `predElse`: - * - * ```ts - * import { as, is } from "@core/unknownutil"; - * - * const isMyType = is.ParametersOf( - * [ - * is.Number, - * as.Optional(is.String), - * as.Optional(is.Boolean), - * ] as const, - * is.ArrayOf(is.Number), - * ); - * const a: unknown = [0, "a", true, 0, 1, 2]; - * if (isMyType(a)) { - * // a is narrowed to [number, string?, boolean?, ...number[]] - * const _: [number, string?, boolean?, ...number[]] = a; - * } - * ``` - * - * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array - * used as `predTup`. If a type error occurs, try adding `as const` as follows: - * - * ```ts - * import { as, is } from "@core/unknownutil"; - * - * const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; - * const isMyType = is.ParametersOf(predTup); - * const a: unknown = [0, "a"]; - * if (isMyType(a)) { - * // a is narrowed to [number, string, boolean?] - * const _: [number, string, boolean?] = a; - * } - * ``` - */ -export function isParametersOf< - T extends readonly [...Predicate[]], ->( - predTup: T, -): Predicate>; -export function isParametersOf< - T extends readonly [...Predicate[]], - E extends Predicate, ->( - predTup: T, - predElse: E, -): Predicate<[...ParametersOf, ...PredicateType]>; -export function isParametersOf< - T extends readonly [...Predicate[]], - E extends Predicate, ->( - predTup: T, - predElse?: E, -): Predicate | [...ParametersOf, ...PredicateType]> { - const requiresLength = 1 + - predTup.findLastIndex((pred) => !hasOptional(pred)); - if (!predElse) { - return rewriteName( - (x: unknown): x is ParametersOf => { - if ( - !isArray(x) || x.length < requiresLength || x.length > predTup.length - ) { - return false; - } - return predTup.every((pred, i) => pred(x[i])); - }, - "isParametersOf", - predTup, - ); - } else { - return rewriteName( - (x: unknown): x is [...ParametersOf, ...PredicateType] => { - if (!isArray(x) || x.length < requiresLength) { - return false; - } - const head = x.slice(0, predTup.length); - const tail = x.slice(predTup.length); - return predTup.every((pred, i) => pred(head[i])) && predElse(tail); - }, - "isParametersOf", - predTup, - predElse, - ); - } -} - -type ParametersOf = T extends readonly [] ? [] - : T extends readonly [...infer P, infer R] - // Tuple of predicates - ? P extends Predicate[] - ? R extends Predicate & WithOptional - // Last parameter is optional - ? [...ParametersOf

, PredicateType?] - // Last parameter is NOT optional - : [...ParametersOf

, PredicateType] - : never - // Array of predicates - : TupleOf; - -/** - * Return a type predicate function that returns `true` if the type of `x` is `UniformTupleOf`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.UniformTupleOf(5); - * const a: unknown = [0, 1, 2, 3, 4]; - * if (isMyType(a)) { - * // a is narrowed to [unknown, unknown, unknown, unknown, unknown] - * const _: [unknown, unknown, unknown, unknown, unknown] = a; - * } - * ``` - * - * With predicate function: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.UniformTupleOf(5, is.Number); - * const a: unknown = [0, 1, 2, 3, 4]; - * if (isMyType(a)) { - * // a is narrowed to [number, number, number, number, number] - * const _: [number, number, number, number, number] = a; - * } - * ``` - */ -export function isUniformTupleOf( - n: N, - pred: Predicate = isAny, -): Predicate> { - return rewriteName( - (x: unknown): x is UniformTupleOf => { - if (!isArray(x) || x.length !== n) { - return false; - } - return x.every((v) => pred(v)); - }, - "isUniformTupleOf", - n, - pred, - ); -} - -// https://stackoverflow.com/a/71700658/1273406 -type UniformTupleOf< - T, - N extends number, - R extends readonly T[] = [], -> = R["length"] extends N ? R : UniformTupleOf; - -/** - * Return a type predicate function that returns `true` if the type of `x` is an Object instance that satisfies `Record`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * Note that this function check if the `x` is an instance of `Object`. - * Use `isRecordOf` instead if you want to check if the `x` satisfies the `Record` type. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.RecordObjectOf(is.Number); - * const a: unknown = {"a": 0, "b": 1}; - * if (isMyType(a)) { - * // a is narrowed to Record - * const _: Record = a; - * } - * ``` - * - * With predicate function for keys: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.RecordObjectOf(is.Number, is.String); - * const a: unknown = {"a": 0, "b": 1}; - * if (isMyType(a)) { - * // a is narrowed to Record - * const _: Record = a; - * } - * ``` - */ -export function isRecordObjectOf( - pred: Predicate, - predKey?: Predicate, -): Predicate> { - return rewriteName( - (x: unknown): x is Record => { - if (!isRecordObject(x)) return false; - for (const k in x) { - if (!pred(x[k])) return false; - if (predKey && !predKey(k)) return false; - } - return true; - }, - "isRecordObjectOf", - pred, - predKey, - ); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` satisfies `Record`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.RecordOf(is.Number); - * const a: unknown = {"a": 0, "b": 1}; - * if (isMyType(a)) { - * // a is narrowed to Record - * const _: Record = a; - * } - * ``` - * - * With predicate function for keys: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.RecordOf(is.Number, is.String); - * const a: unknown = {"a": 0, "b": 1}; - * if (isMyType(a)) { - * // a is narrowed to Record - * const _: Record = a; - * } - * ``` - */ -export function isRecordOf( - pred: Predicate, - predKey?: Predicate, -): Predicate> { - return rewriteName( - (x: unknown): x is Record => { - if (!isRecord(x)) return false; - for (const k in x) { - if (!pred(x[k])) return false; - if (predKey && !predKey(k)) return false; - } - return true; - }, - "isRecordOf", - pred, - predKey, - ); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `Map`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.MapOf(is.Number); - * const a: unknown = new Map([["a", 0], ["b", 1]]); - * if (isMyType(a)) { - * // a is narrowed to Map - * const _: Map = a; - * } - * ``` - * - * With predicate function for keys: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.MapOf(is.Number, is.String); - * const a: unknown = new Map([["a", 0], ["b", 1]]); - * if (isMyType(a)) { - * // a is narrowed to Map - * const _: Map = a; - * } - * ``` - */ -export function isMapOf( - pred: Predicate, - predKey?: Predicate, -): Predicate> { - return rewriteName( - (x: unknown): x is Map => { - if (!isMap(x)) return false; - for (const entry of x.entries()) { - const [k, v] = entry; - if (!pred(v)) return false; - if (predKey && !predKey(k)) return false; - } - return true; - }, - "isMapOf", - pred, - predKey, - ); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `ObjectOf`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * If `as.Optional` is specified in the predicate function, the property becomes optional. - * - * The number of keys of `x` must be greater than or equal to the number of keys of `predObj`. - * - * ```ts - * import { as, is } from "@core/unknownutil"; - * - * const isMyType = is.ObjectOf({ - * a: is.Number, - * b: is.String, - * c: as.Optional(is.Boolean), - * }); - * const a: unknown = { a: 0, b: "a", other: "other" }; - * if (isMyType(a)) { - * // "other" key in `a` is ignored because of `options.strict` is `false`. - * // a is narrowed to { a: number; b: string; c?: boolean | undefined } - * const _: { a: number; b: string; c?: boolean | undefined } = a; - * } - * ``` - */ -export function isObjectOf< - T extends Record>, ->(predObj: T): Predicate> & WithPredObj { - return annotate( - rewriteName( - (x: unknown): x is ObjectOf => { - if ( - x == null || - typeof x !== "object" && typeof x !== "function" || - Array.isArray(x) - ) return false; - // Check each values - for (const k in predObj) { - if (!predObj[k]((x as T)[k])) return false; - } - return true; - }, - "isObjectOf", - predObj, - ), - "predObj", - predObj, - ); -} - -type ObjectOf>> = FlatType< - // Optional - & { - [K in keyof T as T[K] extends WithOptional ? K : never]?: - T[K] extends Predicate ? U : never; - } - // Non optional - & { - [K in keyof T as T[K] extends WithOptional ? never : K]: - T[K] extends Predicate ? U : never; - } ->; - -type WithPredObj>> = { - predObj: T; -}; - -/** - * Return a type predicate function that returns `true` if the type of `x` is strictly follow the `ObjectOf`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * If `as.Optional` is specified in the predicate function, the property becomes optional. - * - * The number of keys of `x` must be equal to the number of non optional keys of `predObj`. - * - * ```ts - * import { as, is } from "@core/unknownutil"; - * - * const isMyType = is.StrictOf(is.ObjectOf({ - * a: is.Number, - * b: is.String, - * c: as.Optional(is.Boolean), - * })); - * const a: unknown = { a: 0, b: "a", other: "other" }; - * if (isMyType(a)) { - * // This block will not be executed because of "other" key in `a`. - * } - * ``` - */ -export function isStrictOf< - T extends Record, - P extends Record>, ->( - pred: - & Predicate - & WithPredObj

, -): - & Predicate - & WithPredObj

{ - const s = new Set(Object.keys(pred.predObj)); - return rewriteName( - (x: unknown): x is T => { - if (!pred(x)) return false; - // deno-lint-ignore no-explicit-any - const ks = Object.keys(x as any); - return ks.length <= s.size && ks.every((k) => s.has(k)); - }, - "isStrictOf", - pred, - ) as Predicate & WithPredObj

; -} - -/** - * Return `true` if the type of `x` is instance of `ctor`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.InstanceOf(Date); - * const a: unknown = new Date(); - * if (isMyType(a)) { - * // a is narrowed to Date - * const _: Date = a; - * } - * ``` - */ -// deno-lint-ignore no-explicit-any -export function isInstanceOf unknown>( - ctor: T, -): Predicate> { - return rewriteName( - (x: unknown): x is InstanceType => x instanceof ctor, - "isInstanceOf", - ctor, - ); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is a literal type of `pred`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.LiteralOf("hello"); - * const a: unknown = "hello"; - * if (isMyType(a)) { - * // a is narrowed to "hello" - * const _: "hello" = a; - * } - * ``` - */ -export function isLiteralOf( - literal: T, -): Predicate { - return rewriteName( - (x: unknown): x is T => x === literal, - "isLiteralOf", - literal, - ); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is one of literal type in `preds`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.LiteralOneOf(["hello", "world"] as const); - * const a: unknown = "hello"; - * if (isMyType(a)) { - * // a is narrowed to "hello" | "world" - * const _: "hello" | "world" = a; - * } - * ``` - */ -export function isLiteralOneOf( - literals: T, -): Predicate { - const s = new Set(literals); - return rewriteName( - (x: unknown): x is T[number] => s.has(x as T[number]), - "isLiteralOneOf", - literals, - ); -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `UnionOf`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.UnionOf([is.Number, is.String, is.Boolean]); - * const a: unknown = 0; - * if (isMyType(a)) { - * // a is narrowed to number | string | boolean - * const _: number | string | boolean = a; - * } - * ``` - * - * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array - * used as `preds`. If a type error occurs, try adding `as const` as follows: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const preds = [is.Number, is.String, is.Boolean] as const; - * const isMyType = is.UnionOf(preds); - * const a: unknown = 0; - * if (isMyType(a)) { - * // a is narrowed to number | string | boolean - * const _: number | string | boolean = a; - * } - * ``` - */ -export function isUnionOf< - T extends readonly [Predicate, ...Predicate[]], ->( - preds: T, -): Predicate> & WithUnion { - return annotate( - rewriteName( - (x: unknown): x is UnionOf => preds.some((pred) => pred(x)), - "isUnionOf", - preds, - ), - "union", - preds, - ); -} - -type UnionOf = T extends readonly [Predicate, ...infer R] - ? U | UnionOf - : never; - -type WithUnion< - T extends readonly [Predicate, ...Predicate[]], -> = { - union: T; -}; - -/** - * Return a type predicate function that returns `true` if the type of `x` is `IntersectionOf`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const isMyType = is.IntersectionOf([ - * is.ObjectOf({ a: is.Number }), - * is.ObjectOf({ b: is.String }), - * ]); - * const a: unknown = { a: 0, b: "a" }; - * if (isMyType(a)) { - * // a is narrowed to { a: number } & { b: string } - * const _: { a: number } & { b: string } = a; - * } - * ``` - * - * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array - * used as `preds`. If a type error occurs, try adding `as const` as follows: - * - * ```ts - * import { is } from "@core/unknownutil"; - * - * const preds = [ - * is.ObjectOf({ a: is.Number }), - * is.ObjectOf({ b: is.String }), - * ] as const - * const isMyType = is.IntersectionOf(preds); - * const a: unknown = { a: 0, b: "a" }; - * if (isMyType(a)) { - * // a is narrowed to { a: number } & { b: string } - * const _: { a: number } & { b: string } = a; - * } - * ``` - */ -export function isIntersectionOf< - T extends readonly [ - Predicate & WithPredObj>>, - ...( - & Predicate - & WithPredObj>> - )[], - ], ->( - preds: T, -): - & Predicate> - & WithPredObj>>; -export function isIntersectionOf< - T extends readonly [Predicate], ->( - preds: T, -): T[0]; -export function isIntersectionOf< - T extends readonly [Predicate, ...Predicate[]], ->( - preds: T, -): - & Predicate> - & WithPredObj>>; -export function isIntersectionOf< - T extends readonly [Predicate, ...Predicate[]], ->( - preds: T, -): - | Predicate - | Predicate> - & WithPredObj>> { - const predObj = {}; - const restPreds = preds.filter((pred) => { - if (!hasAnnotation(pred, "predObj")) { - return true; - } - Object.assign(predObj, pred.predObj); - }); - if (restPreds.length < preds.length) { - restPreds.push(isObjectOf(predObj)); - } - if (restPreds.length === 1) { - return restPreds[0]; - } - return rewriteName( - (x: unknown): x is IntersectionOf => restPreds.every((pred) => pred(x)), - "isIntersectionOf", - preds, - ); -} - -type IntersectionOf = TupleToIntersection>; - -/** - * Return a type predicate function that returns `true` if the type of `x` is `Required>`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```typescript - * import { as, is } from "@core/unknownutil"; - * - * const isMyType = is.RequiredOf(is.ObjectOf({ - * a: is.Number, - * b: is.UnionOf([is.String, is.Undefined]), - * c: as.Optional(is.Boolean), - * })); - * const a: unknown = { a: 0, b: "b", c: true, other: "other" }; - * if (isMyType(a)) { - * // 'a' is narrowed to { a: number; b: string | undefined; c: boolean } - * const _: { a: number; b: string | undefined; c: boolean } = a; - * } - * ``` - */ -export function isRequiredOf< - T extends Record, - P extends Record>, ->( - pred: Predicate & WithPredObj

, -): - & Predicate>> - & WithPredObj

{ - const predObj = Object.fromEntries( - Object.entries(pred.predObj).map(([k, v]) => [k, asUnoptional(v)]), - ); - return isObjectOf(predObj) as - & Predicate>> - & WithPredObj

; -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `Partial>`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```typescript - * import { as, is } from "@core/unknownutil"; - * - * const isMyType = is.PartialOf(is.ObjectOf({ - * a: is.Number, - * b: is.UnionOf([is.String, is.Undefined]), - * c: as.Optional(is.Boolean), - * })); - * const a: unknown = { a: undefined, other: "other" }; - * if (isMyType(a)) { - * // The "other" key in `a` is ignored. - * // 'a' is narrowed to { a?: number | undefined; b?: string | undefined; c?: boolean | undefined } - * const _: { a?: number | undefined; b?: string | undefined; c?: boolean | undefined } = a; - * } - * ``` - */ -export function isPartialOf< - T extends Record, - P extends Record>, ->( - pred: Predicate & WithPredObj

, -): - & Predicate>> - & WithPredObj

{ - const predObj = Object.fromEntries( - Object.entries(pred.predObj).map(([k, v]) => [k, asOptional(v)]), - ) as Record>; - return isObjectOf(predObj) as - & Predicate>> - & WithPredObj

; -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `Pick, K>`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```typescript - * import { as, is } from "@core/unknownutil"; - * - * const isMyType = is.PickOf(is.ObjectOf({ - * a: is.Number, - * b: is.String, - * c: as.Optional(is.Boolean), - * }), ["a", "c"]); - * const a: unknown = { a: 0, b: "a", other: "other" }; - * if (isMyType(a)) { - * // The "b" and "other" key in `a` is ignored. - * // 'a' is narrowed to { a: number; c?: boolean | undefined } - * const _: { a: number; c?: boolean | undefined } = a; - * } - * ``` - */ -export function isPickOf< - T extends Record, - P extends Record>, - K extends keyof T, ->( - pred: Predicate & WithPredObj

, - keys: K[], -): - & Predicate>> - & WithPredObj

{ - const s = new Set(keys); - const predObj = Object.fromEntries( - Object.entries(pred.predObj).filter(([k]) => s.has(k as K)), - ); - return isObjectOf(predObj) as - & Predicate>> - & WithPredObj

; -} - -/** - * Return a type predicate function that returns `true` if the type of `x` is `Omit, K>`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```typescript - * import { as, is } from "@core/unknownutil"; - * - * const isMyType = is.OmitOf(is.ObjectOf({ - * a: is.Number, - * b: is.String, - * c: as.Optional(is.Boolean), - * }), ["a", "c"]); - * const a: unknown = { a: 0, b: "a", other: "other" }; - * if (isMyType(a)) { - * // The "a", "c", and "other" key in `a` is ignored. - * // 'a' is narrowed to { b: string } - * const _: { b: string } = a; - * } - * ``` - */ -export function isOmitOf< - T extends Record, - P extends Record>, - K extends keyof T, ->( - pred: Predicate & WithPredObj

, - keys: K[], -): - & Predicate>> - & WithPredObj

{ - const s = new Set(keys); - const predObj = Object.fromEntries( - Object.entries(pred.predObj).filter(([k]) => !s.has(k as K)), - ); - return isObjectOf(predObj) as - & Predicate>> - & WithPredObj

; -} - -export const is = { - Any: isAny, - Array: isArray, - ArrayOf: isArrayOf, - AsyncFunction: isAsyncFunction, - BigInt: isBigInt, - Boolean: isBoolean, - Function: isFunction, - InstanceOf: isInstanceOf, - IntersectionOf: isIntersectionOf, - LiteralOf: isLiteralOf, - LiteralOneOf: isLiteralOneOf, - Map: isMap, - MapOf: isMapOf, - Null: isNull, - Nullish: isNullish, - Number: isNumber, - ObjectOf: isObjectOf, - OmitOf: isOmitOf, - ParametersOf: isParametersOf, - PartialOf: isPartialOf, - PickOf: isPickOf, - Primitive: isPrimitive, - Record: isRecord, - RecordObject: isRecordObject, - RecordObjectOf: isRecordObjectOf, - RecordOf: isRecordOf, - RequiredOf: isRequiredOf, - Set: isSet, - SetOf: isSetOf, - StrictOf: isStrictOf, - String: isString, - Symbol: isSymbol, - SyncFunction: isSyncFunction, - TupleOf: isTupleOf, - Undefined: isUndefined, - UniformTupleOf: isUniformTupleOf, - UnionOf: isUnionOf, - Unknown: isUnknown, -}; diff --git a/is/__snapshots__/array_of_test.ts.snap b/is/__snapshots__/array_of_test.ts.snap new file mode 100644 index 0000000..be5103b --- /dev/null +++ b/is/__snapshots__/array_of_test.ts.snap @@ -0,0 +1,5 @@ +export const snapshot = {}; + +snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; + +snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; diff --git a/is/__snapshots__/instance_of_test.ts.snap b/is/__snapshots__/instance_of_test.ts.snap new file mode 100644 index 0000000..29206ee --- /dev/null +++ b/is/__snapshots__/instance_of_test.ts.snap @@ -0,0 +1,5 @@ +export const snapshot = {}; + +snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; + +snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; diff --git a/is/__snapshots__/intersection_of_test.ts.snap b/is/__snapshots__/intersection_of_test.ts.snap new file mode 100644 index 0000000..004da6e --- /dev/null +++ b/is/__snapshots__/intersection_of_test.ts.snap @@ -0,0 +1,17 @@ +export const snapshot = {}; + +snapshot[`isIntersectionOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isString +})" +`; + +snapshot[`isIntersectionOf > returns properly named function 2`] = `"isString"`; + +snapshot[`isIntersectionOf > returns properly named function 3`] = ` +"isIntersectionOf([ + isFunction, + isObjectOf({b: isString}) +])" +`; diff --git a/is/__snapshots__/literal_of_test.ts.snap b/is/__snapshots__/literal_of_test.ts.snap new file mode 100644 index 0000000..e7e6c72 --- /dev/null +++ b/is/__snapshots__/literal_of_test.ts.snap @@ -0,0 +1,15 @@ +export const snapshot = {}; + +snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; + +snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; + +snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; + +snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; + +snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; + +snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; + +snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; diff --git a/is/__snapshots__/literal_one_of_test.ts.snap b/is/__snapshots__/literal_one_of_test.ts.snap new file mode 100644 index 0000000..0bdf47c --- /dev/null +++ b/is/__snapshots__/literal_one_of_test.ts.snap @@ -0,0 +1,3 @@ +export const snapshot = {}; + +snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; diff --git a/is/__snapshots__/map_of_test.ts.snap b/is/__snapshots__/map_of_test.ts.snap new file mode 100644 index 0000000..1f0a965 --- /dev/null +++ b/is/__snapshots__/map_of_test.ts.snap @@ -0,0 +1,9 @@ +export const snapshot = {}; + +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; + +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; + +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; + +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; diff --git a/is/__snapshots__/object_of_test.ts.snap b/is/__snapshots__/object_of_test.ts.snap new file mode 100644 index 0000000..c915e6d --- /dev/null +++ b/is/__snapshots__/object_of_test.ts.snap @@ -0,0 +1,19 @@ +export const snapshot = {}; + +snapshot[`isObjectOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isString, + c: isBoolean +})" +`; + +snapshot[`isObjectOf > returns properly named function 2`] = `"isObjectOf({a: a})"`; + +snapshot[`isObjectOf > returns properly named function 3`] = ` +"isObjectOf({ + a: isObjectOf({ + b: isObjectOf({c: isBoolean}) + }) +})" +`; diff --git a/is/__snapshots__/omit_of_test.ts.snap b/is/__snapshots__/omit_of_test.ts.snap new file mode 100644 index 0000000..dad991f --- /dev/null +++ b/is/__snapshots__/omit_of_test.ts.snap @@ -0,0 +1,10 @@ +export const snapshot = {}; + +snapshot[`isOmitOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" +`; + +snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; diff --git a/is/__snapshots__/parameters_of_test.ts.snap b/is/__snapshots__/parameters_of_test.ts.snap new file mode 100644 index 0000000..e305ef2 --- /dev/null +++ b/is/__snapshots__/parameters_of_test.ts.snap @@ -0,0 +1,49 @@ +export const snapshot = {}; + +snapshot[`isParametersOf > returns properly named function 1`] = ` +"isParametersOf([ + isNumber, + isString, + asOptional(isBoolean) +])" +`; + +snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)])"`; + +snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([])"`; + +snapshot[`isParametersOf > returns properly named function 4`] = ` +"isParametersOf([ + isParametersOf([ + isParametersOf([ + isNumber, + isString, + asOptional(isBoolean) + ]) + ]) +])" +`; + +snapshot[`isParametersOf > returns properly named function 1`] = ` +"isParametersOf([ + isNumber, + isString, + asOptional(isBoolean) +], isArray)" +`; + +snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)], isArrayOf(isString))"`; + +snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([], isArrayOf(isString))"`; + +snapshot[`isParametersOf > returns properly named function 4`] = ` +"isParametersOf([ + isParametersOf([ + isParametersOf([ + isNumber, + isString, + asOptional(isBoolean) + ], isArray) + ], isArray) +])" +`; diff --git a/is/__snapshots__/partial_of_test.ts.snap b/is/__snapshots__/partial_of_test.ts.snap new file mode 100644 index 0000000..d6109b0 --- /dev/null +++ b/is/__snapshots__/partial_of_test.ts.snap @@ -0,0 +1,23 @@ +export const snapshot = {}; + +snapshot[`isPartialOf > returns properly named function 1`] = ` +"isObjectOf({ + a: asOptional(isNumber), + b: asOptional(isUnionOf([ + isString, + isUndefined + ])), + c: asOptional(isBoolean) +})" +`; + +snapshot[`isPartialOf > returns properly named function 2`] = ` +"isObjectOf({ + a: asOptional(isNumber), + b: asOptional(isUnionOf([ + isString, + isUndefined + ])), + c: asOptional(isBoolean) +})" +`; diff --git a/is/__snapshots__/pick_of_test.ts.snap b/is/__snapshots__/pick_of_test.ts.snap new file mode 100644 index 0000000..f76fbb4 --- /dev/null +++ b/is/__snapshots__/pick_of_test.ts.snap @@ -0,0 +1,10 @@ +export const snapshot = {}; + +snapshot[`isPickOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" +`; + +snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; diff --git a/is/__snapshots__/record_object_of_test.ts.snap b/is/__snapshots__/record_object_of_test.ts.snap new file mode 100644 index 0000000..d62a5d2 --- /dev/null +++ b/is/__snapshots__/record_object_of_test.ts.snap @@ -0,0 +1,9 @@ +export const snapshot = {}; + +snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; + +snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; + +snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; + +snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; diff --git a/is/__snapshots__/record_of_test.ts.snap b/is/__snapshots__/record_of_test.ts.snap new file mode 100644 index 0000000..90eeadd --- /dev/null +++ b/is/__snapshots__/record_of_test.ts.snap @@ -0,0 +1,9 @@ +export const snapshot = {}; + +snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; + +snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; + +snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; + +snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; diff --git a/is/__snapshots__/required_of_test.ts.snap b/is/__snapshots__/required_of_test.ts.snap new file mode 100644 index 0000000..0f7010e --- /dev/null +++ b/is/__snapshots__/required_of_test.ts.snap @@ -0,0 +1,23 @@ +export const snapshot = {}; + +snapshot[`isRequiredOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isUnionOf([ + isString, + isUndefined + ]), + c: isBoolean +})" +`; + +snapshot[`isRequiredOf > returns properly named function 2`] = ` +"isObjectOf({ + a: isNumber, + b: isUnionOf([ + isString, + isUndefined + ]), + c: isBoolean +})" +`; diff --git a/is/__snapshots__/set_of_test.ts.snap b/is/__snapshots__/set_of_test.ts.snap new file mode 100644 index 0000000..66104bd --- /dev/null +++ b/is/__snapshots__/set_of_test.ts.snap @@ -0,0 +1,5 @@ +export const snapshot = {}; + +snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; + +snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; diff --git a/is/__snapshots__/strict_of_test.ts.snap b/is/__snapshots__/strict_of_test.ts.snap new file mode 100644 index 0000000..597e1e7 --- /dev/null +++ b/is/__snapshots__/strict_of_test.ts.snap @@ -0,0 +1,19 @@ +export const snapshot = {}; + +snapshot[`isStrictOf > returns properly named function 1`] = ` +"isStrictOf(isObjectOf({ + a: isNumber, + b: isString, + c: isBoolean +}))" +`; + +snapshot[`isStrictOf > returns properly named function 2`] = `"isStrictOf(isObjectOf({a: a}))"`; + +snapshot[`isStrictOf > returns properly named function 3`] = ` +"isStrictOf(isObjectOf({ + a: isStrictOf(isObjectOf({ + b: isStrictOf(isObjectOf({c: isBoolean})) + })) +}))" +`; diff --git a/is/__snapshots__/tuple_of_test.ts.snap b/is/__snapshots__/tuple_of_test.ts.snap new file mode 100644 index 0000000..5673452 --- /dev/null +++ b/is/__snapshots__/tuple_of_test.ts.snap @@ -0,0 +1,45 @@ +export const snapshot = {}; + +snapshot[`isTupleOf > returns properly named function 1`] = ` +"isTupleOf([ + isNumber, + isString, + isBoolean +])" +`; + +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; + +snapshot[`isTupleOf > returns properly named function 3`] = ` +"isTupleOf([ + isTupleOf([ + isTupleOf([ + isNumber, + isString, + isBoolean + ]) + ]) +])" +`; + +snapshot[`isTupleOf > returns properly named function 1`] = ` +"isTupleOf([ + isNumber, + isString, + isBoolean +], isArray)" +`; + +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; + +snapshot[`isTupleOf > returns properly named function 3`] = ` +"isTupleOf([ + isTupleOf([ + isTupleOf([ + isNumber, + isString, + isBoolean + ], isArray) + ], isArray) +])" +`; diff --git a/is/__snapshots__/uniform_tuple_of_test.ts.snap b/is/__snapshots__/uniform_tuple_of_test.ts.snap new file mode 100644 index 0000000..8f13f88 --- /dev/null +++ b/is/__snapshots__/uniform_tuple_of_test.ts.snap @@ -0,0 +1,7 @@ +export const snapshot = {}; + +snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, undefined)"`; + +snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; + +snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; diff --git a/is/__snapshots__/union_of_test.ts.snap b/is/__snapshots__/union_of_test.ts.snap new file mode 100644 index 0000000..cc9cb3a --- /dev/null +++ b/is/__snapshots__/union_of_test.ts.snap @@ -0,0 +1,9 @@ +export const snapshot = {}; + +snapshot[`isUnionOf > returns properly named function 1`] = ` +"isUnionOf([ + isNumber, + isString, + isBoolean +])" +`; diff --git a/is/any.ts b/is/any.ts new file mode 100644 index 0000000..ce352d3 --- /dev/null +++ b/is/any.ts @@ -0,0 +1,17 @@ +/** + * Assume `x is `any` and always return `true` regardless of the type of `x`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a = "a"; + * if (is.Any(a)) { + * // a is narrowed to any + * const _: any = a; + * } + * ``` + */ +// deno-lint-ignore no-explicit-any +export function isAny(_x: unknown): _x is any { + return true; +} diff --git a/is/any_test.ts b/is/any_test.ts new file mode 100644 index 0000000..bc7746f --- /dev/null +++ b/is/any_test.ts @@ -0,0 +1,24 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isAny } from "./any.ts"; + +Deno.test("isAny", async (t) => { + await testWithExamples(t, isAny, { + validExamples: [ + "string", + "number", + "bigint", + "boolean", + "array", + "set", + "record", + "map", + "syncFunction", + "asyncFunction", + "null", + "undefined", + "symbol", + "date", + "promise", + ], + }); +}); diff --git a/is/array.ts b/is/array.ts new file mode 100644 index 0000000..4df306a --- /dev/null +++ b/is/array.ts @@ -0,0 +1,18 @@ +/** + * Return `true` if the type of `x` is `unknown[]`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = [0, 1, 2]; + * if (is.Array(a)) { + * // a is narrowed to unknown[] + * const _: unknown[] = a; + * } + * ``` + */ +export function isArray( + x: unknown, +): x is unknown[] { + return Array.isArray(x); +} diff --git a/is/array_of.ts b/is/array_of.ts new file mode 100644 index 0000000..8ae5a6f --- /dev/null +++ b/is/array_of.ts @@ -0,0 +1,29 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate } from "../type.ts"; +import { isArray } from "./array.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `T[]`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.ArrayOf(is.String); + * const a: unknown = ["a", "b", "c"]; + * if (isMyType(a)) { + * // a is narrowed to string[] + * const _: string[] = a; + * } + * ``` + */ +export function isArrayOf( + pred: Predicate, +): Predicate { + return rewriteName( + (x: unknown): x is T[] => isArray(x) && x.every(pred), + "isArrayOf", + pred, + ); +} diff --git a/is/array_of_test.ts b/is/array_of_test.ts new file mode 100644 index 0000000..0be24d5 --- /dev/null +++ b/is/array_of_test.ts @@ -0,0 +1,32 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isArrayOf } from "./array_of.ts"; + +Deno.test("isArrayOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isArrayOf(is.Number).name); + await assertSnapshot(t, isArrayOf((_x): _x is string => false).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = [0, 1, 2]; + if (isArrayOf(is.Number)(a)) { + assertType>(true); + } + }); + await t.step("returns true on T array", () => { + assertEquals(isArrayOf(is.Number)([0, 1, 2]), true); + assertEquals(isArrayOf(is.String)(["a", "b", "c"]), true); + assertEquals(isArrayOf(is.Boolean)([true, false, true]), true); + }); + await t.step("returns false on non T array", () => { + assertEquals(isArrayOf(is.String)([0, 1, 2]), false); + assertEquals(isArrayOf(is.Number)(["a", "b", "c"]), false); + assertEquals(isArrayOf(is.String)([true, false, true]), false); + }); + await testWithExamples(t, isArrayOf((_: unknown): _ is unknown => true), { + excludeExamples: ["array"], + }); +}); diff --git a/is/array_test.ts b/is/array_test.ts new file mode 100644 index 0000000..1994ab3 --- /dev/null +++ b/is/array_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isArray } from "./array.ts"; + +Deno.test("isArray", async (t) => { + await testWithExamples(t, isArray, { validExamples: ["array"] }); +}); diff --git a/is/async_function.ts b/is/async_function.ts new file mode 100644 index 0000000..562e526 --- /dev/null +++ b/is/async_function.ts @@ -0,0 +1,20 @@ +const objectToString = Object.prototype.toString; + +/** + * Return `true` if the type of `x` is `function` (async function). + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = async () => {}; + * if (is.AsyncFunction(a)) { + * // a is narrowed to (...args: unknown[]) => Promise + * const _: ((...args: unknown[]) => Promise) = a; + * } + * ``` + */ +export function isAsyncFunction( + x: unknown, +): x is (...args: unknown[]) => Promise { + return objectToString.call(x) === "[object AsyncFunction]"; +} diff --git a/is/async_function_test.ts b/is/async_function_test.ts new file mode 100644 index 0000000..7fbe8e7 --- /dev/null +++ b/is/async_function_test.ts @@ -0,0 +1,8 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isAsyncFunction } from "./async_function.ts"; + +Deno.test("isAsyncFunction", async (t) => { + await testWithExamples(t, isAsyncFunction, { + validExamples: ["asyncFunction"], + }); +}); diff --git a/is/bigint.ts b/is/bigint.ts new file mode 100644 index 0000000..78529ed --- /dev/null +++ b/is/bigint.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `bigint`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = 0n; + * if (is.Bigint(a)) { + * // a is narrowed to bigint + * const _: bigint = a; + * } + * ``` + */ +export function isBigint(x: unknown): x is bigint { + return typeof x === "bigint"; +} diff --git a/is/bigint_test.ts b/is/bigint_test.ts new file mode 100644 index 0000000..8d1fa2e --- /dev/null +++ b/is/bigint_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isBigint } from "./bigint.ts"; + +Deno.test("isBigint", async (t) => { + await testWithExamples(t, isBigint, { validExamples: ["bigint"] }); +}); diff --git a/is/boolean.ts b/is/boolean.ts new file mode 100644 index 0000000..b9daf8d --- /dev/null +++ b/is/boolean.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `boolean`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = true; + * if (is.Boolean(a)) { + * // a is narrowed to boolean + * const _: boolean = a; + * } + * ``` + */ +export function isBoolean(x: unknown): x is boolean { + return typeof x === "boolean"; +} diff --git a/is/boolean_test.ts b/is/boolean_test.ts new file mode 100644 index 0000000..8f9a376 --- /dev/null +++ b/is/boolean_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isBoolean } from "./boolean.ts"; + +Deno.test("isBoolean", async (t) => { + await testWithExamples(t, isBoolean, { validExamples: ["boolean"] }); +}); diff --git a/is/function.ts b/is/function.ts new file mode 100644 index 0000000..155f47a --- /dev/null +++ b/is/function.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `function`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = () => {}; + * if (is.Function(a)) { + * // a is narrowed to (...args: unknown[]) => unknown + * const _: ((...args: unknown[]) => unknown) = a; + * } + * ``` + */ +export function isFunction(x: unknown): x is (...args: unknown[]) => unknown { + return x instanceof Function; +} diff --git a/is/function_test.ts b/is/function_test.ts new file mode 100644 index 0000000..cca305e --- /dev/null +++ b/is/function_test.ts @@ -0,0 +1,8 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isFunction } from "./function.ts"; + +Deno.test("isFunction", async (t) => { + await testWithExamples(t, isFunction, { + validExamples: ["syncFunction", "asyncFunction"], + }); +}); diff --git a/is/instance_of.ts b/is/instance_of.ts new file mode 100644 index 0000000..9c2c00a --- /dev/null +++ b/is/instance_of.ts @@ -0,0 +1,29 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate } from "../type.ts"; + +/** + * Return `true` if the type of `x` is instance of `ctor`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.InstanceOf(Date); + * const a: unknown = new Date(); + * if (isMyType(a)) { + * // a is narrowed to Date + * const _: Date = a; + * } + * ``` + */ +// deno-lint-ignore no-explicit-any +export function isInstanceOf unknown>( + ctor: T, +): Predicate> { + return rewriteName( + (x: unknown): x is InstanceType => x instanceof ctor, + "isInstanceOf", + ctor, + ); +} diff --git a/is/instance_of_test.ts b/is/instance_of_test.ts new file mode 100644 index 0000000..2d45d1b --- /dev/null +++ b/is/instance_of_test.ts @@ -0,0 +1,47 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { isInstanceOf } from "./instance_of.ts"; + +Deno.test("isInstanceOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isInstanceOf(Date).name); + await assertSnapshot(t, isInstanceOf(class {}).name); + }); + await t.step("returns true on T instance", () => { + class Cls {} + assertEquals(isInstanceOf(Cls)(new Cls()), true); + assertEquals(isInstanceOf(Date)(new Date()), true); + assertEquals(isInstanceOf(Promise)(new Promise(() => {})), true); + }); + await t.step("with user-defined class", async (t) => { + class Cls {} + await testWithExamples(t, isInstanceOf(Cls)); + }); + await t.step("with Date", async (t) => { + await testWithExamples(t, isInstanceOf(Date), { validExamples: ["date"] }); + }); + await t.step("with Promise", async (t) => { + await testWithExamples(t, isInstanceOf(Promise), { + validExamples: ["promise"], + }); + }); + await t.step("returns proper type predicate", () => { + class Cls {} + const a: unknown = new Cls(); + if (isInstanceOf(Cls)(a)) { + assertType>(true); + } + + const b: unknown = new Date(); + if (isInstanceOf(Date)(b)) { + assertType>(true); + } + + const c: unknown = new Promise(() => {}); + if (isInstanceOf(Promise)(c)) { + assertType>>(true); + } + }); +}); diff --git a/is/intersection_of.ts b/is/intersection_of.ts new file mode 100644 index 0000000..3246a7f --- /dev/null +++ b/is/intersection_of.ts @@ -0,0 +1,105 @@ +import { rewriteName } from "../_funcutil.ts"; +import { hasAnnotation, type WithPredObj } from "../_annotation.ts"; +import type { Predicate } from "../type.ts"; +import { isObjectOf } from "./object_of.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `IntersectionOf`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.IntersectionOf([ + * is.ObjectOf({ a: is.Number }), + * is.ObjectOf({ b: is.String }), + * ]); + * const a: unknown = { a: 0, b: "a" }; + * if (isMyType(a)) { + * // a is narrowed to { a: number } & { b: string } + * const _: { a: number } & { b: string } = a; + * } + * ``` + * + * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array + * used as `preds`. If a type error occurs, try adding `as const` as follows: + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const preds = [ + * is.ObjectOf({ a: is.Number }), + * is.ObjectOf({ b: is.String }), + * ] as const + * const isMyType = is.IntersectionOf(preds); + * const a: unknown = { a: 0, b: "a" }; + * if (isMyType(a)) { + * // a is narrowed to { a: number } & { b: string } + * const _: { a: number } & { b: string } = a; + * } + * ``` + */ +export function isIntersectionOf< + T extends readonly [ + Predicate & WithPredObj>>, + ...( + & Predicate + & WithPredObj>> + )[], + ], +>( + preds: T, +): + & Predicate> + & WithPredObj>>; +export function isIntersectionOf< + T extends readonly [Predicate], +>( + preds: T, +): T[0]; +export function isIntersectionOf< + T extends readonly [Predicate, ...Predicate[]], +>( + preds: T, +): + & Predicate> + & WithPredObj>>; +export function isIntersectionOf< + T extends readonly [Predicate, ...Predicate[]], +>( + preds: T, +): + | Predicate + | Predicate> + & WithPredObj>> { + const predObj = {}; + const restPreds = preds.filter((pred) => { + if (!hasAnnotation(pred, "predObj")) { + return true; + } + Object.assign(predObj, pred.predObj); + }); + if (restPreds.length < preds.length) { + restPreds.push(isObjectOf(predObj)); + } + if (restPreds.length === 1) { + return restPreds[0]; + } + return rewriteName( + (x: unknown): x is IntersectionOf => restPreds.every((pred) => pred(x)), + "isIntersectionOf", + preds, + ); +} + +type TupleToIntersection = T extends readonly [] ? never + : T extends readonly [infer U] ? U + : T extends readonly [infer U, ...infer R] ? U & TupleToIntersection + : never; + +type IntersectionOf = TupleToIntersection< + { + -readonly [P in keyof T]: T[P] extends Predicate ? U : never; + } +>; diff --git a/is/intersection_of_test.ts b/is/intersection_of_test.ts new file mode 100644 index 0000000..56ead45 --- /dev/null +++ b/is/intersection_of_test.ts @@ -0,0 +1,135 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isIntersectionOf } from "./intersection_of.ts"; + +Deno.test("isIntersectionOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot( + t, + isIntersectionOf([ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ b: is.String }), + ]).name, + "Should return `isObjectOf`, if all predicates that", + ); + await assertSnapshot( + t, + isIntersectionOf([ + is.String, + ]).name, + "Should return as is, if there is only one predicate", + ); + await assertSnapshot( + t, + isIntersectionOf([ + is.Function, + is.ObjectOf({ b: is.String }), + ]).name, + ); + }); + await t.step("returns proper type predicate", () => { + const objPreds = [ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ b: is.String }), + ] as const; + const funcPreds = [ + is.Function, + is.ObjectOf({ b: is.String }), + ] as const; + const a: unknown = { a: 0, b: "a" }; + if (isIntersectionOf(objPreds)(a)) { + assertType>(true); + } + if (isIntersectionOf([is.String])(a)) { + assertType>(true); + } + if (isIntersectionOf(funcPreds)(a)) { + assertType< + Equal< + typeof a, + & ((...args: unknown[]) => unknown) + & { b: string } + > + >(true); + } + }); + await t.step("returns true on all of T", () => { + const objPreds = [ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ b: is.String }), + ] as const; + const funcPreds = [ + is.Function, + is.ObjectOf({ b: is.String }), + ] as const; + const f = Object.assign(() => void 0, { b: "a" }); + assertEquals(isIntersectionOf(objPreds)({ a: 0, b: "a" }), true); + assertEquals(isIntersectionOf([is.String])("a"), true); + assertEquals(isIntersectionOf(funcPreds)(f), true); + }); + await t.step("returns false on non of T", async (t) => { + const preds = [ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ b: is.String }), + ] as const; + assertEquals( + isIntersectionOf(preds)({ a: 0, b: 0 }), + false, + "Some properties has wrong type", + ); + assertEquals( + isIntersectionOf(preds)({ a: 0 }), + false, + "Some properties does not exists", + ); + await testWithExamples(t, isIntersectionOf(preds), { + excludeExamples: ["record"], + }); + }); + await t.step("returns false on non of T with any predicates", async (t) => { + const preds = [ + is.Function, + is.ObjectOf({ b: is.String }), + ] as const; + assertEquals( + isIntersectionOf(preds)({ b: "a" }), + false, + "Not a function object", + ); + assertEquals( + isIntersectionOf(preds)(() => void 0), + false, + "Some properties does not exists in Function object", + ); + await testWithExamples(t, isIntersectionOf(preds), { + excludeExamples: ["record"], + }); + }); + await t.step("asdf", async (t) => { + const preds = [ + is.ObjectOf({ a: is.String }), + is.ObjectOf({ b: is.String }), + ] as const; + assertEquals( + is.PickOf(isIntersectionOf(preds), ["a"])({ a: "a" }), + true, + "All properties has correct type", + ); + assertEquals( + isIntersectionOf(preds)({ b: "a" }), + false, + "Not a function object", + ); + assertEquals( + isIntersectionOf(preds)(() => void 0), + false, + "Some properties does not exists in Function object", + ); + await testWithExamples(t, isIntersectionOf(preds), { + excludeExamples: ["record"], + }); + }); +}); diff --git a/is/literal_of.ts b/is/literal_of.ts new file mode 100644 index 0000000..1d43d8b --- /dev/null +++ b/is/literal_of.ts @@ -0,0 +1,28 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate, Primitive } from "../type.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is a literal type of `pred`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.LiteralOf("hello"); + * const a: unknown = "hello"; + * if (isMyType(a)) { + * // a is narrowed to "hello" + * const _: "hello" = a; + * } + * ``` + */ +export function isLiteralOf( + literal: T, +): Predicate { + return rewriteName( + (x: unknown): x is T => x === literal, + "isLiteralOf", + literal, + ); +} diff --git a/is/literal_of_test.ts b/is/literal_of_test.ts new file mode 100644 index 0000000..424d828 --- /dev/null +++ b/is/literal_of_test.ts @@ -0,0 +1,32 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { isLiteralOf } from "./literal_of.ts"; + +Deno.test("isLiteralOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isLiteralOf("hello").name); + await assertSnapshot(t, isLiteralOf(100).name); + await assertSnapshot(t, isLiteralOf(100n).name); + await assertSnapshot(t, isLiteralOf(true).name); + await assertSnapshot(t, isLiteralOf(null).name); + await assertSnapshot(t, isLiteralOf(undefined).name); + await assertSnapshot(t, isLiteralOf(Symbol("asdf")).name); + }); + await t.step("returns proper type predicate", () => { + const pred = "hello"; + const a: unknown = "hello"; + if (isLiteralOf(pred)(a)) { + assertType>(true); + } + }); + await t.step("returns true on literal T", () => { + const pred = "hello"; + assertEquals(isLiteralOf(pred)("hello"), true); + }); + await t.step("returns false on non literal T", async (t) => { + const pred = "hello"; + await testWithExamples(t, isLiteralOf(pred)); + }); +}); diff --git a/is/literal_one_of.ts b/is/literal_one_of.ts new file mode 100644 index 0000000..5ae6f2a --- /dev/null +++ b/is/literal_one_of.ts @@ -0,0 +1,29 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate, Primitive } from "../type.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is one of literal type in `preds`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.LiteralOneOf(["hello", "world"] as const); + * const a: unknown = "hello"; + * if (isMyType(a)) { + * // a is narrowed to "hello" | "world" + * const _: "hello" | "world" = a; + * } + * ``` + */ +export function isLiteralOneOf( + literals: T, +): Predicate { + const s = new Set(literals); + return rewriteName( + (x: unknown): x is T[number] => s.has(x as T[number]), + "isLiteralOneOf", + literals, + ); +} diff --git a/is/literal_one_of_test.ts b/is/literal_one_of_test.ts new file mode 100644 index 0000000..93d77f7 --- /dev/null +++ b/is/literal_one_of_test.ts @@ -0,0 +1,27 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { isLiteralOneOf } from "./literal_one_of.ts"; + +Deno.test("isLiteralOneOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isLiteralOneOf(["hello", "world"]).name); + }); + await t.step("returns proper type predicate", () => { + const preds = ["hello", "world"] as const; + const a: unknown = "hello"; + if (isLiteralOneOf(preds)(a)) { + assertType>(true); + } + }); + await t.step("returns true on literal T", () => { + const preds = ["hello", "world"] as const; + assertEquals(isLiteralOneOf(preds)("hello"), true); + assertEquals(isLiteralOneOf(preds)("world"), true); + }); + await t.step("returns false on non literal T", async (t) => { + const preds = ["hello", "world"] as const; + await testWithExamples(t, isLiteralOneOf(preds)); + }); +}); diff --git a/is/map.ts b/is/map.ts new file mode 100644 index 0000000..0c045b6 --- /dev/null +++ b/is/map.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `Map`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = new Map([["a", 0], ["b", 1]]); + * if (is.Map(a)) { + * // a is narrowed to Map + * const _: Map = a; + * } + * ``` + */ +export function isMap(x: unknown): x is Map { + return x instanceof Map; +} diff --git a/is/map_of.ts b/is/map_of.ts new file mode 100644 index 0000000..3c4b4f1 --- /dev/null +++ b/is/map_of.ts @@ -0,0 +1,52 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate } from "../type.ts"; +import { isMap } from "./map.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `Map`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.MapOf(is.Number); + * const a: unknown = new Map([["a", 0], ["b", 1]]); + * if (isMyType(a)) { + * // a is narrowed to Map + * const _: Map = a; + * } + * ``` + * + * With predicate function for keys: + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.MapOf(is.Number, is.String); + * const a: unknown = new Map([["a", 0], ["b", 1]]); + * if (isMyType(a)) { + * // a is narrowed to Map + * const _: Map = a; + * } + * ``` + */ +export function isMapOf( + pred: Predicate, + predKey?: Predicate, +): Predicate> { + return rewriteName( + (x: unknown): x is Map => { + if (!isMap(x)) return false; + for (const entry of x.entries()) { + const [k, v] = entry; + if (!pred(v)) return false; + if (predKey && !predKey(k)) return false; + } + return true; + }, + "isMapOf", + pred, + predKey, + ); +} diff --git a/is/map_of_test.ts b/is/map_of_test.ts new file mode 100644 index 0000000..1b6d60f --- /dev/null +++ b/is/map_of_test.ts @@ -0,0 +1,66 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isMapOf } from "./map_of.ts"; + +Deno.test("isMapOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isMapOf(is.Number).name); + await assertSnapshot(t, isMapOf((_x): _x is string => false).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = new Map([["a", 0]]); + if (isMapOf(is.Number)(a)) { + assertType>>(true); + } + }); + await t.step("returns true on T map", () => { + assertEquals(isMapOf(is.Number)(new Map([["a", 0]])), true); + assertEquals(isMapOf(is.String)(new Map([["a", "a"]])), true); + assertEquals(isMapOf(is.Boolean)(new Map([["a", true]])), true); + }); + await t.step("returns false on non T map", () => { + assertEquals(isMapOf(is.String)(new Map([["a", 0]])), false); + assertEquals(isMapOf(is.Number)(new Map([["a", "a"]])), false); + assertEquals(isMapOf(is.String)(new Map([["a", true]])), false); + }); + await testWithExamples(t, isMapOf((_: unknown): _ is unknown => true), { + excludeExamples: ["map"], + }); +}); + +Deno.test("isMapOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isMapOf(is.Number, is.String).name); + await assertSnapshot( + t, + isMapOf((_x): _x is string => false, is.String).name, + ); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = new Map([["a", 0]]); + if (isMapOf(is.Number, is.String)(a)) { + assertType>>(true); + } + }); + await t.step("returns true on T map", () => { + assertEquals(isMapOf(is.Number, is.String)(new Map([["a", 0]])), true); + assertEquals(isMapOf(is.String, is.String)(new Map([["a", "a"]])), true); + assertEquals(isMapOf(is.Boolean, is.String)(new Map([["a", true]])), true); + }); + await t.step("returns false on non T map", () => { + assertEquals(isMapOf(is.String, is.String)(new Map([["a", 0]])), false); + assertEquals(isMapOf(is.Number, is.String)(new Map([["a", "a"]])), false); + assertEquals(isMapOf(is.String, is.String)(new Map([["a", true]])), false); + }); + await t.step("returns false on non K map", () => { + assertEquals(isMapOf(is.Number, is.Number)(new Map([["a", 0]])), false); + assertEquals(isMapOf(is.String, is.Number)(new Map([["a", "a"]])), false); + assertEquals(isMapOf(is.Boolean, is.Number)(new Map([["a", true]])), false); + }); + await testWithExamples(t, isMapOf((_: unknown): _ is unknown => true), { + excludeExamples: ["map"], + }); +}); diff --git a/is/map_test.ts b/is/map_test.ts new file mode 100644 index 0000000..b7e8af6 --- /dev/null +++ b/is/map_test.ts @@ -0,0 +1,8 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isMap } from "./map.ts"; + +Deno.test("isMap", async (t) => { + await testWithExamples(t, isMap, { + validExamples: ["map"], + }); +}); diff --git a/is/mod.ts b/is/mod.ts new file mode 100644 index 0000000..7f75b9d --- /dev/null +++ b/is/mod.ts @@ -0,0 +1,79 @@ +import { isAny } from "./any.ts"; +import { isArray } from "./array.ts"; +import { isArrayOf } from "./array_of.ts"; +import { isAsyncFunction } from "./async_function.ts"; +import { isBigint } from "./bigint.ts"; +import { isBoolean } from "./boolean.ts"; +import { isFunction } from "./function.ts"; +import { isInstanceOf } from "./instance_of.ts"; +import { isIntersectionOf } from "./intersection_of.ts"; +import { isLiteralOf } from "./literal_of.ts"; +import { isLiteralOneOf } from "./literal_one_of.ts"; +import { isMap } from "./map.ts"; +import { isMapOf } from "./map_of.ts"; +import { isNull } from "./null.ts"; +import { isNullish } from "./nullish.ts"; +import { isNumber } from "./number.ts"; +import { isObjectOf } from "./object_of.ts"; +import { isOmitOf } from "./omit_of.ts"; +import { isParametersOf } from "./parameters_of.ts"; +import { isPartialOf } from "./partial_of.ts"; +import { isPickOf } from "./pick_of.ts"; +import { isPrimitive } from "./primitive.ts"; +import { isRecord } from "./record.ts"; +import { isRecordObject } from "./record_object.ts"; +import { isRecordObjectOf } from "./record_object_of.ts"; +import { isRecordOf } from "./record_of.ts"; +import { isRequiredOf } from "./required_of.ts"; +import { isSet } from "./set.ts"; +import { isSetOf } from "./set_of.ts"; +import { isStrictOf } from "./strict_of.ts"; +import { isString } from "./string.ts"; +import { isSymbol } from "./symbol.ts"; +import { isSyncFunction } from "./sync_function.ts"; +import { isTupleOf } from "./tuple_of.ts"; +import { isUndefined } from "./undefined.ts"; +import { isUniformTupleOf } from "./uniform_tuple_of.ts"; +import { isUnionOf } from "./union_of.ts"; +import { isUnknown } from "./unknown.ts"; + +export const is = { + Any: isAny, + Array: isArray, + ArrayOf: isArrayOf, + AsyncFunction: isAsyncFunction, + Bigint: isBigint, + Boolean: isBoolean, + Function: isFunction, + InstanceOf: isInstanceOf, + IntersectionOf: isIntersectionOf, + LiteralOf: isLiteralOf, + LiteralOneOf: isLiteralOneOf, + Map: isMap, + MapOf: isMapOf, + Null: isNull, + Nullish: isNullish, + Number: isNumber, + ObjectOf: isObjectOf, + OmitOf: isOmitOf, + ParametersOf: isParametersOf, + PartialOf: isPartialOf, + PickOf: isPickOf, + Primitive: isPrimitive, + Record: isRecord, + RecordObject: isRecordObject, + RecordObjectOf: isRecordObjectOf, + RecordOf: isRecordOf, + RequiredOf: isRequiredOf, + Set: isSet, + SetOf: isSetOf, + StrictOf: isStrictOf, + String: isString, + Symbol: isSymbol, + SyncFunction: isSyncFunction, + TupleOf: isTupleOf, + Undefined: isUndefined, + UniformTupleOf: isUniformTupleOf, + UnionOf: isUnionOf, + Unknown: isUnknown, +} as const; diff --git a/is/mod_test.ts b/is/mod_test.ts new file mode 100644 index 0000000..7276c9e --- /dev/null +++ b/is/mod_test.ts @@ -0,0 +1,33 @@ +import { assertEquals } from "@std/assert"; +import { globToRegExp } from "@std/path"; +import { is } from "./mod.ts"; + +const excludes = [ + "mod.ts", + "*_test.ts", +]; + +Deno.test("is", async (t) => { + const names = await listIsFunctions(); + await t.step( + "must have all `is*` function aliases as entries", + () => { + assertEquals(Object.keys(is).sort(), names); + }, + ); +}); + +async function listIsFunctions(): Promise { + const patterns = excludes.map((p) => globToRegExp(p)); + const names: string[] = []; + for await (const entry of Deno.readDir(import.meta.dirname!)) { + if (!entry.isFile || !entry.name.endsWith(".ts")) continue; + if (patterns.some((p) => p.test(entry.name))) continue; + const mod = await import(import.meta.resolve(`./${entry.name}`)); + const isFunctionNames = Object.entries(mod) + .filter(([k, _]) => k.startsWith("is")) + .map(([k, _]) => k.slice(2)); + names.push(...isFunctionNames); + } + return names.toSorted(); +} diff --git a/is/null.ts b/is/null.ts new file mode 100644 index 0000000..703fcd8 --- /dev/null +++ b/is/null.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `null`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = null; + * if (is.Null(a)) { + * // a is narrowed to null + * const _: null = a; + * } + * ``` + */ +export function isNull(x: unknown): x is null { + return x === null; +} diff --git a/is/null_test.ts b/is/null_test.ts new file mode 100644 index 0000000..aa6ddb5 --- /dev/null +++ b/is/null_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isNull } from "./null.ts"; + +Deno.test("isNull", async (t) => { + await testWithExamples(t, isNull, { validExamples: ["null"] }); +}); diff --git a/is/nullish.ts b/is/nullish.ts new file mode 100644 index 0000000..0b476c5 --- /dev/null +++ b/is/nullish.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `null` or `undefined`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = null; + * if (is.Nullish(a)) { + * // a is narrowed to null | undefined + * const _: (null | undefined) = a; + * } + * ``` + */ +export function isNullish(x: unknown): x is null | undefined { + return x == null; +} diff --git a/is/nullish_test.ts b/is/nullish_test.ts new file mode 100644 index 0000000..a383654 --- /dev/null +++ b/is/nullish_test.ts @@ -0,0 +1,8 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isNullish } from "./nullish.ts"; + +Deno.test("isNullish", async (t) => { + await testWithExamples(t, isNullish, { + validExamples: ["null", "undefined"], + }); +}); diff --git a/is/number.ts b/is/number.ts new file mode 100644 index 0000000..9f2f429 --- /dev/null +++ b/is/number.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `number`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = 0; + * if (is.Number(a)) { + * // a is narrowed to number + * const _: number = a; + * } + * ``` + */ +export function isNumber(x: unknown): x is number { + return typeof x === "number"; +} diff --git a/is/number_test.ts b/is/number_test.ts new file mode 100644 index 0000000..a81c129 --- /dev/null +++ b/is/number_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isNumber } from "./number.ts"; + +Deno.test("isNumber", async (t) => { + await testWithExamples(t, isNumber, { validExamples: ["number"] }); +}); diff --git a/is/object_of.ts b/is/object_of.ts new file mode 100644 index 0000000..6e5f461 --- /dev/null +++ b/is/object_of.ts @@ -0,0 +1,71 @@ +import type { FlatType } from "../_typeutil.ts"; +import { rewriteName } from "../_funcutil.ts"; +import { + annotate, + type WithOptional, + type WithPredObj, +} from "../_annotation.ts"; +import type { Predicate } from "../type.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `ObjectOf`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * If `as.Optional` is specified in the predicate function, the property becomes optional. + * + * The number of keys of `x` must be greater than or equal to the number of keys of `predObj`. + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.ObjectOf({ + * a: is.Number, + * b: is.String, + * c: as.Optional(is.Boolean), + * }); + * const a: unknown = { a: 0, b: "a", other: "other" }; + * if (isMyType(a)) { + * // "other" key in `a` is ignored because of `options.strict` is `false`. + * // a is narrowed to { a: number; b: string; c?: boolean | undefined } + * const _: { a: number; b: string; c?: boolean | undefined } = a; + * } + * ``` + */ +export function isObjectOf< + T extends Record>, +>(predObj: T): Predicate> & WithPredObj { + return annotate( + rewriteName( + (x: unknown): x is ObjectOf => { + if ( + x == null || + typeof x !== "object" && typeof x !== "function" || + Array.isArray(x) + ) return false; + // Check each values + for (const k in predObj) { + if (!predObj[k]((x as T)[k])) return false; + } + return true; + }, + "isObjectOf", + predObj, + ), + "predObj", + predObj, + ); +} + +type ObjectOf>> = FlatType< + // Optional + & { + [K in keyof T as T[K] extends WithOptional ? K : never]?: + T[K] extends Predicate ? U : never; + } + // Non optional + & { + [K in keyof T as T[K] extends WithOptional ? never : K]: + T[K] extends Predicate ? U : never; + } +>; diff --git a/is/object_of_test.ts b/is/object_of_test.ts new file mode 100644 index 0000000..c356bff --- /dev/null +++ b/is/object_of_test.ts @@ -0,0 +1,90 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isObjectOf } from "./object_of.ts"; + +Deno.test("isObjectOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot( + t, + isObjectOf({ a: is.Number, b: is.String, c: is.Boolean }).name, + ); + await assertSnapshot( + t, + isObjectOf({ a: (_x): _x is string => false }).name, + ); + // Nested + await assertSnapshot( + t, + isObjectOf({ a: isObjectOf({ b: isObjectOf({ c: is.Boolean }) }) }).name, + ); + }); + await t.step("returns proper type predicate", () => { + const predObj = { + a: is.Number, + b: is.String, + c: is.Boolean, + }; + const a: unknown = { a: 0, b: "a", c: true }; + if (isObjectOf(predObj)(a)) { + assertType>(true); + } + }); + await t.step("returns true on T object", () => { + const predObj = { + a: is.Number, + b: is.String, + c: is.Boolean, + }; + assertEquals(isObjectOf(predObj)({ a: 0, b: "a", c: true }), true); + assertEquals( + isObjectOf(predObj)({ a: 0, b: "a", c: true, d: "ignored" }), + true, + "Object have an unknown property", + ); + assertEquals( + isObjectOf(predObj)( + Object.assign(() => void 0, { a: 0, b: "a", c: true }), + ), + true, + "Function object", + ); + }); + await t.step("returns false on non T object", () => { + const predObj = { + a: is.Number, + b: is.String, + c: is.Boolean, + }; + assertEquals(isObjectOf(predObj)("a"), false, "Value is not an object"); + assertEquals( + isObjectOf(predObj)({ a: 0, b: "a", c: "" }), + false, + "Object have a different type property", + ); + assertEquals( + isObjectOf(predObj)({ a: 0, b: "a" }), + false, + "Object does not have one property", + ); + assertEquals( + isObjectOf({ 0: is.String })(["a"]), + false, + "Value is not an object", + ); + }); + await t.step("returns true on T instance", () => { + const date = new Date(); + const predObj = { + getFullYear: is.Function, + }; + assertEquals(isObjectOf(predObj)(date), true, "Value is not an object"); + }); + await testWithExamples( + t, + isObjectOf({ a: (_: unknown): _ is unknown => false }), + { excludeExamples: ["record"] }, + ); +}); diff --git a/is/omit_of.ts b/is/omit_of.ts new file mode 100644 index 0000000..e35e3a5 --- /dev/null +++ b/is/omit_of.ts @@ -0,0 +1,44 @@ +import type { FlatType } from "../_typeutil.ts"; +import type { WithPredObj } from "../_annotation.ts"; +import type { Predicate } from "../type.ts"; +import { isObjectOf } from "./object_of.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `Omit, K>`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```typescript + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.OmitOf(is.ObjectOf({ + * a: is.Number, + * b: is.String, + * c: as.Optional(is.Boolean), + * }), ["a", "c"]); + * const a: unknown = { a: 0, b: "a", other: "other" }; + * if (isMyType(a)) { + * // The "a", "c", and "other" key in `a` is ignored. + * // 'a' is narrowed to { b: string } + * const _: { b: string } = a; + * } + * ``` + */ +export function isOmitOf< + T extends Record, + P extends Record>, + K extends keyof T, +>( + pred: Predicate & WithPredObj

, + keys: K[], +): + & Predicate>> + & WithPredObj

{ + const s = new Set(keys); + const predObj = Object.fromEntries( + Object.entries(pred.predObj).filter(([k]) => !s.has(k as K)), + ); + return isObjectOf(predObj) as + & Predicate>> + & WithPredObj

; +} diff --git a/is/omit_of_test.ts b/is/omit_of_test.ts new file mode 100644 index 0000000..d87787e --- /dev/null +++ b/is/omit_of_test.ts @@ -0,0 +1,51 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import type { Equal } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isOmitOf } from "./omit_of.ts"; + +Deno.test("isOmitOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: is.String, + c: is.Boolean, + }); + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isOmitOf(pred, ["b"]).name); + // Nestable + await assertSnapshot(t, isOmitOf(isOmitOf(pred, ["b"]), ["c"]).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isOmitOf(pred, ["b"])(a)) { + assertType< + Equal + >(true); + } + if (isOmitOf(isOmitOf(pred, ["b"]), ["c"])(a)) { + assertType< + Equal + >(true); + } + }); + await t.step("returns true on Omit object", () => { + assertEquals( + isOmitOf(pred, ["b"])({ a: 0, b: undefined, c: true }), + true, + ); + assertEquals(isOmitOf(pred, ["b", "c"])({ a: 0 }), true); + }); + await t.step("returns false on non Omit object", () => { + assertEquals( + isOmitOf(pred, ["b"])("a"), + false, + "Value is not an object", + ); + assertEquals( + isOmitOf(pred, ["b"])({ a: 0, b: "a", c: "" }), + false, + "Object have a different type property", + ); + }); +}); diff --git a/is/parameters_of.ts b/is/parameters_of.ts new file mode 100644 index 0000000..242f0ae --- /dev/null +++ b/is/parameters_of.ts @@ -0,0 +1,131 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { WithOptional } from "../_annotation.ts"; +import { hasOptional } from "../as/optional.ts"; +import type { Predicate, PredicateType } from "../type.ts"; +import { isArray } from "./array.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `ParametersOf` or `ParametersOf`. + * + * This is similar to `TupleOf` or `TupleOf`, but if `as.Optional()` is specified at the trailing, the trailing elements becomes optional and makes variable-length tuple. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.ParametersOf([ + * is.Number, + * as.Optional(is.String), + * is.Boolean, + * as.Optional(is.Number), + * as.Optional(is.String), + * as.Optional(is.Boolean), + * ] as const); + * const a: unknown = [0, undefined, "a"]; + * if (isMyType(a)) { + * // a is narrowed to [number, string | undefined, boolean, number?, string?, boolean?] + * const _: [number, string | undefined, boolean, number?, string?, boolean?] = a; + * } + * ``` + * + * With `predElse`: + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.ParametersOf( + * [ + * is.Number, + * as.Optional(is.String), + * as.Optional(is.Boolean), + * ] as const, + * is.ArrayOf(is.Number), + * ); + * const a: unknown = [0, "a", true, 0, 1, 2]; + * if (isMyType(a)) { + * // a is narrowed to [number, string?, boolean?, ...number[]] + * const _: [number, string?, boolean?, ...number[]] = a; + * } + * ``` + * + * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array + * used as `predTup`. If a type error occurs, try adding `as const` as follows: + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; + * const isMyType = is.ParametersOf(predTup); + * const a: unknown = [0, "a"]; + * if (isMyType(a)) { + * // a is narrowed to [number, string, boolean?] + * const _: [number, string, boolean?] = a; + * } + * ``` + */ +export function isParametersOf< + T extends readonly [...Predicate[]], +>( + predTup: T, +): Predicate>; +export function isParametersOf< + T extends readonly [...Predicate[]], + E extends Predicate, +>( + predTup: T, + predElse: E, +): Predicate<[...ParametersOf, ...PredicateType]>; +export function isParametersOf< + T extends readonly [...Predicate[]], + E extends Predicate, +>( + predTup: T, + predElse?: E, +): Predicate | [...ParametersOf, ...PredicateType]> { + const requiresLength = 1 + + predTup.findLastIndex((pred) => !hasOptional(pred)); + if (!predElse) { + return rewriteName( + (x: unknown): x is ParametersOf => { + if ( + !isArray(x) || x.length < requiresLength || x.length > predTup.length + ) { + return false; + } + return predTup.every((pred, i) => pred(x[i])); + }, + "isParametersOf", + predTup, + ); + } else { + return rewriteName( + (x: unknown): x is [...ParametersOf, ...PredicateType] => { + if (!isArray(x) || x.length < requiresLength) { + return false; + } + const head = x.slice(0, predTup.length); + const tail = x.slice(predTup.length); + return predTup.every((pred, i) => pred(head[i])) && predElse(tail); + }, + "isParametersOf", + predTup, + predElse, + ); + } +} + +type ParametersOf = T extends readonly [] ? [] + : T extends readonly [...infer P, infer R] + // Tuple of predicates + ? P extends Predicate[] + ? R extends Predicate & WithOptional + // Last parameter is optional + ? [...ParametersOf

, PredicateType?] + // Last parameter is NOT optional + : [...ParametersOf

, PredicateType] + : never + // Array of predicates + : { + -readonly [P in keyof T]: T[P] extends Predicate ? U : never; + }; diff --git a/is/parameters_of_test.ts b/is/parameters_of_test.ts new file mode 100644 index 0000000..f0dfffa --- /dev/null +++ b/is/parameters_of_test.ts @@ -0,0 +1,153 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { as } from "../as/mod.ts"; +import { is } from "./mod.ts"; +import { isParametersOf } from "./parameters_of.ts"; + +Deno.test("isParametersOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot( + t, + isParametersOf([is.Number, is.String, as.Optional(is.Boolean)]).name, + ); + await assertSnapshot( + t, + isParametersOf([(_x): _x is string => false]).name, + ); + await assertSnapshot( + t, + isParametersOf([]).name, + ); + // Nested + await assertSnapshot( + t, + isParametersOf([ + isParametersOf([ + isParametersOf([is.Number, is.String, as.Optional(is.Boolean)]), + ]), + ]).name, + ); + }); + await t.step("returns proper type predicate", () => { + const predTup = [ + as.Optional(is.Number), + is.String, + as.Optional(is.String), + as.Optional(is.Boolean), + ] as const; + const a: unknown = [0, "a"]; + if (isParametersOf(predTup)(a)) { + assertType< + Equal + >(true); + } + }); + await t.step("returns true on T tuple", () => { + const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; + assertEquals(isParametersOf(predTup)([0, "a", true]), true); + assertEquals(isParametersOf(predTup)([0, "a"]), true); + }); + await t.step("returns false on non T tuple", () => { + const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; + assertEquals(isParametersOf(predTup)([0, 1, 2]), false); + assertEquals(isParametersOf(predTup)([0, "a", true, 0]), false); + }); + await testWithExamples( + t, + isParametersOf([(_: unknown): _ is unknown => true]), + { + excludeExamples: ["array"], + }, + ); +}); + +Deno.test("isParametersOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot( + t, + isParametersOf([is.Number, is.String, as.Optional(is.Boolean)], is.Array) + .name, + ); + await assertSnapshot( + t, + isParametersOf([(_x): _x is string => false], is.ArrayOf(is.String)) + .name, + ); + // Empty + await assertSnapshot( + t, + isParametersOf([], is.ArrayOf(is.String)).name, + ); + // Nested + await assertSnapshot( + t, + isParametersOf([ + isParametersOf( + [isParametersOf( + [is.Number, is.String, as.Optional(is.Boolean)], + is.Array, + )], + is.Array, + ), + ]).name, + ); + }); + await t.step("returns proper type predicate", () => { + const predTup = [ + as.Optional(is.Number), + is.String, + as.Optional(is.String), + as.Optional(is.Boolean), + ] as const; + const predElse = is.ArrayOf(is.Number); + const a: unknown = [0, "a"]; + if (isParametersOf(predTup, predElse)(a)) { + assertType< + Equal< + typeof a, + [number | undefined, string, string?, boolean?, ...number[]] + > + >( + true, + ); + } + }); + await t.step("returns true on T tuple", () => { + const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; + const predElse = is.ArrayOf(is.Number); + assertEquals( + isParametersOf(predTup, predElse)([0, "a", true, 0, 1, 2]), + true, + ); + assertEquals( + isParametersOf(predTup, predElse)([0, "a", undefined, 0, 1, 2]), + true, + ); + assertEquals(isParametersOf(predTup, predElse)([0, "a"]), true); + }); + await t.step("returns false on non T tuple", () => { + const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; + const predElse = is.ArrayOf(is.String); + assertEquals(isParametersOf(predTup, predElse)([0, 1, 2, 0, 1, 2]), false); + assertEquals(isParametersOf(predTup, predElse)([0, "a", 0, 1, 2]), false); + assertEquals( + isParametersOf(predTup, predElse)([0, "a", true, 0, 1, 2]), + false, + ); + assertEquals( + isParametersOf(predTup, predElse)([0, "a", undefined, 0, 1, 2]), + false, + ); + assertEquals(isParametersOf(predTup, predElse)([0, "a", "b"]), false); + }); + const predElse = is.Array; + await testWithExamples( + t, + isParametersOf([(_: unknown): _ is unknown => true], predElse), + { + excludeExamples: ["array"], + }, + ); +}); diff --git a/is/partial_of.ts b/is/partial_of.ts new file mode 100644 index 0000000..e8157f0 --- /dev/null +++ b/is/partial_of.ts @@ -0,0 +1,42 @@ +import type { FlatType } from "../_typeutil.ts"; +import type { WithPredObj } from "../_annotation.ts"; +import { asOptional } from "../as/optional.ts"; +import type { Predicate } from "../type.ts"; +import { isObjectOf } from "./object_of.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `Partial>`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```typescript + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.PartialOf(is.ObjectOf({ + * a: is.Number, + * b: is.UnionOf([is.String, is.Undefined]), + * c: as.Optional(is.Boolean), + * })); + * const a: unknown = { a: undefined, other: "other" }; + * if (isMyType(a)) { + * // The "other" key in `a` is ignored. + * // 'a' is narrowed to { a?: number | undefined; b?: string | undefined; c?: boolean | undefined } + * const _: { a?: number | undefined; b?: string | undefined; c?: boolean | undefined } = a; + * } + * ``` + */ +export function isPartialOf< + T extends Record, + P extends Record>, +>( + pred: Predicate & WithPredObj

, +): + & Predicate>> + & WithPredObj

{ + const predObj = Object.fromEntries( + Object.entries(pred.predObj).map(([k, v]) => [k, asOptional(v)]), + ) as Record>; + return isObjectOf(predObj) as + & Predicate>> + & WithPredObj

; +} diff --git a/is/partial_of_test.ts b/is/partial_of_test.ts new file mode 100644 index 0000000..78483db --- /dev/null +++ b/is/partial_of_test.ts @@ -0,0 +1,43 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import type { Equal } from "../_testutil.ts"; +import { as } from "../as/mod.ts"; +import { is } from "./mod.ts"; +import { isPartialOf } from "./partial_of.ts"; + +Deno.test("isPartialOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: is.UnionOf([is.String, is.Undefined]), + c: as.Optional(is.Boolean), + }); + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isPartialOf(pred).name); + // Nestable (no effect) + await assertSnapshot(t, isPartialOf(isPartialOf(pred)).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isPartialOf(pred)(a)) { + assertType< + Equal> + >(true); + } + }); + await t.step("returns true on Partial object", () => { + assertEquals( + isPartialOf(pred)({ a: undefined, b: undefined, c: undefined }), + true, + ); + assertEquals(isPartialOf(pred)({}), true); + }); + await t.step("returns false on non Partial object", () => { + assertEquals(isPartialOf(pred)("a"), false, "Value is not an object"); + assertEquals( + isPartialOf(pred)({ a: 0, b: "a", c: "" }), + false, + "Object have a different type property", + ); + }); +}); diff --git a/is/pick_of.ts b/is/pick_of.ts new file mode 100644 index 0000000..18f6e82 --- /dev/null +++ b/is/pick_of.ts @@ -0,0 +1,44 @@ +import type { FlatType } from "../_typeutil.ts"; +import type { WithPredObj } from "../_annotation.ts"; +import type { Predicate } from "../type.ts"; +import { isObjectOf } from "./object_of.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `Pick, K>`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```typescript + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.PickOf(is.ObjectOf({ + * a: is.Number, + * b: is.String, + * c: as.Optional(is.Boolean), + * }), ["a", "c"]); + * const a: unknown = { a: 0, b: "a", other: "other" }; + * if (isMyType(a)) { + * // The "b" and "other" key in `a` is ignored. + * // 'a' is narrowed to { a: number; c?: boolean | undefined } + * const _: { a: number; c?: boolean | undefined } = a; + * } + * ``` + */ +export function isPickOf< + T extends Record, + P extends Record>, + K extends keyof T, +>( + pred: Predicate & WithPredObj

, + keys: K[], +): + & Predicate>> + & WithPredObj

{ + const s = new Set(keys); + const predObj = Object.fromEntries( + Object.entries(pred.predObj).filter(([k]) => s.has(k as K)), + ); + return isObjectOf(predObj) as + & Predicate>> + & WithPredObj

; +} diff --git a/is/pick_of_test.ts b/is/pick_of_test.ts new file mode 100644 index 0000000..5213e5a --- /dev/null +++ b/is/pick_of_test.ts @@ -0,0 +1,51 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import type { Equal } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isPickOf } from "./pick_of.ts"; + +Deno.test("isPickOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: is.String, + c: is.Boolean, + }); + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isPickOf(pred, ["a", "c"]).name); + // Nestable + await assertSnapshot(t, isPickOf(isPickOf(pred, ["a", "c"]), ["a"]).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isPickOf(pred, ["a", "c"])(a)) { + assertType< + Equal + >(true); + } + if (isPickOf(isPickOf(pred, ["a", "c"]), ["a"])(a)) { + assertType< + Equal + >(true); + } + }); + await t.step("returns true on Pick object", () => { + assertEquals( + isPickOf(pred, ["a", "c"])({ a: 0, b: undefined, c: true }), + true, + ); + assertEquals(isPickOf(pred, ["a"])({ a: 0 }), true); + }); + await t.step("returns false on non Pick object", () => { + assertEquals( + isPickOf(pred, ["a", "c"])("a"), + false, + "Value is not an object", + ); + assertEquals( + isPickOf(pred, ["a", "c"])({ a: 0, b: "a", c: "" }), + false, + "Object have a different type property", + ); + }); +}); diff --git a/is/primitive.ts b/is/primitive.ts new file mode 100644 index 0000000..501a94b --- /dev/null +++ b/is/primitive.ts @@ -0,0 +1,26 @@ +import type { Primitive } from "../type.ts"; + +const primitiveSet: Set = new Set([ + "string", + "number", + "bigint", + "boolean", + "symbol", +]); + +/** + * Return `true` if the type of `x` is `Primitive`. + * + * ```ts + * import { is, type Primitive } from "@core/unknownutil"; + * + * const a: unknown = 0; + * if (is.Primitive(a)) { + * // a is narrowed to Primitive + * const _: Primitive = a; + * } + * ``` + */ +export function isPrimitive(x: unknown): x is Primitive { + return x == null || primitiveSet.has(typeof x); +} diff --git a/is/primitive_test.ts b/is/primitive_test.ts new file mode 100644 index 0000000..29b806b --- /dev/null +++ b/is/primitive_test.ts @@ -0,0 +1,16 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isPrimitive } from "./primitive.ts"; + +Deno.test("isPrimitive", async (t) => { + await testWithExamples(t, isPrimitive, { + validExamples: [ + "string", + "number", + "bigint", + "boolean", + "null", + "undefined", + "symbol", + ], + }); +}); diff --git a/is/record.ts b/is/record.ts new file mode 100644 index 0000000..9e263f0 --- /dev/null +++ b/is/record.ts @@ -0,0 +1,26 @@ +/** + * Return `true` if the type of `x` satisfies `Record`. + * + * Note that this function returns `true` for ambiguous instances like `Set`, `Map`, `Date`, `Promise`, etc. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = {"a": 0, "b": 1}; + * if (is.Record(a)) { + * // a is narrowed to Record + * const _: Record = a; + * } + * + * const b: unknown = new Set(); + * if (is.Record(b)) { + * // b is narrowed to Record + * const _: Record = b; + * } + * ``` + */ +export function isRecord( + x: unknown, +): x is Record { + return x != null && !Array.isArray(x) && typeof x === "object"; +} diff --git a/is/record_object.ts b/is/record_object.ts new file mode 100644 index 0000000..3442142 --- /dev/null +++ b/is/record_object.ts @@ -0,0 +1,26 @@ +/** + * Return `true` if the type of `x` is an object instance that satisfies `Record`. + * + * Note that this function check if the `x` is an instance of `Object`. + * Use `isRecord` instead if you want to check if the `x` satisfies the `Record` type. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = {"a": 0, "b": 1}; + * if (is.RecordObject(a)) { + * // a is narrowed to Record + * const _: Record = a; + * } + * + * const b: unknown = new Set(); + * if (is.RecordObject(b)) { + * // b is not a raw object, so it is not narrowed + * } + * ``` + */ +export function isRecordObject( + x: unknown, +): x is Record { + return x != null && typeof x === "object" && x.constructor === Object; +} diff --git a/is/record_object_of.ts b/is/record_object_of.ts new file mode 100644 index 0000000..627a64c --- /dev/null +++ b/is/record_object_of.ts @@ -0,0 +1,54 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate } from "../type.ts"; +import { isRecordObject } from "./record_object.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is an Object instance that satisfies `Record`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * Note that this function check if the `x` is an instance of `Object`. + * Use `isRecordOf` instead if you want to check if the `x` satisfies the `Record` type. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.RecordObjectOf(is.Number); + * const a: unknown = {"a": 0, "b": 1}; + * if (isMyType(a)) { + * // a is narrowed to Record + * const _: Record = a; + * } + * ``` + * + * With predicate function for keys: + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.RecordObjectOf(is.Number, is.String); + * const a: unknown = {"a": 0, "b": 1}; + * if (isMyType(a)) { + * // a is narrowed to Record + * const _: Record = a; + * } + * ``` + */ +export function isRecordObjectOf( + pred: Predicate, + predKey?: Predicate, +): Predicate> { + return rewriteName( + (x: unknown): x is Record => { + if (!isRecordObject(x)) return false; + for (const k in x) { + if (!pred(x[k])) return false; + if (predKey && !predKey(k)) return false; + } + return true; + }, + "isRecordObjectOf", + pred, + predKey, + ); +} diff --git a/is/record_object_of_test.ts b/is/record_object_of_test.ts new file mode 100644 index 0000000..b209932 --- /dev/null +++ b/is/record_object_of_test.ts @@ -0,0 +1,74 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isRecordObjectOf } from "./record_object_of.ts"; + +Deno.test("isRecordObjectOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isRecordObjectOf(is.Number).name); + await assertSnapshot(t, isRecordObjectOf((_x): _x is string => false).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0 }; + if (isRecordObjectOf(is.Number)(a)) { + assertType>>(true); + } + }); + await t.step("returns true on T record", () => { + assertEquals(isRecordObjectOf(is.Number)({ a: 0 }), true); + assertEquals(isRecordObjectOf(is.String)({ a: "a" }), true); + assertEquals(isRecordObjectOf(is.Boolean)({ a: true }), true); + }); + await t.step("returns false on non T record", () => { + assertEquals(isRecordObjectOf(is.String)({ a: 0 }), false); + assertEquals(isRecordObjectOf(is.Number)({ a: "a" }), false); + assertEquals(isRecordObjectOf(is.String)({ a: true }), false); + }); + await testWithExamples( + t, + isRecordObjectOf((_: unknown): _ is unknown => true), + { + excludeExamples: ["record"], + }, + ); +}); + +Deno.test("isRecordObjectOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isRecordObjectOf(is.Number, is.String).name); + await assertSnapshot( + t, + isRecordObjectOf((_x): _x is string => false, is.String).name, + ); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0 }; + if (isRecordObjectOf(is.Number, is.String)(a)) { + assertType>>(true); + } + }); + await t.step("returns true on T record", () => { + assertEquals(isRecordObjectOf(is.Number, is.String)({ a: 0 }), true); + assertEquals(isRecordObjectOf(is.String, is.String)({ a: "a" }), true); + assertEquals(isRecordObjectOf(is.Boolean, is.String)({ a: true }), true); + }); + await t.step("returns false on non T record", () => { + assertEquals(isRecordObjectOf(is.String, is.String)({ a: 0 }), false); + assertEquals(isRecordObjectOf(is.Number, is.String)({ a: "a" }), false); + assertEquals(isRecordObjectOf(is.String, is.String)({ a: true }), false); + }); + await t.step("returns false on non K record", () => { + assertEquals(isRecordObjectOf(is.Number, is.Number)({ a: 0 }), false); + assertEquals(isRecordObjectOf(is.String, is.Number)({ a: "a" }), false); + assertEquals(isRecordObjectOf(is.Boolean, is.Number)({ a: true }), false); + }); + await testWithExamples( + t, + isRecordObjectOf((_: unknown): _ is unknown => true), + { + excludeExamples: ["record"], + }, + ); +}); diff --git a/is/record_object_test.ts b/is/record_object_test.ts new file mode 100644 index 0000000..73c71ee --- /dev/null +++ b/is/record_object_test.ts @@ -0,0 +1,8 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isRecordObject } from "./record_object.ts"; + +Deno.test("isRecordObject", async (t) => { + await testWithExamples(t, isRecordObject, { + validExamples: ["record"], + }); +}); diff --git a/is/record_of.ts b/is/record_of.ts new file mode 100644 index 0000000..399d5f9 --- /dev/null +++ b/is/record_of.ts @@ -0,0 +1,51 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate } from "../type.ts"; +import { isRecord } from "./record.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` satisfies `Record`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.RecordOf(is.Number); + * const a: unknown = {"a": 0, "b": 1}; + * if (isMyType(a)) { + * // a is narrowed to Record + * const _: Record = a; + * } + * ``` + * + * With predicate function for keys: + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.RecordOf(is.Number, is.String); + * const a: unknown = {"a": 0, "b": 1}; + * if (isMyType(a)) { + * // a is narrowed to Record + * const _: Record = a; + * } + * ``` + */ +export function isRecordOf( + pred: Predicate, + predKey?: Predicate, +): Predicate> { + return rewriteName( + (x: unknown): x is Record => { + if (!isRecord(x)) return false; + for (const k in x) { + if (!pred(x[k])) return false; + if (predKey && !predKey(k)) return false; + } + return true; + }, + "isRecordOf", + pred, + predKey, + ); +} diff --git a/is/record_of_test.ts b/is/record_of_test.ts new file mode 100644 index 0000000..3c30c13 --- /dev/null +++ b/is/record_of_test.ts @@ -0,0 +1,74 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isRecordOf } from "./record_of.ts"; + +Deno.test("isRecordOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isRecordOf(is.Number).name); + await assertSnapshot(t, isRecordOf((_x): _x is string => false).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0 }; + if (isRecordOf(is.Number)(a)) { + assertType>>(true); + } + }); + await t.step("returns true on T record", () => { + assertEquals(isRecordOf(is.Number)({ a: 0 }), true); + assertEquals(isRecordOf(is.String)({ a: "a" }), true); + assertEquals(isRecordOf(is.Boolean)({ a: true }), true); + }); + await t.step("returns false on non T record", () => { + assertEquals(isRecordOf(is.String)({ a: 0 }), false); + assertEquals(isRecordOf(is.Number)({ a: "a" }), false); + assertEquals(isRecordOf(is.String)({ a: true }), false); + }); + await testWithExamples( + t, + isRecordOf((_: unknown): _ is unknown => true), + { + excludeExamples: ["record", "date", "promise", "set", "map"], + }, + ); +}); + +Deno.test("isRecordOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isRecordOf(is.Number, is.String).name); + await assertSnapshot( + t, + isRecordOf((_x): _x is string => false, is.String).name, + ); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0 }; + if (isRecordOf(is.Number, is.String)(a)) { + assertType>>(true); + } + }); + await t.step("returns true on T record", () => { + assertEquals(isRecordOf(is.Number, is.String)({ a: 0 }), true); + assertEquals(isRecordOf(is.String, is.String)({ a: "a" }), true); + assertEquals(isRecordOf(is.Boolean, is.String)({ a: true }), true); + }); + await t.step("returns false on non T record", () => { + assertEquals(isRecordOf(is.String, is.String)({ a: 0 }), false); + assertEquals(isRecordOf(is.Number, is.String)({ a: "a" }), false); + assertEquals(isRecordOf(is.String, is.String)({ a: true }), false); + }); + await t.step("returns false on non K record", () => { + assertEquals(isRecordOf(is.Number, is.Number)({ a: 0 }), false); + assertEquals(isRecordOf(is.String, is.Number)({ a: "a" }), false); + assertEquals(isRecordOf(is.Boolean, is.Number)({ a: true }), false); + }); + await testWithExamples( + t, + isRecordOf((_: unknown): _ is unknown => true), + { + excludeExamples: ["record", "date", "promise", "set", "map"], + }, + ); +}); diff --git a/is/record_test.ts b/is/record_test.ts new file mode 100644 index 0000000..dfa182e --- /dev/null +++ b/is/record_test.ts @@ -0,0 +1,8 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isRecord } from "./record.ts"; + +Deno.test("isRecord", async (t) => { + await testWithExamples(t, isRecord, { + validExamples: ["record", "date", "promise", "set", "map"], + }); +}); diff --git a/is/required_of.ts b/is/required_of.ts new file mode 100644 index 0000000..6f3167e --- /dev/null +++ b/is/required_of.ts @@ -0,0 +1,41 @@ +import type { FlatType } from "../_typeutil.ts"; +import type { WithPredObj } from "../_annotation.ts"; +import { asUnoptional } from "../as/optional.ts"; +import type { Predicate } from "../type.ts"; +import { isObjectOf } from "./object_of.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `Required>`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```typescript + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.RequiredOf(is.ObjectOf({ + * a: is.Number, + * b: is.UnionOf([is.String, is.Undefined]), + * c: as.Optional(is.Boolean), + * })); + * const a: unknown = { a: 0, b: "b", c: true, other: "other" }; + * if (isMyType(a)) { + * // 'a' is narrowed to { a: number; b: string | undefined; c: boolean } + * const _: { a: number; b: string | undefined; c: boolean } = a; + * } + * ``` + */ +export function isRequiredOf< + T extends Record, + P extends Record>, +>( + pred: Predicate & WithPredObj

, +): + & Predicate>> + & WithPredObj

{ + const predObj = Object.fromEntries( + Object.entries(pred.predObj).map(([k, v]) => [k, asUnoptional(v)]), + ); + return isObjectOf(predObj) as + & Predicate>> + & WithPredObj

; +} diff --git a/is/required_of_test.ts b/is/required_of_test.ts new file mode 100644 index 0000000..b5b9743 --- /dev/null +++ b/is/required_of_test.ts @@ -0,0 +1,48 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import type { Equal } from "../_testutil.ts"; +import { as } from "../as/mod.ts"; +import { is } from "./mod.ts"; +import { isRequiredOf } from "./required_of.ts"; + +Deno.test("isRequiredOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: is.UnionOf([is.String, is.Undefined]), + c: as.Optional(is.Boolean), + }); + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isRequiredOf(pred).name); + // Nestable (no effect) + await assertSnapshot(t, isRequiredOf(isRequiredOf(pred)).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isRequiredOf(pred)(a)) { + assertType< + Equal + >(true); + } + }); + await t.step("returns true on Required object", () => { + assertEquals( + isRequiredOf(pred)({ a: undefined, b: undefined, c: undefined }), + false, + "Object does not have required properties", + ); + assertEquals( + isRequiredOf(pred)({}), + false, + "Object does not have required properties", + ); + }); + await t.step("returns false on non Required object", () => { + assertEquals(isRequiredOf(pred)("a"), false, "Value is not an object"); + assertEquals( + isRequiredOf(pred)({ a: 0, b: "a", c: "" }), + false, + "Object have a different type property", + ); + }); +}); diff --git a/is/set.ts b/is/set.ts new file mode 100644 index 0000000..5e3bb8a --- /dev/null +++ b/is/set.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `Set`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = new Set([0, 1, 2]); + * if (is.Set(a)) { + * // a is narrowed to Set + * const _: Set = a; + * } + * ``` + */ +export function isSet(x: unknown): x is Set { + return x instanceof Set; +} diff --git a/is/set_of.ts b/is/set_of.ts new file mode 100644 index 0000000..47de82d --- /dev/null +++ b/is/set_of.ts @@ -0,0 +1,35 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate } from "../type.ts"; +import { isSet } from "./set.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `Set`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.SetOf(is.String); + * const a: unknown = new Set(["a", "b", "c"]); + * if (isMyType(a)) { + * // a is narrowed to Set + * const _: Set = a; + * } + * ``` + */ +export function isSetOf( + pred: Predicate, +): Predicate> { + return rewriteName( + (x: unknown): x is Set => { + if (!isSet(x)) return false; + for (const v of x.values()) { + if (!pred(v)) return false; + } + return true; + }, + "isSetOf", + pred, + ); +} diff --git a/is/set_of_test.ts b/is/set_of_test.ts new file mode 100644 index 0000000..48aacd1 --- /dev/null +++ b/is/set_of_test.ts @@ -0,0 +1,32 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isSetOf } from "./set_of.ts"; + +Deno.test("isSetOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isSetOf(is.Number).name); + await assertSnapshot(t, isSetOf((_x): _x is string => false).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = new Set([0, 1, 2]); + if (isSetOf(is.Number)(a)) { + assertType>>(true); + } + }); + await t.step("returns true on T set", () => { + assertEquals(isSetOf(is.Number)(new Set([0, 1, 2])), true); + assertEquals(isSetOf(is.String)(new Set(["a", "b", "c"])), true); + assertEquals(isSetOf(is.Boolean)(new Set([true, false, true])), true); + }); + await t.step("returns false on non T set", () => { + assertEquals(isSetOf(is.String)(new Set([0, 1, 2])), false); + assertEquals(isSetOf(is.Number)(new Set(["a", "b", "c"])), false); + assertEquals(isSetOf(is.String)(new Set([true, false, true])), false); + }); + await testWithExamples(t, isSetOf((_: unknown): _ is unknown => true), { + excludeExamples: ["set"], + }); +}); diff --git a/is/set_test.ts b/is/set_test.ts new file mode 100644 index 0000000..d072835 --- /dev/null +++ b/is/set_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isSet } from "./set.ts"; + +Deno.test("isSet", async (t) => { + await testWithExamples(t, isSet, { validExamples: ["set"] }); +}); diff --git a/is/strict_of.ts b/is/strict_of.ts new file mode 100644 index 0000000..d2f0d62 --- /dev/null +++ b/is/strict_of.ts @@ -0,0 +1,49 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { WithPredObj } from "../_annotation.ts"; +import type { Predicate } from "../type.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is strictly follow the `ObjectOf`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * If `as.Optional` is specified in the predicate function, the property becomes optional. + * + * The number of keys of `x` must be equal to the number of non optional keys of `predObj`. + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.StrictOf(is.ObjectOf({ + * a: is.Number, + * b: is.String, + * c: as.Optional(is.Boolean), + * })); + * const a: unknown = { a: 0, b: "a", other: "other" }; + * if (isMyType(a)) { + * // This block will not be executed because of "other" key in `a`. + * } + * ``` + */ +export function isStrictOf< + T extends Record, + P extends Record>, +>( + pred: + & Predicate + & WithPredObj

, +): + & Predicate + & WithPredObj

{ + const s = new Set(Object.keys(pred.predObj)); + return rewriteName( + (x: unknown): x is T => { + if (!pred(x)) return false; + // deno-lint-ignore no-explicit-any + const ks = Object.keys(x as any); + return ks.length <= s.size && ks.every((k) => s.has(k)); + }, + "isStrictOf", + pred, + ) as Predicate & WithPredObj

; +} diff --git a/is/strict_of_test.ts b/is/strict_of_test.ts new file mode 100644 index 0000000..f542901 --- /dev/null +++ b/is/strict_of_test.ts @@ -0,0 +1,158 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { as } from "../as/mod.ts"; +import { is } from "./mod.ts"; +import { isStrictOf } from "./strict_of.ts"; + +Deno.test("isStrictOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot( + t, + isStrictOf(is.ObjectOf({ a: is.Number, b: is.String, c: is.Boolean })) + .name, + ); + await assertSnapshot( + t, + isStrictOf(is.ObjectOf({ a: (_x): _x is string => false })).name, + ); + // Nested + await assertSnapshot( + t, + isStrictOf( + is.ObjectOf({ + a: isStrictOf( + is.ObjectOf({ b: isStrictOf(is.ObjectOf({ c: is.Boolean })) }), + ), + }), + ).name, + ); + }); + await t.step("returns proper type predicate", () => { + const predObj = { + a: is.Number, + b: is.String, + c: is.Boolean, + }; + const a: unknown = { a: 0, b: "a", c: true }; + if (isStrictOf(is.ObjectOf(predObj))(a)) { + assertType>(true); + } + }); + await t.step("returns true on T object", () => { + const predObj = { + a: is.Number, + b: is.String, + c: is.Boolean, + }; + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ a: 0, b: "a", c: true }), + true, + ); + }); + await t.step("returns false on non T object", () => { + const predObj = { + a: is.Number, + b: is.String, + c: is.Boolean, + }; + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ a: 0, b: "a", c: "" }), + false, + "Object have a different type property", + ); + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ a: 0, b: "a" }), + false, + "Object does not have one property", + ); + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ + a: 0, + b: "a", + c: true, + d: "invalid", + }), + false, + "Object have an unknown property", + ); + }); + await testWithExamples( + t, + isStrictOf(is.ObjectOf({ a: (_: unknown): _ is unknown => false })), + { excludeExamples: ["record"] }, + ); + await t.step("with optional properties", async (t) => { + await t.step("returns proper type predicate", () => { + const predObj = { + a: is.Number, + b: is.UnionOf([is.String, is.Undefined]), + c: as.Optional(is.Boolean), + }; + const a: unknown = { a: 0, b: "a" }; + if (isStrictOf(is.ObjectOf(predObj))(a)) { + assertType< + Equal + >(true); + } + }); + await t.step("returns true on T object", () => { + const predObj = { + a: is.Number, + b: is.UnionOf([is.String, is.Undefined]), + c: as.Optional(is.Boolean), + }; + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ a: 0, b: "a", c: true }), + true, + ); + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ a: 0, b: "a" }), + true, + "Object does not have an optional property", + ); + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ a: 0, b: "a", c: undefined }), + true, + "Object has `undefined` as value of optional property", + ); + }); + await t.step("returns false on non T object", () => { + const predObj = { + a: is.Number, + b: is.UnionOf([is.String, is.Undefined]), + c: as.Optional(is.Boolean), + }; + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ a: 0, b: "a", c: "" }), + false, + "Object have a different type property", + ); + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ a: 0, b: "a", c: null }), + false, + "Object has `null` as value of optional property", + ); + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ + a: 0, + b: "a", + c: true, + d: "invalid", + }), + false, + "Object have an unknown property", + ); + assertEquals( + isStrictOf(is.ObjectOf(predObj))({ + a: 0, + b: "a", + d: "invalid", + }), + false, + "Object have the same number of properties but an unknown property exists", + ); + }); + }); +}); diff --git a/is/string.ts b/is/string.ts new file mode 100644 index 0000000..1725bef --- /dev/null +++ b/is/string.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `string`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = "a"; + * if (is.String(a)) { + * // a is narrowed to string + * const _: string = a; + * } + * ``` + */ +export function isString(x: unknown): x is string { + return typeof x === "string"; +} diff --git a/is/string_test.ts b/is/string_test.ts new file mode 100644 index 0000000..be147d4 --- /dev/null +++ b/is/string_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isString } from "./string.ts"; + +Deno.test("isString", async (t) => { + await testWithExamples(t, isString, { validExamples: ["string"] }); +}); diff --git a/is/symbol.ts b/is/symbol.ts new file mode 100644 index 0000000..bb35a85 --- /dev/null +++ b/is/symbol.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `symbol`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = Symbol("symbol"); + * if (is.Symbol(a)) { + * // a is narrowed to symbol + * const _: symbol = a; + * } + * ``` + */ +export function isSymbol(x: unknown): x is symbol { + return typeof x === "symbol"; +} diff --git a/is/symbol_test.ts b/is/symbol_test.ts new file mode 100644 index 0000000..f22f262 --- /dev/null +++ b/is/symbol_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isSymbol } from "./symbol.ts"; + +Deno.test("isSymbol", async (t) => { + await testWithExamples(t, isSymbol, { validExamples: ["symbol"] }); +}); diff --git a/is/sync_function.ts b/is/sync_function.ts new file mode 100644 index 0000000..c6f7fa0 --- /dev/null +++ b/is/sync_function.ts @@ -0,0 +1,20 @@ +const objectToString = Object.prototype.toString; + +/** + * Return `true` if the type of `x` is `function` (non async function). + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = () => {}; + * if (is.SyncFunction(a)) { + * // a is narrowed to (...args: unknown[]) => unknown + * const _: ((...args: unknown[]) => unknown) = a; + * } + * ``` + */ +export function isSyncFunction( + x: unknown, +): x is (...args: unknown[]) => unknown { + return objectToString.call(x) === "[object Function]"; +} diff --git a/is/sync_function_test.ts b/is/sync_function_test.ts new file mode 100644 index 0000000..b8c933e --- /dev/null +++ b/is/sync_function_test.ts @@ -0,0 +1,8 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isSyncFunction } from "./sync_function.ts"; + +Deno.test("isSyncFunction", async (t) => { + await testWithExamples(t, isSyncFunction, { + validExamples: ["syncFunction"], + }); +}); diff --git a/is/tuple_of.ts b/is/tuple_of.ts new file mode 100644 index 0000000..9c86adc --- /dev/null +++ b/is/tuple_of.ts @@ -0,0 +1,103 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate, PredicateType } from "../type.ts"; +import { isArray } from "./array.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `TupleOf` or `TupleOf`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.TupleOf([is.Number, is.String, is.Boolean]); + * const a: unknown = [0, "a", true]; + * if (isMyType(a)) { + * // a is narrowed to [number, string, boolean] + * const _: [number, string, boolean] = a; + * } + * ``` + * + * With `predElse`: + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.TupleOf( + * [is.Number, is.String, is.Boolean], + * is.ArrayOf(is.Number), + * ); + * const a: unknown = [0, "a", true, 0, 1, 2]; + * if (isMyType(a)) { + * // a is narrowed to [number, string, boolean, ...number[]] + * const _: [number, string, boolean, ...number[]] = a; + * } + * ``` + * + * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array + * used as `predTup`. If a type error occurs, try adding `as const` as follows: + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const predTup = [is.Number, is.String, is.Boolean] as const; + * const isMyType = is.TupleOf(predTup); + * const a: unknown = [0, "a", true]; + * if (isMyType(a)) { + * // a is narrowed to [number, string, boolean] + * const _: [number, string, boolean] = a; + * } + * ``` + */ +export function isTupleOf< + T extends readonly [Predicate, ...Predicate[]], +>( + predTup: T, +): Predicate>; + +export function isTupleOf< + T extends readonly [Predicate, ...Predicate[]], + E extends Predicate, +>( + predTup: T, + predElse: E, +): Predicate<[...TupleOf, ...PredicateType]>; + +export function isTupleOf< + T extends readonly [Predicate, ...Predicate[]], + E extends Predicate, +>( + predTup: T, + predElse?: E, +): Predicate | [...TupleOf, ...PredicateType]> { + if (!predElse) { + return rewriteName( + (x: unknown): x is TupleOf => { + if (!isArray(x) || x.length !== predTup.length) { + return false; + } + return predTup.every((pred, i) => pred(x[i])); + }, + "isTupleOf", + predTup, + ); + } else { + return rewriteName( + (x: unknown): x is [...TupleOf, ...PredicateType] => { + if (!isArray(x) || x.length < predTup.length) { + return false; + } + const head = x.slice(0, predTup.length); + const tail = x.slice(predTup.length); + return predTup.every((pred, i) => pred(head[i])) && predElse(tail); + }, + "isTupleOf", + predTup, + predElse, + ); + } +} + +type TupleOf = { + -readonly [P in keyof T]: T[P] extends Predicate ? U : never; +}; diff --git a/is/tuple_of_test.ts b/is/tuple_of_test.ts new file mode 100644 index 0000000..8aabb02 --- /dev/null +++ b/is/tuple_of_test.ts @@ -0,0 +1,103 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isTupleOf } from "./tuple_of.ts"; + +Deno.test("isTupleOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot( + t, + isTupleOf([is.Number, is.String, is.Boolean]).name, + ); + await assertSnapshot( + t, + isTupleOf([(_x): _x is string => false]).name, + ); + // Nested + await assertSnapshot( + t, + isTupleOf([isTupleOf([isTupleOf([is.Number, is.String, is.Boolean])])]) + .name, + ); + }); + await t.step("returns proper type predicate", () => { + const predTup = [is.Number, is.String, is.Boolean] as const; + const a: unknown = [0, "a", true]; + if (isTupleOf(predTup)(a)) { + assertType>(true); + } + }); + await t.step("returns true on T tuple", () => { + const predTup = [is.Number, is.String, is.Boolean] as const; + assertEquals(isTupleOf(predTup)([0, "a", true]), true); + }); + await t.step("returns false on non T tuple", () => { + const predTup = [is.Number, is.String, is.Boolean] as const; + assertEquals(isTupleOf(predTup)([0, 1, 2]), false); + assertEquals(isTupleOf(predTup)([0, "a"]), false); + assertEquals(isTupleOf(predTup)([0, "a", true, 0]), false); + }); + await testWithExamples(t, isTupleOf([(_: unknown): _ is unknown => true]), { + excludeExamples: ["array"], + }); +}); + +Deno.test("isTupleOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot( + t, + isTupleOf([is.Number, is.String, is.Boolean], is.Array).name, + ); + await assertSnapshot( + t, + isTupleOf([(_x): _x is string => false], is.ArrayOf(is.String)) + .name, + ); + // Nested + await assertSnapshot( + t, + isTupleOf([ + isTupleOf( + [isTupleOf([is.Number, is.String, is.Boolean], is.Array)], + is.Array, + ), + ]).name, + ); + }); + await t.step("returns proper type predicate", () => { + const predTup = [is.Number, is.String, is.Boolean] as const; + const predElse = is.ArrayOf(is.Number); + const a: unknown = [0, "a", true, 0, 1, 2]; + if (isTupleOf(predTup, predElse)(a)) { + assertType>( + true, + ); + } + }); + await t.step("returns true on T tuple", () => { + const predTup = [is.Number, is.String, is.Boolean] as const; + const predElse = is.ArrayOf(is.Number); + assertEquals(isTupleOf(predTup, predElse)([0, "a", true, 0, 1, 2]), true); + }); + await t.step("returns false on non T tuple", () => { + const predTup = [is.Number, is.String, is.Boolean] as const; + const predElse = is.ArrayOf(is.String); + assertEquals(isTupleOf(predTup, predElse)([0, 1, 2, 0, 1, 2]), false); + assertEquals(isTupleOf(predTup, predElse)([0, "a", 0, 1, 2]), false); + assertEquals( + isTupleOf(predTup, predElse)([0, "a", true, 0, 0, 1, 2]), + false, + ); + assertEquals(isTupleOf(predTup, predElse)([0, "a", true, 0, 1, 2]), false); + }); + const predElse = is.Array; + await testWithExamples( + t, + isTupleOf([(_: unknown): _ is unknown => true], predElse), + { + excludeExamples: ["array"], + }, + ); +}); diff --git a/is/undefined.ts b/is/undefined.ts new file mode 100644 index 0000000..4a8ecd9 --- /dev/null +++ b/is/undefined.ts @@ -0,0 +1,16 @@ +/** + * Return `true` if the type of `x` is `undefined`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a: unknown = undefined; + * if (is.Undefined(a)) { + * // a is narrowed to undefined + * const _: undefined = a; + * } + * ``` + */ +export function isUndefined(x: unknown): x is undefined { + return typeof x === "undefined"; +} diff --git a/is/undefined_test.ts b/is/undefined_test.ts new file mode 100644 index 0000000..3b6ec26 --- /dev/null +++ b/is/undefined_test.ts @@ -0,0 +1,6 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isUndefined } from "./undefined.ts"; + +Deno.test("isUndefined", async (t) => { + await testWithExamples(t, isUndefined, { validExamples: ["undefined"] }); +}); diff --git a/is/uniform_tuple_of.ts b/is/uniform_tuple_of.ts new file mode 100644 index 0000000..fd3eca6 --- /dev/null +++ b/is/uniform_tuple_of.ts @@ -0,0 +1,56 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate } from "../type.ts"; +import { isArray } from "./array.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `UniformTupleOf`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.UniformTupleOf(5); + * const a: unknown = [0, 1, 2, 3, 4]; + * if (isMyType(a)) { + * // a is narrowed to [unknown, unknown, unknown, unknown, unknown] + * const _: [unknown, unknown, unknown, unknown, unknown] = a; + * } + * ``` + * + * With predicate function: + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.UniformTupleOf(5, is.Number); + * const a: unknown = [0, 1, 2, 3, 4]; + * if (isMyType(a)) { + * // a is narrowed to [number, number, number, number, number] + * const _: [number, number, number, number, number] = a; + * } + * ``` + */ +export function isUniformTupleOf( + n: N, + pred?: Predicate, +): Predicate> { + return rewriteName( + (x: unknown): x is UniformTupleOf => { + if (!isArray(x) || x.length !== n) { + return false; + } + return !pred || x.every((v) => pred(v)); + }, + "isUniformTupleOf", + n, + pred, + ); +} + +// https://stackoverflow.com/a/71700658/1273406 +type UniformTupleOf< + T, + N extends number, + R extends readonly T[] = [], +> = R["length"] extends N ? R : UniformTupleOf; diff --git a/is/uniform_tuple_of_test.ts b/is/uniform_tuple_of_test.ts new file mode 100644 index 0000000..78ebbb3 --- /dev/null +++ b/is/uniform_tuple_of_test.ts @@ -0,0 +1,43 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import { is } from "./mod.ts"; +import { isUniformTupleOf } from "./uniform_tuple_of.ts"; + +Deno.test("isUniformTupleOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isUniformTupleOf(3).name); + await assertSnapshot(t, isUniformTupleOf(3, is.Number).name); + await assertSnapshot( + t, + isUniformTupleOf(3, (_x): _x is string => false).name, + ); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = [0, 1, 2, 3, 4]; + if (isUniformTupleOf(5)(a)) { + assertType< + Equal + >(true); + } + + if (isUniformTupleOf(5, is.Number)(a)) { + assertType>( + true, + ); + } + }); + await t.step("returns true on mono-typed T tuple", () => { + assertEquals(isUniformTupleOf(3)([0, 1, 2]), true); + assertEquals(isUniformTupleOf(3, is.Number)([0, 1, 2]), true); + }); + await t.step("returns false on non mono-typed T tuple", () => { + assertEquals(isUniformTupleOf(4)([0, 1, 2]), false); + assertEquals(isUniformTupleOf(4)([0, 1, 2, 3, 4]), false); + assertEquals(isUniformTupleOf(3, is.Number)(["a", "b", "c"]), false); + }); + await testWithExamples(t, isUniformTupleOf(4), { + excludeExamples: ["array"], + }); +}); diff --git a/is/union_of.ts b/is/union_of.ts new file mode 100644 index 0000000..787e781 --- /dev/null +++ b/is/union_of.ts @@ -0,0 +1,54 @@ +import { rewriteName } from "../_funcutil.ts"; +import { annotate } from "../_annotation.ts"; +import type { Predicate } from "../type.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `UnionOf`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const isMyType = is.UnionOf([is.Number, is.String, is.Boolean]); + * const a: unknown = 0; + * if (isMyType(a)) { + * // a is narrowed to number | string | boolean + * const _: number | string | boolean = a; + * } + * ``` + * + * Depending on the version of TypeScript and how values are provided, it may be necessary to add `as const` to the array + * used as `preds`. If a type error occurs, try adding `as const` as follows: + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const preds = [is.Number, is.String, is.Boolean] as const; + * const isMyType = is.UnionOf(preds); + * const a: unknown = 0; + * if (isMyType(a)) { + * // a is narrowed to number | string | boolean + * const _: number | string | boolean = a; + * } + * ``` + */ +export function isUnionOf< + T extends readonly [Predicate, ...Predicate[]], +>( + preds: T, +): Predicate> { + return annotate( + rewriteName( + (x: unknown): x is UnionOf => preds.some((pred) => pred(x)), + "isUnionOf", + preds, + ), + "union", + preds, + ); +} + +type UnionOf = T extends readonly [Predicate, ...infer R] + ? U | UnionOf + : never; diff --git a/is/union_of_test.ts b/is/union_of_test.ts new file mode 100644 index 0000000..e159b78 --- /dev/null +++ b/is/union_of_test.ts @@ -0,0 +1,43 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { PredicateType } from "../type.ts"; +import { is } from "./mod.ts"; +import { isUnionOf } from "./union_of.ts"; + +Deno.test("isUnionOf", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isUnionOf([is.Number, is.String, is.Boolean]).name); + }); + await t.step("returns proper type predicate", () => { + const preds = [is.Number, is.String, is.Boolean] as const; + const a: unknown = [0, "a", true]; + if (isUnionOf(preds)(a)) { + assertType>(true); + } + }); + await t.step("returns proper type predicate (#49)", () => { + const isFoo = is.ObjectOf({ foo: is.String }); + const isBar = is.ObjectOf({ foo: is.String, bar: is.Number }); + type Foo = PredicateType; + type Bar = PredicateType; + const preds = [isFoo, isBar] as const; + const a: unknown = [0, "a", true]; + if (isUnionOf(preds)(a)) { + assertType>(true); + } + }); + await t.step("returns true on one of T", () => { + const preds = [is.Number, is.String, is.Boolean] as const; + assertEquals(isUnionOf(preds)(0), true); + assertEquals(isUnionOf(preds)("a"), true); + assertEquals(isUnionOf(preds)(true), true); + }); + await t.step("returns false on non of T", async (t) => { + const preds = [is.Number, is.String, is.Boolean] as const; + await testWithExamples(t, isUnionOf(preds), { + excludeExamples: ["number", "string", "boolean"], + }); + }); +}); diff --git a/is/unknown.ts b/is/unknown.ts new file mode 100644 index 0000000..b9e5a6d --- /dev/null +++ b/is/unknown.ts @@ -0,0 +1,16 @@ +/** + * Assume `x` is `unknown` and always return `true` regardless of the type of `x`. + * + * ```ts + * import { is } from "@core/unknownutil"; + * + * const a = "a"; + * if (is.Unknown(a)) { + * // a is narrowed to unknown + * const _: unknown = a; + * } + * ``` + */ +export function isUnknown(_x: unknown): _x is unknown { + return true; +} diff --git a/is/unknown_test.ts b/is/unknown_test.ts new file mode 100644 index 0000000..1f25945 --- /dev/null +++ b/is/unknown_test.ts @@ -0,0 +1,24 @@ +import { testWithExamples } from "../_testutil.ts"; +import { isUnknown } from "./unknown.ts"; + +Deno.test("isUnknown", async (t) => { + await testWithExamples(t, isUnknown, { + validExamples: [ + "string", + "number", + "bigint", + "boolean", + "array", + "set", + "record", + "map", + "syncFunction", + "asyncFunction", + "null", + "undefined", + "symbol", + "date", + "promise", + ], + }); +}); diff --git a/is_bench.ts b/is_bench.ts index 7b77e32..c38c5db 100644 --- a/is_bench.ts +++ b/is_bench.ts @@ -1,5 +1,5 @@ import { as } from "./as/mod.ts"; -import { is } from "./is.ts"; +import { is } from "./is/mod.ts"; const cs: unknown[] = [ "Hello world", @@ -61,10 +61,10 @@ Deno.bench({ }); Deno.bench({ - name: "is.BigInt", + name: "is.Bigint", fn: () => { for (const c of cs) { - is.BigInt(c); + is.Bigint(c); } }, }); diff --git a/is_test.ts b/is_test.ts deleted file mode 100644 index bc4cde0..0000000 --- a/is_test.ts +++ /dev/null @@ -1,1410 +0,0 @@ -import { assertEquals, assertStrictEquals } from "@std/assert"; -import { assertSnapshot } from "@std/testing/snapshot"; -import { assertType } from "@std/testing/types"; -import { type Equal, stringify } from "./_testutil.ts"; -import type { Predicate, PredicateType } from "./is.ts"; -import { as } from "./as/mod.ts"; -import { - is, - isAny, - isArray, - isArrayOf, - isAsyncFunction, - isBigInt, - isBoolean, - isFunction, - isInstanceOf, - isIntersectionOf, - isLiteralOf, - isLiteralOneOf, - isMap, - isMapOf, - isNull, - isNullish, - isNumber, - isObjectOf, - isOmitOf, - isParametersOf, - isPartialOf, - isPickOf, - isPrimitive, - isRecord, - isRecordObject, - isRecordObjectOf, - isRecordOf, - isRequiredOf, - isSet, - isSetOf, - isStrictOf, - isString, - isSymbol, - isSyncFunction, - isTupleOf, - isUndefined, - isUniformTupleOf, - isUnionOf, - isUnknown, -} from "./is.ts"; - -const examples = { - string: ["", "Hello world"], - number: [0, 1234567890], - bigint: [0n, 1234567890n], - boolean: [true, false], - array: [[], [0, 1, 2], ["a", "b", "c"], [0, "a", true]], - set: [new Set(), new Set([0, 1, 2]), new Set(["a", "b", "c"])], - record: [{}, { a: 0, b: 1, c: 2 }, { a: "a", b: "b", c: "c" }], - map: [ - new Map(), - new Map([["a", 0], ["b", 1], ["c", 2]]), - new Map([["a", "a"], ["b", "b"], ["c", "c"]]), - ], - syncFunction: [function a() {}, () => {}], - asyncFunction: [async function b() {}, async () => {}], - null: [null], - undefined: [undefined], - symbol: [Symbol("a"), Symbol("b"), Symbol("c")], - date: [new Date(1690248225000), new Date(0)], - promise: [new Promise(() => {})], -} as const; - -async function testWithExamples( - t: Deno.TestContext, - pred: Predicate, - opts?: { - validExamples?: (keyof typeof examples)[]; - excludeExamples?: (keyof typeof examples)[]; - }, -): Promise { - const { validExamples = [], excludeExamples = [] } = opts ?? {}; - const exampleEntries = (Object.entries(examples) as unknown as [ - name: keyof typeof examples, - example: unknown[], - ][]).filter(([k]) => !excludeExamples.includes(k)); - for (const [name, example] of exampleEntries) { - const expect = validExamples.includes(name); - for (const v of example) { - await t.step( - `returns ${expect} on ${stringify(v)}`, - () => { - assertEquals(pred(v), expect); - }, - ); - } - } -} - -Deno.test("isAny", async (t) => { - await testWithExamples(t, isAny, { - validExamples: [ - "string", - "number", - "bigint", - "boolean", - "array", - "set", - "record", - "map", - "syncFunction", - "asyncFunction", - "null", - "undefined", - "symbol", - "date", - "promise", - ], - }); -}); - -Deno.test("isUnknown", async (t) => { - await testWithExamples(t, isUnknown, { - validExamples: [ - "string", - "number", - "bigint", - "boolean", - "array", - "set", - "record", - "map", - "syncFunction", - "asyncFunction", - "null", - "undefined", - "symbol", - "date", - "promise", - ], - }); -}); - -Deno.test("isString", async (t) => { - await testWithExamples(t, isString, { validExamples: ["string"] }); -}); - -Deno.test("isNumber", async (t) => { - await testWithExamples(t, isNumber, { validExamples: ["number"] }); -}); - -Deno.test("isBigInt", async (t) => { - await testWithExamples(t, isBigInt, { validExamples: ["bigint"] }); -}); - -Deno.test("isBoolean", async (t) => { - await testWithExamples(t, isBoolean, { validExamples: ["boolean"] }); -}); - -Deno.test("isArray", async (t) => { - await testWithExamples(t, isArray, { validExamples: ["array"] }); -}); - -Deno.test("isSet", async (t) => { - await testWithExamples(t, isSet, { validExamples: ["set"] }); -}); - -Deno.test("isRecordObject", async (t) => { - await testWithExamples(t, isRecordObject, { - validExamples: ["record"], - }); -}); - -Deno.test("isRecord", async (t) => { - await testWithExamples(t, isRecord, { - validExamples: ["record", "date", "promise", "set", "map"], - }); -}); - -Deno.test("isMap", async (t) => { - await testWithExamples(t, isMap, { - validExamples: ["map"], - }); -}); - -Deno.test("isFunction", async (t) => { - await testWithExamples(t, isFunction, { - validExamples: ["syncFunction", "asyncFunction"], - }); -}); - -Deno.test("isSyncFunction", async (t) => { - await testWithExamples(t, isSyncFunction, { - validExamples: ["syncFunction"], - }); -}); - -Deno.test("isAsyncFunction", async (t) => { - await testWithExamples(t, isAsyncFunction, { - validExamples: ["asyncFunction"], - }); -}); - -Deno.test("isNull", async (t) => { - await testWithExamples(t, isNull, { validExamples: ["null"] }); -}); - -Deno.test("isUndefined", async (t) => { - await testWithExamples(t, isUndefined, { validExamples: ["undefined"] }); -}); - -Deno.test("isNullish", async (t) => { - await testWithExamples(t, isNullish, { - validExamples: ["null", "undefined"], - }); -}); - -Deno.test("isSymbol", async (t) => { - await testWithExamples(t, isSymbol, { validExamples: ["symbol"] }); -}); - -Deno.test("isPrimitive", async (t) => { - await testWithExamples(t, isPrimitive, { - validExamples: [ - "string", - "number", - "bigint", - "boolean", - "null", - "undefined", - "symbol", - ], - }); -}); - -Deno.test("isArrayOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isArrayOf(isNumber).name); - await assertSnapshot(t, isArrayOf((_x): _x is string => false).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = [0, 1, 2]; - if (isArrayOf(isNumber)(a)) { - assertType>(true); - } - }); - await t.step("returns true on T array", () => { - assertEquals(isArrayOf(isNumber)([0, 1, 2]), true); - assertEquals(isArrayOf(isString)(["a", "b", "c"]), true); - assertEquals(isArrayOf(isBoolean)([true, false, true]), true); - }); - await t.step("returns false on non T array", () => { - assertEquals(isArrayOf(isString)([0, 1, 2]), false); - assertEquals(isArrayOf(isNumber)(["a", "b", "c"]), false); - assertEquals(isArrayOf(isString)([true, false, true]), false); - }); - await testWithExamples(t, isArrayOf((_: unknown): _ is unknown => true), { - excludeExamples: ["array"], - }); -}); - -Deno.test("isSetOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isSetOf(isNumber).name); - await assertSnapshot(t, isSetOf((_x): _x is string => false).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = new Set([0, 1, 2]); - if (isSetOf(isNumber)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T set", () => { - assertEquals(isSetOf(isNumber)(new Set([0, 1, 2])), true); - assertEquals(isSetOf(isString)(new Set(["a", "b", "c"])), true); - assertEquals(isSetOf(isBoolean)(new Set([true, false, true])), true); - }); - await t.step("returns false on non T set", () => { - assertEquals(isSetOf(isString)(new Set([0, 1, 2])), false); - assertEquals(isSetOf(isNumber)(new Set(["a", "b", "c"])), false); - assertEquals(isSetOf(isString)(new Set([true, false, true])), false); - }); - await testWithExamples(t, isSetOf((_: unknown): _ is unknown => true), { - excludeExamples: ["set"], - }); -}); - -Deno.test("isTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isTupleOf([isNumber, isString, isBoolean]).name, - ); - await assertSnapshot( - t, - isTupleOf([(_x): _x is string => false]).name, - ); - // Nested - await assertSnapshot( - t, - isTupleOf([isTupleOf([isTupleOf([isNumber, isString, isBoolean])])]).name, - ); - }); - await t.step("returns proper type predicate", () => { - const predTup = [isNumber, isString, isBoolean] as const; - const a: unknown = [0, "a", true]; - if (isTupleOf(predTup)(a)) { - assertType>(true); - } - }); - await t.step("returns true on T tuple", () => { - const predTup = [isNumber, isString, isBoolean] as const; - assertEquals(isTupleOf(predTup)([0, "a", true]), true); - }); - await t.step("returns false on non T tuple", () => { - const predTup = [isNumber, isString, isBoolean] as const; - assertEquals(isTupleOf(predTup)([0, 1, 2]), false); - assertEquals(isTupleOf(predTup)([0, "a"]), false); - assertEquals(isTupleOf(predTup)([0, "a", true, 0]), false); - }); - await testWithExamples(t, isTupleOf([(_: unknown): _ is unknown => true]), { - excludeExamples: ["array"], - }); -}); - -Deno.test("isTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isTupleOf([isNumber, isString, isBoolean], isArray).name, - ); - await assertSnapshot( - t, - isTupleOf([(_x): _x is string => false], isArrayOf(isString)) - .name, - ); - // Nested - await assertSnapshot( - t, - isTupleOf([ - isTupleOf( - [isTupleOf([isNumber, isString, isBoolean], isArray)], - isArray, - ), - ]).name, - ); - }); - await t.step("returns proper type predicate", () => { - const predTup = [isNumber, isString, isBoolean] as const; - const predElse = isArrayOf(isNumber); - const a: unknown = [0, "a", true, 0, 1, 2]; - if (isTupleOf(predTup, predElse)(a)) { - assertType>( - true, - ); - } - }); - await t.step("returns true on T tuple", () => { - const predTup = [isNumber, isString, isBoolean] as const; - const predElse = isArrayOf(isNumber); - assertEquals(isTupleOf(predTup, predElse)([0, "a", true, 0, 1, 2]), true); - }); - await t.step("returns false on non T tuple", () => { - const predTup = [isNumber, isString, isBoolean] as const; - const predElse = isArrayOf(isString); - assertEquals(isTupleOf(predTup, predElse)([0, 1, 2, 0, 1, 2]), false); - assertEquals(isTupleOf(predTup, predElse)([0, "a", 0, 1, 2]), false); - assertEquals( - isTupleOf(predTup, predElse)([0, "a", true, 0, 0, 1, 2]), - false, - ); - assertEquals(isTupleOf(predTup, predElse)([0, "a", true, 0, 1, 2]), false); - }); - const predElse = isArray; - await testWithExamples( - t, - isTupleOf([(_: unknown): _ is unknown => true], predElse), - { - excludeExamples: ["array"], - }, - ); -}); - -Deno.test("isParametersOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isParametersOf([isNumber, isString, as.Optional(isBoolean)]).name, - ); - await assertSnapshot( - t, - isParametersOf([(_x): _x is string => false]).name, - ); - await assertSnapshot( - t, - isParametersOf([]).name, - ); - // Nested - await assertSnapshot( - t, - isParametersOf([ - isParametersOf([ - isParametersOf([isNumber, isString, as.Optional(isBoolean)]), - ]), - ]).name, - ); - }); - await t.step("returns proper type predicate", () => { - const predTup = [ - as.Optional(isNumber), - isString, - as.Optional(isString), - as.Optional(isBoolean), - ] as const; - const a: unknown = [0, "a"]; - if (isParametersOf(predTup)(a)) { - assertType< - Equal - >(true); - } - }); - await t.step("returns true on T tuple", () => { - const predTup = [isNumber, isString, as.Optional(isBoolean)] as const; - assertEquals(isParametersOf(predTup)([0, "a", true]), true); - assertEquals(isParametersOf(predTup)([0, "a"]), true); - }); - await t.step("returns false on non T tuple", () => { - const predTup = [isNumber, isString, as.Optional(isBoolean)] as const; - assertEquals(isParametersOf(predTup)([0, 1, 2]), false); - assertEquals(isParametersOf(predTup)([0, "a", true, 0]), false); - }); - await testWithExamples( - t, - isParametersOf([(_: unknown): _ is unknown => true]), - { - excludeExamples: ["array"], - }, - ); -}); - -Deno.test("isParametersOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isParametersOf([isNumber, isString, as.Optional(isBoolean)], isArray) - .name, - ); - await assertSnapshot( - t, - isParametersOf([(_x): _x is string => false], isArrayOf(isString)) - .name, - ); - // Empty - await assertSnapshot( - t, - isParametersOf([], isArrayOf(isString)).name, - ); - // Nested - await assertSnapshot( - t, - isParametersOf([ - isParametersOf( - [isParametersOf( - [isNumber, isString, as.Optional(isBoolean)], - isArray, - )], - isArray, - ), - ]).name, - ); - }); - await t.step("returns proper type predicate", () => { - const predTup = [ - as.Optional(isNumber), - isString, - as.Optional(isString), - as.Optional(isBoolean), - ] as const; - const predElse = isArrayOf(isNumber); - const a: unknown = [0, "a"]; - if (isParametersOf(predTup, predElse)(a)) { - assertType< - Equal< - typeof a, - [number | undefined, string, string?, boolean?, ...number[]] - > - >( - true, - ); - } - }); - await t.step("returns true on T tuple", () => { - const predTup = [isNumber, isString, as.Optional(isBoolean)] as const; - const predElse = isArrayOf(isNumber); - assertEquals( - isParametersOf(predTup, predElse)([0, "a", true, 0, 1, 2]), - true, - ); - assertEquals( - isParametersOf(predTup, predElse)([0, "a", undefined, 0, 1, 2]), - true, - ); - assertEquals(isParametersOf(predTup, predElse)([0, "a"]), true); - }); - await t.step("returns false on non T tuple", () => { - const predTup = [isNumber, isString, as.Optional(isBoolean)] as const; - const predElse = isArrayOf(isString); - assertEquals(isParametersOf(predTup, predElse)([0, 1, 2, 0, 1, 2]), false); - assertEquals(isParametersOf(predTup, predElse)([0, "a", 0, 1, 2]), false); - assertEquals( - isParametersOf(predTup, predElse)([0, "a", true, 0, 1, 2]), - false, - ); - assertEquals( - isParametersOf(predTup, predElse)([0, "a", undefined, 0, 1, 2]), - false, - ); - assertEquals(isParametersOf(predTup, predElse)([0, "a", "b"]), false); - }); - const predElse = isArray; - await testWithExamples( - t, - isParametersOf([(_: unknown): _ is unknown => true], predElse), - { - excludeExamples: ["array"], - }, - ); -}); - -Deno.test("isUniformTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isUniformTupleOf(3).name); - await assertSnapshot(t, isUniformTupleOf(3, isNumber).name); - await assertSnapshot( - t, - isUniformTupleOf(3, (_x): _x is string => false).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = [0, 1, 2, 3, 4]; - if (isUniformTupleOf(5)(a)) { - assertType< - Equal - >(true); - } - - if (isUniformTupleOf(5, isNumber)(a)) { - assertType>( - true, - ); - } - }); - await t.step("returns true on mono-typed T tuple", () => { - assertEquals(isUniformTupleOf(3)([0, 1, 2]), true); - assertEquals(isUniformTupleOf(3, isNumber)([0, 1, 2]), true); - }); - await t.step("returns false on non mono-typed T tuple", () => { - assertEquals(isUniformTupleOf(4)([0, 1, 2]), false); - assertEquals(isUniformTupleOf(4)([0, 1, 2, 3, 4]), false); - assertEquals(isUniformTupleOf(3, isNumber)(["a", "b", "c"]), false); - }); - await testWithExamples(t, isUniformTupleOf(4), { - excludeExamples: ["array"], - }); -}); - -Deno.test("isRecordObjectOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isRecordObjectOf(isNumber).name); - await assertSnapshot(t, isRecordObjectOf((_x): _x is string => false).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordObjectOf(isNumber)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T record", () => { - assertEquals(isRecordObjectOf(isNumber)({ a: 0 }), true); - assertEquals(isRecordObjectOf(isString)({ a: "a" }), true); - assertEquals(isRecordObjectOf(isBoolean)({ a: true }), true); - }); - await t.step("returns false on non T record", () => { - assertEquals(isRecordObjectOf(isString)({ a: 0 }), false); - assertEquals(isRecordObjectOf(isNumber)({ a: "a" }), false); - assertEquals(isRecordObjectOf(isString)({ a: true }), false); - }); - await testWithExamples( - t, - isRecordObjectOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record"], - }, - ); -}); - -Deno.test("isRecordObjectOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isRecordObjectOf(isNumber, isString).name); - await assertSnapshot( - t, - isRecordObjectOf((_x): _x is string => false, isString).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordObjectOf(isNumber, isString)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T record", () => { - assertEquals(isRecordObjectOf(isNumber, isString)({ a: 0 }), true); - assertEquals(isRecordObjectOf(isString, isString)({ a: "a" }), true); - assertEquals(isRecordObjectOf(isBoolean, isString)({ a: true }), true); - }); - await t.step("returns false on non T record", () => { - assertEquals(isRecordObjectOf(isString, isString)({ a: 0 }), false); - assertEquals(isRecordObjectOf(isNumber, isString)({ a: "a" }), false); - assertEquals(isRecordObjectOf(isString, isString)({ a: true }), false); - }); - await t.step("returns false on non K record", () => { - assertEquals(isRecordObjectOf(isNumber, isNumber)({ a: 0 }), false); - assertEquals(isRecordObjectOf(isString, isNumber)({ a: "a" }), false); - assertEquals(isRecordObjectOf(isBoolean, isNumber)({ a: true }), false); - }); - await testWithExamples( - t, - isRecordObjectOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record"], - }, - ); -}); - -Deno.test("isRecordOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isRecordOf(isNumber).name); - await assertSnapshot(t, isRecordOf((_x): _x is string => false).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordOf(isNumber)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T record", () => { - assertEquals(isRecordOf(isNumber)({ a: 0 }), true); - assertEquals(isRecordOf(isString)({ a: "a" }), true); - assertEquals(isRecordOf(isBoolean)({ a: true }), true); - }); - await t.step("returns false on non T record", () => { - assertEquals(isRecordOf(isString)({ a: 0 }), false); - assertEquals(isRecordOf(isNumber)({ a: "a" }), false); - assertEquals(isRecordOf(isString)({ a: true }), false); - }); - await testWithExamples( - t, - isRecordOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record", "date", "promise", "set", "map"], - }, - ); -}); - -Deno.test("isRecordOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isRecordOf(isNumber, isString).name); - await assertSnapshot( - t, - isRecordOf((_x): _x is string => false, isString).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordOf(isNumber, isString)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T record", () => { - assertEquals(isRecordOf(isNumber, isString)({ a: 0 }), true); - assertEquals(isRecordOf(isString, isString)({ a: "a" }), true); - assertEquals(isRecordOf(isBoolean, isString)({ a: true }), true); - }); - await t.step("returns false on non T record", () => { - assertEquals(isRecordOf(isString, isString)({ a: 0 }), false); - assertEquals(isRecordOf(isNumber, isString)({ a: "a" }), false); - assertEquals(isRecordOf(isString, isString)({ a: true }), false); - }); - await t.step("returns false on non K record", () => { - assertEquals(isRecordOf(isNumber, isNumber)({ a: 0 }), false); - assertEquals(isRecordOf(isString, isNumber)({ a: "a" }), false); - assertEquals(isRecordOf(isBoolean, isNumber)({ a: true }), false); - }); - await testWithExamples( - t, - isRecordOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record", "date", "promise", "set", "map"], - }, - ); -}); - -Deno.test("isMapOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isMapOf(isNumber).name); - await assertSnapshot(t, isMapOf((_x): _x is string => false).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = new Map([["a", 0]]); - if (isMapOf(isNumber)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T map", () => { - assertEquals(isMapOf(isNumber)(new Map([["a", 0]])), true); - assertEquals(isMapOf(isString)(new Map([["a", "a"]])), true); - assertEquals(isMapOf(isBoolean)(new Map([["a", true]])), true); - }); - await t.step("returns false on non T map", () => { - assertEquals(isMapOf(isString)(new Map([["a", 0]])), false); - assertEquals(isMapOf(isNumber)(new Map([["a", "a"]])), false); - assertEquals(isMapOf(isString)(new Map([["a", true]])), false); - }); - await testWithExamples(t, isMapOf((_: unknown): _ is unknown => true), { - excludeExamples: ["map"], - }); -}); - -Deno.test("isMapOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isMapOf(isNumber, isString).name); - await assertSnapshot( - t, - isMapOf((_x): _x is string => false, isString).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = new Map([["a", 0]]); - if (isMapOf(isNumber, isString)(a)) { - assertType>>(true); - } - }); - await t.step("returns true on T map", () => { - assertEquals(isMapOf(isNumber, isString)(new Map([["a", 0]])), true); - assertEquals(isMapOf(isString, isString)(new Map([["a", "a"]])), true); - assertEquals(isMapOf(isBoolean, isString)(new Map([["a", true]])), true); - }); - await t.step("returns false on non T map", () => { - assertEquals(isMapOf(isString, isString)(new Map([["a", 0]])), false); - assertEquals(isMapOf(isNumber, isString)(new Map([["a", "a"]])), false); - assertEquals(isMapOf(isString, isString)(new Map([["a", true]])), false); - }); - await t.step("returns false on non K map", () => { - assertEquals(isMapOf(isNumber, isNumber)(new Map([["a", 0]])), false); - assertEquals(isMapOf(isString, isNumber)(new Map([["a", "a"]])), false); - assertEquals(isMapOf(isBoolean, isNumber)(new Map([["a", true]])), false); - }); - await testWithExamples(t, isMapOf((_: unknown): _ is unknown => true), { - excludeExamples: ["map"], - }); -}); - -Deno.test("isObjectOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isObjectOf({ a: isNumber, b: isString, c: isBoolean }).name, - ); - await assertSnapshot( - t, - isObjectOf({ a: (_x): _x is string => false }).name, - ); - // Nested - await assertSnapshot( - t, - isObjectOf({ a: isObjectOf({ b: isObjectOf({ c: isBoolean }) }) }).name, - ); - }); - await t.step("returns proper type predicate", () => { - const predObj = { - a: isNumber, - b: isString, - c: isBoolean, - }; - const a: unknown = { a: 0, b: "a", c: true }; - if (isObjectOf(predObj)(a)) { - assertType>(true); - } - }); - await t.step("returns true on T object", () => { - const predObj = { - a: isNumber, - b: isString, - c: isBoolean, - }; - assertEquals(isObjectOf(predObj)({ a: 0, b: "a", c: true }), true); - assertEquals( - isObjectOf(predObj)({ a: 0, b: "a", c: true, d: "ignored" }), - true, - "Object have an unknown property", - ); - assertEquals( - isObjectOf(predObj)( - Object.assign(() => void 0, { a: 0, b: "a", c: true }), - ), - true, - "Function object", - ); - }); - await t.step("returns false on non T object", () => { - const predObj = { - a: isNumber, - b: isString, - c: isBoolean, - }; - assertEquals(isObjectOf(predObj)("a"), false, "Value is not an object"); - assertEquals( - isObjectOf(predObj)({ a: 0, b: "a", c: "" }), - false, - "Object have a different type property", - ); - assertEquals( - isObjectOf(predObj)({ a: 0, b: "a" }), - false, - "Object does not have one property", - ); - assertEquals( - isObjectOf({ 0: isString })(["a"]), - false, - "Value is not an object", - ); - }); - await t.step("returns true on T instance", () => { - const date = new Date(); - const predObj = { - getFullYear: isFunction, - }; - assertEquals(isObjectOf(predObj)(date), true, "Value is not an object"); - }); - await testWithExamples( - t, - isObjectOf({ a: (_: unknown): _ is unknown => false }), - { excludeExamples: ["record"] }, - ); -}); - -Deno.test("isStrictOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isStrictOf(isObjectOf({ a: isNumber, b: isString, c: isBoolean })).name, - ); - await assertSnapshot( - t, - isStrictOf(isObjectOf({ a: (_x): _x is string => false })).name, - ); - // Nested - await assertSnapshot( - t, - isStrictOf( - isObjectOf({ - a: isStrictOf( - isObjectOf({ b: isStrictOf(isObjectOf({ c: isBoolean })) }), - ), - }), - ).name, - ); - }); - await t.step("returns proper type predicate", () => { - const predObj = { - a: isNumber, - b: isString, - c: isBoolean, - }; - const a: unknown = { a: 0, b: "a", c: true }; - if (isStrictOf(isObjectOf(predObj))(a)) { - assertType>(true); - } - }); - await t.step("returns true on T object", () => { - const predObj = { - a: isNumber, - b: isString, - c: isBoolean, - }; - assertEquals( - isStrictOf(isObjectOf(predObj))({ a: 0, b: "a", c: true }), - true, - ); - }); - await t.step("returns false on non T object", () => { - const predObj = { - a: isNumber, - b: isString, - c: isBoolean, - }; - assertEquals( - isStrictOf(isObjectOf(predObj))({ a: 0, b: "a", c: "" }), - false, - "Object have a different type property", - ); - assertEquals( - isStrictOf(isObjectOf(predObj))({ a: 0, b: "a" }), - false, - "Object does not have one property", - ); - assertEquals( - isStrictOf(isObjectOf(predObj))({ - a: 0, - b: "a", - c: true, - d: "invalid", - }), - false, - "Object have an unknown property", - ); - }); - await testWithExamples( - t, - isStrictOf(isObjectOf({ a: (_: unknown): _ is unknown => false })), - { excludeExamples: ["record"] }, - ); - await t.step("with optional properties", async (t) => { - await t.step("returns proper type predicate", () => { - const predObj = { - a: isNumber, - b: isUnionOf([isString, isUndefined]), - c: as.Optional(isBoolean), - }; - const a: unknown = { a: 0, b: "a" }; - if (isStrictOf(isObjectOf(predObj))(a)) { - assertType< - Equal - >(true); - } - }); - await t.step("returns true on T object", () => { - const predObj = { - a: isNumber, - b: isUnionOf([isString, isUndefined]), - c: as.Optional(isBoolean), - }; - assertEquals( - isStrictOf(isObjectOf(predObj))({ a: 0, b: "a", c: true }), - true, - ); - assertEquals( - isStrictOf(isObjectOf(predObj))({ a: 0, b: "a" }), - true, - "Object does not have an optional property", - ); - assertEquals( - isStrictOf(isObjectOf(predObj))({ a: 0, b: "a", c: undefined }), - true, - "Object has `undefined` as value of optional property", - ); - }); - await t.step("returns false on non T object", () => { - const predObj = { - a: isNumber, - b: isUnionOf([isString, isUndefined]), - c: as.Optional(isBoolean), - }; - assertEquals( - isStrictOf(isObjectOf(predObj))({ a: 0, b: "a", c: "" }), - false, - "Object have a different type property", - ); - assertEquals( - isStrictOf(isObjectOf(predObj))({ a: 0, b: "a", c: null }), - false, - "Object has `null` as value of optional property", - ); - assertEquals( - isStrictOf(isObjectOf(predObj))({ - a: 0, - b: "a", - c: true, - d: "invalid", - }), - false, - "Object have an unknown property", - ); - assertEquals( - isStrictOf(isObjectOf(predObj))({ - a: 0, - b: "a", - d: "invalid", - }), - false, - "Object have the same number of properties but an unknown property exists", - ); - }); - }); -}); - -Deno.test("isInstanceOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isInstanceOf(Date).name); - await assertSnapshot(t, isInstanceOf(class {}).name); - }); - await t.step("returns true on T instance", () => { - class Cls {} - assertEquals(isInstanceOf(Cls)(new Cls()), true); - assertEquals(isInstanceOf(Date)(new Date()), true); - assertEquals(isInstanceOf(Promise)(new Promise(() => {})), true); - }); - await t.step("with user-defined class", async (t) => { - class Cls {} - await testWithExamples(t, isInstanceOf(Cls)); - }); - await t.step("with Date", async (t) => { - await testWithExamples(t, isInstanceOf(Date), { validExamples: ["date"] }); - }); - await t.step("with Promise", async (t) => { - await testWithExamples(t, isInstanceOf(Promise), { - validExamples: ["promise"], - }); - }); - await t.step("returns proper type predicate", () => { - class Cls {} - const a: unknown = new Cls(); - if (isInstanceOf(Cls)(a)) { - assertType>(true); - } - - const b: unknown = new Date(); - if (isInstanceOf(Date)(b)) { - assertType>(true); - } - - const c: unknown = new Promise(() => {}); - if (isInstanceOf(Promise)(c)) { - assertType>>(true); - } - }); -}); - -Deno.test("isLiteralOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isLiteralOf("hello").name); - await assertSnapshot(t, isLiteralOf(100).name); - await assertSnapshot(t, isLiteralOf(100n).name); - await assertSnapshot(t, isLiteralOf(true).name); - await assertSnapshot(t, isLiteralOf(null).name); - await assertSnapshot(t, isLiteralOf(undefined).name); - await assertSnapshot(t, isLiteralOf(Symbol("asdf")).name); - }); - await t.step("returns proper type predicate", () => { - const pred = "hello"; - const a: unknown = "hello"; - if (isLiteralOf(pred)(a)) { - assertType>(true); - } - }); - await t.step("returns true on literal T", () => { - const pred = "hello"; - assertEquals(isLiteralOf(pred)("hello"), true); - }); - await t.step("returns false on non literal T", async (t) => { - const pred = "hello"; - await testWithExamples(t, isLiteralOf(pred)); - }); -}); - -Deno.test("isLiteralOneOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isLiteralOneOf(["hello", "world"]).name); - }); - await t.step("returns proper type predicate", () => { - const preds = ["hello", "world"] as const; - const a: unknown = "hello"; - if (isLiteralOneOf(preds)(a)) { - assertType>(true); - } - }); - await t.step("returns true on literal T", () => { - const preds = ["hello", "world"] as const; - assertEquals(isLiteralOneOf(preds)("hello"), true); - assertEquals(isLiteralOneOf(preds)("world"), true); - }); - await t.step("returns false on non literal T", async (t) => { - const preds = ["hello", "world"] as const; - await testWithExamples(t, isLiteralOneOf(preds)); - }); -}); - -Deno.test("isUnionOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isUnionOf([isNumber, isString, isBoolean]).name); - }); - await t.step("returns proper type predicate", () => { - const preds = [isNumber, isString, isBoolean] as const; - const a: unknown = [0, "a", true]; - if (isUnionOf(preds)(a)) { - assertType>(true); - } - }); - await t.step("returns proper type predicate (#49)", () => { - const isFoo = isObjectOf({ foo: isString }); - const isBar = isObjectOf({ foo: isString, bar: isNumber }); - type Foo = PredicateType; - type Bar = PredicateType; - const preds = [isFoo, isBar] as const; - const a: unknown = [0, "a", true]; - if (isUnionOf(preds)(a)) { - assertType>(true); - } - }); - await t.step("returns true on one of T", () => { - const preds = [isNumber, isString, isBoolean] as const; - assertEquals(isUnionOf(preds)(0), true); - assertEquals(isUnionOf(preds)("a"), true); - assertEquals(isUnionOf(preds)(true), true); - }); - await t.step("returns false on non of T", async (t) => { - const preds = [isNumber, isString, isBoolean] as const; - await testWithExamples(t, isUnionOf(preds), { - excludeExamples: ["number", "string", "boolean"], - }); - }); -}); - -Deno.test("isIntersectionOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isIntersectionOf([ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ]).name, - "Should return `isObjectOf`, if all predicates that", - ); - await assertSnapshot( - t, - isIntersectionOf([ - isString, - ]).name, - "Should return as is, if there is only one predicate", - ); - await assertSnapshot( - t, - isIntersectionOf([ - isFunction, - isObjectOf({ b: isString }), - ]).name, - ); - }); - await t.step("returns proper type predicate", () => { - const objPreds = [ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ] as const; - const funcPreds = [ - isFunction, - isObjectOf({ b: isString }), - ] as const; - const a: unknown = { a: 0, b: "a" }; - if (isIntersectionOf(objPreds)(a)) { - assertType>(true); - } - if (isIntersectionOf([isString])(a)) { - assertType>(true); - } - if (isIntersectionOf(funcPreds)(a)) { - assertType< - Equal< - typeof a, - & ((...args: unknown[]) => unknown) - & { b: string } - > - >(true); - } - }); - await t.step("returns true on all of T", () => { - const objPreds = [ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ] as const; - const funcPreds = [ - isFunction, - isObjectOf({ b: isString }), - ] as const; - const f = Object.assign(() => void 0, { b: "a" }); - assertEquals(isIntersectionOf(objPreds)({ a: 0, b: "a" }), true); - assertEquals(isIntersectionOf([isString])("a"), true); - assertEquals(isIntersectionOf(funcPreds)(f), true); - }); - await t.step("returns false on non of T", async (t) => { - const preds = [ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ] as const; - assertEquals( - isIntersectionOf(preds)({ a: 0, b: 0 }), - false, - "Some properties has wrong type", - ); - assertEquals( - isIntersectionOf(preds)({ a: 0 }), - false, - "Some properties does not exists", - ); - await testWithExamples(t, isIntersectionOf(preds), { - excludeExamples: ["record"], - }); - }); - await t.step("returns false on non of T with any predicates", async (t) => { - const preds = [ - isFunction, - isObjectOf({ b: isString }), - ] as const; - assertEquals( - isIntersectionOf(preds)({ b: "a" }), - false, - "Not a function object", - ); - assertEquals( - isIntersectionOf(preds)(() => void 0), - false, - "Some properties does not exists in Function object", - ); - await testWithExamples(t, isIntersectionOf(preds), { - excludeExamples: ["record"], - }); - }); -}); - -Deno.test("isRequiredOf", async (t) => { - const pred = isObjectOf({ - a: isNumber, - b: isUnionOf([isString, isUndefined]), - c: as.Optional(isBoolean), - }); - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isRequiredOf(pred).name); - // Nestable (no effect) - await assertSnapshot(t, isRequiredOf(isRequiredOf(pred)).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isRequiredOf(pred)(a)) { - assertType< - Equal - >(true); - } - }); - await t.step("returns true on Required object", () => { - assertEquals( - isRequiredOf(pred)({ a: undefined, b: undefined, c: undefined }), - false, - "Object does not have required properties", - ); - assertEquals( - isRequiredOf(pred)({}), - false, - "Object does not have required properties", - ); - }); - await t.step("returns false on non Required object", () => { - assertEquals(isRequiredOf(pred)("a"), false, "Value is not an object"); - assertEquals( - isRequiredOf(pred)({ a: 0, b: "a", c: "" }), - false, - "Object have a different type property", - ); - }); -}); - -Deno.test("isPartialOf", async (t) => { - const pred = isObjectOf({ - a: isNumber, - b: isUnionOf([isString, isUndefined]), - c: as.Optional(isBoolean), - }); - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isPartialOf(pred).name); - // Nestable (no effect) - await assertSnapshot(t, isPartialOf(isPartialOf(pred)).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isPartialOf(pred)(a)) { - assertType< - Equal> - >(true); - } - }); - await t.step("returns true on Partial object", () => { - assertEquals( - isPartialOf(pred)({ a: undefined, b: undefined, c: undefined }), - true, - ); - assertEquals(isPartialOf(pred)({}), true); - }); - await t.step("returns false on non Partial object", () => { - assertEquals(isPartialOf(pred)("a"), false, "Value is not an object"); - assertEquals( - isPartialOf(pred)({ a: 0, b: "a", c: "" }), - false, - "Object have a different type property", - ); - }); -}); - -Deno.test("isPickOf", async (t) => { - const pred = isObjectOf({ - a: isNumber, - b: isString, - c: isBoolean, - }); - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isPickOf(pred, ["a", "c"]).name); - // Nestable - await assertSnapshot(t, isPickOf(isPickOf(pred, ["a", "c"]), ["a"]).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isPickOf(pred, ["a", "c"])(a)) { - assertType< - Equal - >(true); - } - if (isPickOf(isPickOf(pred, ["a", "c"]), ["a"])(a)) { - assertType< - Equal - >(true); - } - }); - await t.step("returns true on Pick object", () => { - assertEquals( - isPickOf(pred, ["a", "c"])({ a: 0, b: undefined, c: true }), - true, - ); - assertEquals(isPickOf(pred, ["a"])({ a: 0 }), true); - }); - await t.step("returns false on non Pick object", () => { - assertEquals( - isPickOf(pred, ["a", "c"])("a"), - false, - "Value is not an object", - ); - assertEquals( - isPickOf(pred, ["a", "c"])({ a: 0, b: "a", c: "" }), - false, - "Object have a different type property", - ); - }); -}); - -Deno.test("isOmitOf", async (t) => { - const pred = isObjectOf({ - a: isNumber, - b: isString, - c: isBoolean, - }); - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isOmitOf(pred, ["b"]).name); - // Nestable - await assertSnapshot(t, isOmitOf(isOmitOf(pred, ["b"]), ["c"]).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isOmitOf(pred, ["b"])(a)) { - assertType< - Equal - >(true); - } - if (isOmitOf(isOmitOf(pred, ["b"]), ["c"])(a)) { - assertType< - Equal - >(true); - } - }); - await t.step("returns true on Omit object", () => { - assertEquals( - isOmitOf(pred, ["b"])({ a: 0, b: undefined, c: true }), - true, - ); - assertEquals(isOmitOf(pred, ["b", "c"])({ a: 0 }), true); - }); - await t.step("returns false on non Omit object", () => { - assertEquals( - isOmitOf(pred, ["b"])("a"), - false, - "Value is not an object", - ); - assertEquals( - isOmitOf(pred, ["b"])({ a: 0, b: "a", c: "" }), - false, - "Object have a different type property", - ); - }); -}); - -Deno.test("is", async (t) => { - const mod = await import("./is.ts"); - const casesOfAliasAndIsFunction = Object.entries(mod) - .filter(([k, _]) => k.startsWith("is") && k !== "is") - .map(([k, v]) => [k.slice(2), v] as const); - for (const [alias, fn] of casesOfAliasAndIsFunction) { - await t.step(`defines \`${alias}\` function`, () => { - assertStrictEquals(is[alias as keyof typeof is], fn); - }); - } - await t.step( - "only has entries that are the same as the `is*` function aliases", - () => { - const aliases = casesOfAliasAndIsFunction.map(([a]) => a).sort(); - assertEquals(Object.keys(is).sort(), aliases); - }, - ); -}); diff --git a/mod.ts b/mod.ts index e28fc9e..1675800 100644 --- a/mod.ts +++ b/mod.ts @@ -240,6 +240,7 @@ * @module */ +export type * from "./type.ts"; export * from "./as/mod.ts"; -export * from "./is.ts"; +export * from "./is/mod.ts"; export * from "./util.ts"; diff --git a/type.ts b/type.ts new file mode 100644 index 0000000..3079f2e --- /dev/null +++ b/type.ts @@ -0,0 +1,38 @@ +/** + * A type predicate function. + */ +export type Predicate = (x: unknown) => x is T; + +/** + * A type predicated by Predicate. + * + * ```ts + * import { as, is, type PredicateType } from "@core/unknownutil"; + * + * const isPerson = is.ObjectOf({ + * name: is.String, + * age: is.Number, + * address: as.Optional(is.String), + * }); + * + * type Person = PredicateType; + * // Above is equivalent to the following type + * // type Person = { + * // name: string; + * // age: number; + * // address: string | undefined; + * // }; + */ +export type PredicateType

= P extends Predicate ? T : never; + +/** + * JavaScript primitive types. + */ +export type Primitive = + | string + | number + | bigint + | boolean + | null + | undefined + | symbol; diff --git a/util.ts b/util.ts index a17d404..0b52a19 100644 --- a/util.ts +++ b/util.ts @@ -1,4 +1,4 @@ -import type { Predicate } from "./is.ts"; +import type { Predicate } from "./type.ts"; export type AssertMessageFactory = ( x: unknown, From f0b96b696e886949547fad6bb68827f1fc661e56 Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 02:25:04 +0900 Subject: [PATCH 07/22] :coffee: Move bench/script files --- is_bench.ts => .scripts/bench.ts | 6 +++--- {scripts => .scripts}/build_npm.ts | 0 deno.jsonc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) rename is_bench.ts => .scripts/bench.ts (98%) rename {scripts => .scripts}/build_npm.ts (100%) diff --git a/is_bench.ts b/.scripts/bench.ts similarity index 98% rename from is_bench.ts rename to .scripts/bench.ts index c38c5db..75235f9 100644 --- a/is_bench.ts +++ b/.scripts/bench.ts @@ -1,5 +1,5 @@ -import { as } from "./as/mod.ts"; -import { is } from "./is/mod.ts"; +import { as } from "../as/mod.ts"; +import { is } from "../is/mod.ts"; const cs: unknown[] = [ "Hello world", @@ -426,7 +426,7 @@ Deno.bench({ const asOptionalPred = as.Optional(is.String); Deno.bench({ - name: "is.OptionalOf (pre)", + name: "as.Optional (pre)", fn: () => { for (const c of cs) { asOptionalPred(c); diff --git a/scripts/build_npm.ts b/.scripts/build_npm.ts similarity index 100% rename from scripts/build_npm.ts rename to .scripts/build_npm.ts diff --git a/deno.jsonc b/deno.jsonc index d509ed9..f669f47 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -10,7 +10,7 @@ "@std/testing": "jsr:@std/testing@^0.221.0" }, "tasks": { - "build-npm": "deno run -A scripts/build_npm.ts $(git describe --tags --always --dirty)", + "build-npm": "deno run -A .scripts/build_npm.ts $(git describe --tags --always --dirty)", "check": "deno check **/*.ts", "test": "deno test -A --doc --parallel --shuffle", "test:coverage": "deno task test --coverage=.coverage", From 7025a4fb5ffbe1619d84b3dd67d72c89fd428309 Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 02:35:30 +0900 Subject: [PATCH 08/22] :muscle: Individually define utility functions --- util.ts => assert.ts | 46 ----------------------- assert_test.ts | 67 ++++++++++++++++++++++++++++++++++ ensure.ts | 27 ++++++++++++++ util_test.ts => ensure_test.ts | 60 +----------------------------- maybe.ts | 22 +++++++++++ maybe_test.ts | 22 +++++++++++ mod.ts | 6 ++- 7 files changed, 145 insertions(+), 105 deletions(-) rename util.ts => assert.ts (67%) create mode 100644 assert_test.ts create mode 100644 ensure.ts rename util_test.ts => ensure_test.ts (53%) create mode 100644 maybe.ts create mode 100644 maybe_test.ts diff --git a/util.ts b/assert.ts similarity index 67% rename from util.ts rename to assert.ts index 0b52a19..d413841 100644 --- a/util.ts +++ b/assert.ts @@ -92,49 +92,3 @@ export function assert( ); } } - -/** - * Ensures that the given value satisfies the provided predicate. - * - * ```ts - * import { ensure, is } from "@core/unknownutil"; - * - * const a: unknown = "hello"; - * const _: string = ensure(a, is.String); - * ``` - * - * @param x The value to be ensured. - * @param pred The predicate function to test the value against. - * @param options Optional configuration for the assertion. - * @returns The input value `x`. - * @throws {AssertError} If the value does not satisfy the predicate. - */ -export function ensure( - x: unknown, - pred: Predicate, - options: { message?: string; name?: string } = {}, -): T { - assert(x, pred, options); - return x; -} - -/** - * Returns the input value if it satisfies the provided predicate, or `undefined` otherwise. - * - * ```ts - * import { is, maybe } from "@core/unknownutil"; - * - * const a: unknown = "hello"; - * const _: string = maybe(a, is.String) ?? "default value"; - * ``` - * - * @param x The value to be tested. - * @param pred The predicate function to test the value against. - * @returns The input value `x` if it satisfies the predicate, or `undefined` otherwise. - */ -export function maybe( - x: unknown, - pred: Predicate, -): T | undefined { - return pred(x) ? x : undefined; -} diff --git a/assert_test.ts b/assert_test.ts new file mode 100644 index 0000000..3840b64 --- /dev/null +++ b/assert_test.ts @@ -0,0 +1,67 @@ +import { assertThrows } from "@std/assert"; +import { + assert, + AssertError, + defaultAssertMessageFactory, + setAssertMessageFactory, +} from "./assert.ts"; + +const x: unknown = Symbol("x"); + +function truePredicate(_x: unknown): _x is string { + return true; +} + +function falsePredicate(_x: unknown): _x is string { + return false; +} + +Deno.test("assert", async (t) => { + await t.step("does nothing on true predicate", () => { + assert(x, truePredicate); + }); + + await t.step("throws an `AssertError` on false predicate", () => { + assertThrows( + () => assert(x, falsePredicate), + AssertError, + `Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`, + ); + }); + + await t.step( + "throws an `AssertError` on false predicate with a custom name", + () => { + assertThrows( + () => assert(x, falsePredicate, { name: "hello world" }), + AssertError, + `Expected hello world that satisfies the predicate falsePredicate, got symbol: undefined`, + ); + }, + ); + + await t.step( + "throws an `AssertError` with a custom message on false predicate", + () => { + assertThrows( + () => assert(x, falsePredicate, { message: "Hello" }), + AssertError, + "Hello", + ); + }, + ); +}); + +Deno.test("setAssertMessageFactory", async (t) => { + setAssertMessageFactory((x, pred) => `Hello ${typeof x} ${pred.name}`); + + await t.step("change `AssertError` message on `assert` failure", () => { + assertThrows( + () => assert(x, falsePredicate), + AssertError, + "Hello symbol falsePredicate", + ); + }); + + setAssertMessageFactory(defaultAssertMessageFactory); +}); diff --git a/ensure.ts b/ensure.ts new file mode 100644 index 0000000..d2de89a --- /dev/null +++ b/ensure.ts @@ -0,0 +1,27 @@ +import type { Predicate } from "./type.ts"; +import { assert } from "./assert.ts"; + +/** + * Ensures that the given value satisfies the provided predicate. + * + * ```ts + * import { ensure, is } from "@core/unknownutil"; + * + * const a: unknown = "hello"; + * const _: string = ensure(a, is.String); + * ``` + * + * @param x The value to be ensured. + * @param pred The predicate function to test the value against. + * @param options Optional configuration for the assertion. + * @returns The input value `x`. + * @throws {AssertError} If the value does not satisfy the predicate. + */ +export function ensure( + x: unknown, + pred: Predicate, + options: { message?: string; name?: string } = {}, +): T { + assert(x, pred, options); + return x; +} diff --git a/util_test.ts b/ensure_test.ts similarity index 53% rename from util_test.ts rename to ensure_test.ts index 8087ff9..8659fa0 100644 --- a/util_test.ts +++ b/ensure_test.ts @@ -1,12 +1,10 @@ import { assertStrictEquals, assertThrows } from "@std/assert"; import { - assert, AssertError, defaultAssertMessageFactory, - ensure, - maybe, setAssertMessageFactory, -} from "./util.ts"; +} from "./assert.ts"; +import { ensure } from "./ensure.ts"; const x: unknown = Symbol("x"); @@ -18,42 +16,6 @@ function falsePredicate(_x: unknown): _x is string { return false; } -Deno.test("assert", async (t) => { - await t.step("does nothing on true predicate", () => { - assert(x, truePredicate); - }); - - await t.step("throws an `AssertError` on false predicate", () => { - assertThrows( - () => assert(x, falsePredicate), - AssertError, - `Expected a value that satisfies the predicate falsePredicate, got symbol: undefined`, - ); - }); - - await t.step( - "throws an `AssertError` on false predicate with a custom name", - () => { - assertThrows( - () => assert(x, falsePredicate, { name: "hello world" }), - AssertError, - `Expected hello world that satisfies the predicate falsePredicate, got symbol: undefined`, - ); - }, - ); - - await t.step( - "throws an `AssertError` with a custom message on false predicate", - () => { - assertThrows( - () => assert(x, falsePredicate, { message: "Hello" }), - AssertError, - "Hello", - ); - }, - ); -}); - Deno.test("ensure", async (t) => { await t.step("returns `x` as-is on true predicate", () => { assertStrictEquals(ensure(x, truePredicate), x); @@ -90,27 +52,9 @@ Deno.test("ensure", async (t) => { ); }); -Deno.test("maybe", async (t) => { - await t.step("returns `x` as-is on true predicate", () => { - assertStrictEquals(maybe(x, truePredicate), x); - }); - - await t.step("returns `undefined` on false predicate", () => { - assertStrictEquals(maybe(x, falsePredicate), undefined); - }); -}); - Deno.test("setAssertMessageFactory", async (t) => { setAssertMessageFactory((x, pred) => `Hello ${typeof x} ${pred.name}`); - await t.step("change `AssertError` message on `assert` failure", () => { - assertThrows( - () => assert(x, falsePredicate), - AssertError, - "Hello symbol falsePredicate", - ); - }); - await t.step("change `AssertError` message on `ensure` failure", () => { assertThrows( () => ensure(x, falsePredicate), diff --git a/maybe.ts b/maybe.ts new file mode 100644 index 0000000..87e52a0 --- /dev/null +++ b/maybe.ts @@ -0,0 +1,22 @@ +import type { Predicate } from "./type.ts"; + +/** + * Returns the input value if it satisfies the provided predicate, or `undefined` otherwise. + * + * ```ts + * import { is, maybe } from "@core/unknownutil"; + * + * const a: unknown = "hello"; + * const _: string = maybe(a, is.String) ?? "default value"; + * ``` + * + * @param x The value to be tested. + * @param pred The predicate function to test the value against. + * @returns The input value `x` if it satisfies the predicate, or `undefined` otherwise. + */ +export function maybe( + x: unknown, + pred: Predicate, +): T | undefined { + return pred(x) ? x : undefined; +} diff --git a/maybe_test.ts b/maybe_test.ts new file mode 100644 index 0000000..d15d831 --- /dev/null +++ b/maybe_test.ts @@ -0,0 +1,22 @@ +import { assertStrictEquals } from "@std/assert"; +import { maybe } from "./maybe.ts"; + +const x: unknown = Symbol("x"); + +function truePredicate(_x: unknown): _x is string { + return true; +} + +function falsePredicate(_x: unknown): _x is string { + return false; +} + +Deno.test("maybe", async (t) => { + await t.step("returns `x` as-is on true predicate", () => { + assertStrictEquals(maybe(x, truePredicate), x); + }); + + await t.step("returns `undefined` on false predicate", () => { + assertStrictEquals(maybe(x, falsePredicate), undefined); + }); +}); diff --git a/mod.ts b/mod.ts index 1675800..4507418 100644 --- a/mod.ts +++ b/mod.ts @@ -241,6 +241,10 @@ */ export type * from "./type.ts"; + export * from "./as/mod.ts"; export * from "./is/mod.ts"; -export * from "./util.ts"; + +export * from "./assert.ts"; +export * from "./ensure.ts"; +export * from "./maybe.ts"; From 69d7d451d2559d35be44222056a3e7abc687971f Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 02:38:11 +0900 Subject: [PATCH 09/22] :muscle: Rename `inspect.ts` to `_inspect.ts` --- __snapshots__/{inspect_test.ts.snap => _inspect_test.ts.snap} | 0 _funcutil.ts | 2 +- inspect.ts => _inspect.ts | 0 inspect_test.ts => _inspect_test.ts | 2 +- 4 files changed, 2 insertions(+), 2 deletions(-) rename __snapshots__/{inspect_test.ts.snap => _inspect_test.ts.snap} (100%) rename inspect.ts => _inspect.ts (100%) rename inspect_test.ts => _inspect_test.ts (97%) diff --git a/__snapshots__/inspect_test.ts.snap b/__snapshots__/_inspect_test.ts.snap similarity index 100% rename from __snapshots__/inspect_test.ts.snap rename to __snapshots__/_inspect_test.ts.snap diff --git a/_funcutil.ts b/_funcutil.ts index dcb79f6..54e1cf6 100644 --- a/_funcutil.ts +++ b/_funcutil.ts @@ -1,4 +1,4 @@ -import { inspect } from "./inspect.ts"; +import { inspect } from "./_inspect.ts"; /** * Rewrite the function name. diff --git a/inspect.ts b/_inspect.ts similarity index 100% rename from inspect.ts rename to _inspect.ts diff --git a/inspect_test.ts b/_inspect_test.ts similarity index 97% rename from inspect_test.ts rename to _inspect_test.ts index eecceea..c2f0042 100644 --- a/inspect_test.ts +++ b/_inspect_test.ts @@ -1,5 +1,5 @@ import { assertSnapshot } from "@std/testing/snapshot"; -import { inspect } from "./inspect.ts"; +import { inspect } from "./_inspect.ts"; Deno.test("inspect", async (t) => { await t.step("string", async (t) => { From 67ef8f9b9e3173606e3a6d7dbd19e3702e41a82b Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 03:05:24 +0900 Subject: [PATCH 10/22] :herb: Fix tests for `as/optional.ts` --- as/optional_test.ts | 60 ++++----------------------------------------- 1 file changed, 5 insertions(+), 55 deletions(-) diff --git a/as/optional_test.ts b/as/optional_test.ts index 694036d..8c94bf6 100644 --- a/as/optional_test.ts +++ b/as/optional_test.ts @@ -1,59 +1,9 @@ -import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, stringify } from "../_testutil.ts"; -import type { Predicate } from "../type.ts"; +import { type Equal, testWithExamples } from "../_testutil.ts"; import { is } from "../is/mod.ts"; import { asOptional, asUnoptional } from "./optional.ts"; -const examples = { - string: ["", "Hello world"], - number: [0, 1234567890], - bigint: [0n, 1234567890n], - boolean: [true, false], - array: [[], [0, 1, 2], ["a", "b", "c"], [0, "a", true]], - set: [new Set(), new Set([0, 1, 2]), new Set(["a", "b", "c"])], - record: [{}, { a: 0, b: 1, c: 2 }, { a: "a", b: "b", c: "c" }], - map: [ - new Map(), - new Map([["a", 0], ["b", 1], ["c", 2]]), - new Map([["a", "a"], ["b", "b"], ["c", "c"]]), - ], - syncFunction: [function a() {}, () => {}], - asyncFunction: [async function b() {}, async () => {}], - null: [null], - undefined: [undefined], - symbol: [Symbol("a"), Symbol("b"), Symbol("c")], - date: [new Date(1690248225000), new Date(0)], - promise: [new Promise(() => {})], -} as const; - -async function testWithExamples( - t: Deno.TestContext, - pred: Predicate, - opts?: { - validExamples?: (keyof typeof examples)[]; - excludeExamples?: (keyof typeof examples)[]; - }, -): Promise { - const { validExamples = [], excludeExamples = [] } = opts ?? {}; - const exampleEntries = (Object.entries(examples) as unknown as [ - name: keyof typeof examples, - example: unknown[], - ][]).filter(([k]) => !excludeExamples.includes(k)); - for (const [name, example] of exampleEntries) { - const expect = validExamples.includes(name); - for (const v of example) { - await t.step( - `returns ${expect} on ${stringify(v)}`, - () => { - assertEquals(pred(v), expect); - }, - ); - } - } -} - Deno.test("asOptional", async (t) => { await t.step("returns properly named function", async (t) => { await assertSnapshot(t, asOptional(is.Number).name); @@ -62,8 +12,8 @@ Deno.test("asOptional", async (t) => { }); await t.step("returns proper type predicate", () => { const a: unknown = undefined; - if (asOptional(is.Number)(a)) { - assertType>(true); + if (is.ObjectOf({ a: asOptional(is.Number) })(a)) { + assertType>(true); } }); await t.step("with is.String", async (t) => { @@ -146,8 +96,8 @@ Deno.test("asUnoptional", async (t) => { }); await t.step("returns proper type predicate", () => { const a: unknown = undefined; - if (asUnoptional(asOptional(is.Number))(a)) { - assertType>(true); + if (is.ObjectOf({ a: asUnoptional(asOptional(is.Number)) })(a)) { + assertType>(true); } if (asUnoptional(is.Number)(a)) { assertType>(true); From f0392ca09fb1309bd9623816e43a60e5d45956f7 Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 03:26:44 +0900 Subject: [PATCH 11/22] :+1: Add `as.Readonly` and `as.Unreadonly` --- _annotation.ts | 7 +++ as/__snapshots__/readonly_test.ts.snap | 11 ++++ as/mod.ts | 3 + as/readonly.ts | 82 ++++++++++++++++++++++++++ as/readonly_test.ts | 38 ++++++++++++ is/object_of.ts | 35 +++++++++-- 6 files changed, 170 insertions(+), 6 deletions(-) create mode 100644 as/__snapshots__/readonly_test.ts.snap create mode 100644 as/readonly.ts create mode 100644 as/readonly_test.ts diff --git a/_annotation.ts b/_annotation.ts index 0a892ed..87d0106 100644 --- a/_annotation.ts +++ b/_annotation.ts @@ -36,6 +36,13 @@ export type WithOptional = { optional: Predicate; }; +/** + * Annotation for readonly. + */ +export type WithReadonly = { + readonly: Predicate; +}; + /** * Annotation for predObj. */ diff --git a/as/__snapshots__/readonly_test.ts.snap b/as/__snapshots__/readonly_test.ts.snap new file mode 100644 index 0000000..0c89b33 --- /dev/null +++ b/as/__snapshots__/readonly_test.ts.snap @@ -0,0 +1,11 @@ +export const snapshot = {}; + +snapshot[`asReadonly > returns properly named function 1`] = `"asReadonly(isNumber)"`; + +snapshot[`asReadonly > returns properly named function 2`] = `"asReadonly(isNumber)"`; + +snapshot[`asUnreadonly > returns properly named function 1`] = `"isNumber"`; + +snapshot[`asUnreadonly > returns properly named function 2`] = `"isNumber"`; + +snapshot[`asUnreadonly > returns properly named function 3`] = `"isNumber"`; diff --git a/as/mod.ts b/as/mod.ts index 2afeab2..c4619da 100644 --- a/as/mod.ts +++ b/as/mod.ts @@ -1,6 +1,9 @@ import { asOptional, asUnoptional } from "./optional.ts"; +import { asReadonly, asUnreadonly } from "./readonly.ts"; export const as = { Optional: asOptional, + Readonly: asReadonly, Unoptional: asUnoptional, + Unreadonly: asUnreadonly, }; diff --git a/as/readonly.ts b/as/readonly.ts new file mode 100644 index 0000000..9448af0 --- /dev/null +++ b/as/readonly.ts @@ -0,0 +1,82 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { Predicate } from "../type.ts"; +import { + annotate, + hasAnnotation, + unannotate, + type WithReadonly, +} from "../_annotation.ts"; + +/** + * Return an `Readonly` annotated type predicate function that returns `true` if the type of `x` is `T`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.ObjectOf({ + * foo: as.Readonly(is.String), + * }); + * const a: unknown = {}; + * if (isMyType(a)) { + * // a is narrowed to {readonly foo: string} + * const _: {readonly foo: string} = a; + * } + * ``` + */ +export function asReadonly( + pred: Predicate, +): Predicate & WithReadonly { + if (hasAnnotation(pred, "readonly")) { + return pred as Predicate & WithReadonly; + } + return rewriteName( + annotate( + (x: unknown): x is T => pred(x), + "readonly", + pred, + ), + "asReadonly", + pred, + ) as Predicate & WithReadonly; +} + +/** + * Return an `Readonly` un-annotated type predicate function that returns `true` if the type of `x` is `T`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.ObjectOf({ + * foo: as.Unreadonly(as.Readonly(is.String)), + * }); + * const a: unknown = {foo: "a"}; + * if (isMyType(a)) { + * // a is narrowed to {foo: string} + * const _: {foo: string} = a; + * } + * ``` + */ +export function asUnreadonly< + P extends Predicate, + T extends P extends Predicate ? T : never, +>(pred: P): Predicate { + if (!hasAnnotation(pred, "readonly")) { + return pred as Predicate; + } + return unannotate(pred, "readonly") as Predicate; +} + +/** + * Check if the given type predicate has readonly annotation. + */ +export function hasReadonly< + P extends Predicate, +>( + pred: P, +): pred is P & WithReadonly { + return hasAnnotation(pred, "readonly"); +} diff --git a/as/readonly_test.ts b/as/readonly_test.ts new file mode 100644 index 0000000..666913b --- /dev/null +++ b/as/readonly_test.ts @@ -0,0 +1,38 @@ +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import type { Equal } from "../_testutil.ts"; +import { is } from "../is/mod.ts"; +import { asReadonly, asUnreadonly } from "./readonly.ts"; + +Deno.test("asReadonly", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, asReadonly(is.Number).name); + // Nesting does nothing + await assertSnapshot(t, asReadonly(asReadonly(is.Number)).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = undefined; + if (is.ObjectOf({ a: asReadonly(is.Number) })(a)) { + assertType>(true); + } + }); +}); + +Deno.test("asUnreadonly", async (t) => { + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, asUnreadonly(asReadonly(is.Number)).name); + // Non optional does nothing + await assertSnapshot(t, asUnreadonly(is.Number).name); + // Nesting does nothing + await assertSnapshot( + t, + asUnreadonly(asUnreadonly(asReadonly(is.Number))).name, + ); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = undefined; + if (is.ObjectOf({ a: asUnreadonly(asReadonly(is.Number)) })(a)) { + assertType>(true); + } + }); +}); diff --git a/is/object_of.ts b/is/object_of.ts index 6e5f461..1fa87d2 100644 --- a/is/object_of.ts +++ b/is/object_of.ts @@ -4,6 +4,7 @@ import { annotate, type WithOptional, type WithPredObj, + type WithReadonly, } from "../_annotation.ts"; import type { Predicate } from "../type.ts"; @@ -58,14 +59,36 @@ export function isObjectOf< } type ObjectOf>> = FlatType< - // Optional + // Readonly/Optional & { - [K in keyof T as T[K] extends WithOptional ? K : never]?: - T[K] extends Predicate ? U : never; + readonly [ + K in keyof T as T[K] extends WithReadonly + ? T[K] extends WithOptional ? K : never + : never + ]?: T[K] extends Predicate ? U : never; } - // Non optional + // Readonly/Non optional & { - [K in keyof T as T[K] extends WithOptional ? never : K]: - T[K] extends Predicate ? U : never; + readonly [ + K in keyof T as T[K] extends WithReadonly + ? T[K] extends WithOptional ? never : K + : never + ]: T[K] extends Predicate ? U : never; + } + // Non readonly/Optional + & { + [ + K in keyof T as T[K] extends WithReadonly ? never + : T[K] extends WithOptional ? K + : never + ]?: T[K] extends Predicate ? U : never; + } + // Non readonly/Non optional + & { + [ + K in keyof T as T[K] extends WithReadonly ? never + : T[K] extends WithOptional ? never + : K + ]: T[K] extends Predicate ? U : never; } >; From 9b1aa43446c6668f02ab79b3b2011057d003de49 Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 04:20:21 +0900 Subject: [PATCH 12/22] :+1: Add `isReadonlyOf` --- is/__snapshots__/readonly_of_test.ts.snap | 47 +++++++ is/mod.ts | 2 + is/readonly_of.ts | 48 +++++++ is/readonly_of_test.ts | 162 ++++++++++++++++++++++ 4 files changed, 259 insertions(+) create mode 100644 is/__snapshots__/readonly_of_test.ts.snap create mode 100644 is/readonly_of.ts create mode 100644 is/readonly_of_test.ts diff --git a/is/__snapshots__/readonly_of_test.ts.snap b/is/__snapshots__/readonly_of_test.ts.snap new file mode 100644 index 0000000..54c352d --- /dev/null +++ b/is/__snapshots__/readonly_of_test.ts.snap @@ -0,0 +1,47 @@ +export const snapshot = {}; + +snapshot[`isReadonlyOf > with isRecord > returns properly named function 1`] = `"isReadonlyOf(isRecord)"`; + +snapshot[`isReadonlyOf > with isRecord > returns properly named function 2`] = `"isReadonlyOf(isRecord)"`; + +snapshot[`isReadonlyOf > with isObjectOf > returns properly named function 1`] = ` +"isReadonlyOf(isObjectOf({ + a: isNumber, + b: isUnionOf([ + isString, + isUndefined + ]), + c: asReadonly(isBoolean) +}))" +`; + +snapshot[`isReadonlyOf > with isObjectOf > returns properly named function 2`] = ` +"isReadonlyOf(isObjectOf({ + a: isNumber, + b: isUnionOf([ + isString, + isUndefined + ]), + c: asReadonly(isBoolean) +}))" +`; + +snapshot[`isReadonlyOf > with isTupleOf > returns properly named function 1`] = ` +"isReadonlyOf(isTupleOf([ + isNumber, + isString, + asReadonly(isBoolean) +]))" +`; + +snapshot[`isReadonlyOf > with isTupleOf > returns properly named function 2`] = ` +"isReadonlyOf(isTupleOf([ + isNumber, + isString, + asReadonly(isBoolean) +]))" +`; + +snapshot[`isReadonlyOf > with isUniformTupleOf > returns properly named function 1`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`; + +snapshot[`isReadonlyOf > with isUniformTupleOf > returns properly named function 2`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`; diff --git a/is/mod.ts b/is/mod.ts index 7f75b9d..a29adcf 100644 --- a/is/mod.ts +++ b/is/mod.ts @@ -20,6 +20,7 @@ import { isParametersOf } from "./parameters_of.ts"; import { isPartialOf } from "./partial_of.ts"; import { isPickOf } from "./pick_of.ts"; import { isPrimitive } from "./primitive.ts"; +import { isReadonlyOf } from "./readonly_of.ts"; import { isRecord } from "./record.ts"; import { isRecordObject } from "./record_object.ts"; import { isRecordObjectOf } from "./record_object_of.ts"; @@ -60,6 +61,7 @@ export const is = { PartialOf: isPartialOf, PickOf: isPickOf, Primitive: isPrimitive, + ReadonlyOf: isReadonlyOf, Record: isRecord, RecordObject: isRecordObject, RecordObjectOf: isRecordObjectOf, diff --git a/is/readonly_of.ts b/is/readonly_of.ts new file mode 100644 index 0000000..70ee62f --- /dev/null +++ b/is/readonly_of.ts @@ -0,0 +1,48 @@ +import { rewriteName } from "../_funcutil.ts"; +import type { WithPredObj } from "../_annotation.ts"; +import type { Predicate } from "../type.ts"; + +/** + * Return a type predicate function that returns `true` if the type of `x` is `Readonly>`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```typescript + * import { as, is } from "@core/unknownutil"; + * + * const isMyType = is.ReadonlyOf(is.ObjectOf({ + * a: is.Number, + * b: is.UnionOf([is.String, is.Undefined]), + * c: as.Readonly(is.Boolean), + * })); + * const a: unknown = { a: 0, b: "b", c: true }; + * if (isMyType(a)) { + * // 'a' is narrowed to { readonly a: number; readonly b: string | undefined; readonly c: boolean } + * const _: { readonly a: number; readonly b: string | undefined; readonly c: boolean } = a; + * } + * ``` + */ +export function isReadonlyOf< + T extends Record, + P extends Record>, +>( + pred: Predicate & WithPredObj

, +): + & Predicate> + & WithPredObj

; +export function isReadonlyOf< + T extends Record, +>( + pred: Predicate, +): Predicate>; +export function isReadonlyOf< + T extends readonly [unknown, ...unknown[]], +>( + pred: Predicate, +): Predicate>; +export function isReadonlyOf( + pred: Predicate, +): Predicate> { + if (pred.name.startsWith("isReadonlyOf(")) return pred; + return rewriteName((x) => pred(x), "isReadonlyOf", pred); +} diff --git a/is/readonly_of_test.ts b/is/readonly_of_test.ts new file mode 100644 index 0000000..473ab8a --- /dev/null +++ b/is/readonly_of_test.ts @@ -0,0 +1,162 @@ +import { assertEquals } from "@std/assert"; +import { assertSnapshot } from "@std/testing/snapshot"; +import { assertType } from "@std/testing/types"; +import type { Equal } from "../_testutil.ts"; +import { as } from "../as/mod.ts"; +import { is } from "../is/mod.ts"; +import { isReadonlyOf } from "./readonly_of.ts"; + +Deno.test("isReadonlyOf", async (t) => { + await t.step("with isRecord", async (t) => { + const pred = is.Record; + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isReadonlyOf(pred).name); + // Nestable (no effect) + await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isReadonlyOf(pred)(a)) { + assertType< + Equal< + typeof a, + Readonly> + > + >(true); + } + }); + await t.step("returns true on Readonly object", () => { + assertEquals( + isReadonlyOf(pred)({ a: 0, b: "b", c: true } as const), + true, + ); + assertEquals( + isReadonlyOf(pred)({ a: 0, b: "b", c: true }), + true, + ); + }); + await t.step("returns false on non Readonly object", () => { + assertEquals(isReadonlyOf(pred)("a"), false, "Value is not an object"); + assertEquals( + isReadonlyOf(pred)([]), + false, + "Object have a different type property", + ); + }); + }); + await t.step("with isObjectOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: is.UnionOf([is.String, is.Undefined]), + c: as.Readonly(is.Boolean), + }); + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isReadonlyOf(pred).name); + // Nestable (no effect) + await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isReadonlyOf(pred)(a)) { + assertType< + Equal< + typeof a, + Readonly<{ a: number; b: string | undefined; c: boolean }> + > + >(true); + } + }); + await t.step("returns true on Readonly object", () => { + assertEquals( + isReadonlyOf(pred)({ a: 0, b: "b", c: true } as const), + true, + ); + assertEquals( + isReadonlyOf(pred)({ a: 0, b: "b", c: true }), + true, + ); + }); + await t.step("returns false on non Readonly object", () => { + assertEquals(isReadonlyOf(pred)("a"), false, "Value is not an object"); + assertEquals( + isReadonlyOf(pred)({ a: 0, b: "a", c: "" }), + false, + "Object have a different type property", + ); + }); + }); + await t.step("with isTupleOf", async (t) => { + const pred = is.TupleOf([is.Number, is.String, as.Readonly(is.Boolean)]); + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isReadonlyOf(pred).name); + // Nestable (no effect) + await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = []; + if (isReadonlyOf(pred)(a)) { + assertType< + Equal< + typeof a, + Readonly<[number, string, boolean]> + > + >(true); + } + }); + await t.step("returns true on Readonly object", () => { + assertEquals( + isReadonlyOf(pred)([0, "b", true] as const), + true, + ); + assertEquals( + isReadonlyOf(pred)([0, "b", true]), + true, + ); + }); + await t.step("returns false on non Readonly object", () => { + assertEquals(isReadonlyOf(pred)("a"), false, "Value is not an object"); + assertEquals( + isReadonlyOf(pred)([0, "a", ""]), + false, + "Object have a different type property", + ); + }); + }); + await t.step("with isUniformTupleOf", async (t) => { + const pred = is.UniformTupleOf(3, is.Number); + await t.step("returns properly named function", async (t) => { + await assertSnapshot(t, isReadonlyOf(pred).name); + // Nestable (no effect) + await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); + }); + await t.step("returns proper type predicate", () => { + const a: unknown = []; + if (isReadonlyOf(pred)(a)) { + assertType< + Equal< + typeof a, + Readonly<[number, number, number]> + > + >(true); + } + }); + await t.step("returns true on Readonly object", () => { + assertEquals( + isReadonlyOf(pred)([0, 1, 2] as const), + true, + ); + assertEquals( + isReadonlyOf(pred)([0, 1, 2]), + true, + ); + }); + await t.step("returns false on non Readonly object", () => { + assertEquals(isReadonlyOf(pred)("a"), false, "Value is not an object"); + assertEquals( + isReadonlyOf(pred)([0, "a", ""]), + false, + "Object have a different type property", + ); + }); + }); +}); From 161a015f3bff41cb5ce4ab747f8959594629c863 Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 04:48:59 +0900 Subject: [PATCH 13/22] :bug: Fix `asOptional` and `asReadonly` for type --- as/optional.ts | 23 ++++++++++++++++------- as/readonly.ts | 14 +++++++------- is/object_of_test.ts | 27 +++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 14 deletions(-) diff --git a/as/optional.ts b/as/optional.ts index 1ca182d..25e563f 100644 --- a/as/optional.ts +++ b/as/optional.ts @@ -1,5 +1,5 @@ import { rewriteName } from "../_funcutil.ts"; -import type { Predicate } from "../type.ts"; +import type { Predicate, PredicateType } from "../type.ts"; import { annotate, hasAnnotation, @@ -25,21 +25,30 @@ import { * } * ``` */ -export function asOptional( - pred: Predicate, -): Predicate & WithOptional { +export function asOptional

>( + pred: P, +): + & Extract>> + & Predicate | undefined> + & WithOptional> { if (hasAnnotation(pred, "optional")) { - return pred as Predicate & WithOptional; + return pred as + & Extract>> + & Predicate | undefined> + & WithOptional>; } return rewriteName( annotate( - (x: unknown): x is T | undefined => x === undefined || pred(x), + (x: unknown) => x === undefined || pred(x), "optional", pred, ), "asOptional", pred, - ) as Predicate & WithOptional; + ) as unknown as + & Extract>> + & Predicate | undefined> + & WithOptional>; } /** diff --git a/as/readonly.ts b/as/readonly.ts index 9448af0..51a8357 100644 --- a/as/readonly.ts +++ b/as/readonly.ts @@ -1,5 +1,5 @@ import { rewriteName } from "../_funcutil.ts"; -import type { Predicate } from "../type.ts"; +import type { Predicate, PredicateType } from "../type.ts"; import { annotate, hasAnnotation, @@ -25,21 +25,21 @@ import { * } * ``` */ -export function asReadonly( - pred: Predicate, -): Predicate & WithReadonly { +export function asReadonly

>( + pred: P, +): P & WithReadonly { if (hasAnnotation(pred, "readonly")) { - return pred as Predicate & WithReadonly; + return pred as P & WithReadonly; } return rewriteName( annotate( - (x: unknown): x is T => pred(x), + (x: unknown) => pred(x), "readonly", pred, ), "asReadonly", pred, - ) as Predicate & WithReadonly; + ) as unknown as P & WithReadonly; } /** diff --git a/is/object_of_test.ts b/is/object_of_test.ts index c356bff..e951ef6 100644 --- a/is/object_of_test.ts +++ b/is/object_of_test.ts @@ -3,6 +3,7 @@ import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; import { type Equal, testWithExamples } from "../_testutil.ts"; import { is } from "./mod.ts"; +import { as } from "../as/mod.ts"; import { isObjectOf } from "./object_of.ts"; Deno.test("isObjectOf", async (t) => { @@ -82,6 +83,32 @@ Deno.test("isObjectOf", async (t) => { }; assertEquals(isObjectOf(predObj)(date), true, "Value is not an object"); }); + await t.step("with asOptional/asReadonly", () => { + const predObj = { + a: as.Readonly(as.Optional(is.String)), + b: as.Optional(as.Readonly(is.String)), + c: as.Readonly(is.String), + d: as.Optional(is.String), + e: as.Unreadonly(as.Unoptional(as.Readonly(as.Optional(is.String)))), + f: as.Unoptional(as.Unreadonly(as.Optional(as.Readonly(is.String)))), + }; + const a: unknown = undefined; + if (isObjectOf(predObj)(a)) { + assertType< + Equal< + typeof a, + { + readonly a?: string; + readonly b?: string; + readonly c: string; + d?: string; + e: string; + f: string; + } + > + >(true); + } + }); await testWithExamples( t, isObjectOf({ a: (_: unknown): _ is unknown => false }), From 26d9523e02281bfb5bb9a6ed569c2a59bb336cda Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 04:52:31 +0900 Subject: [PATCH 14/22] :herb: Improve tests of `isPartialOf` and `isRequiredOf` --- is/__snapshots__/partial_of_test.ts.snap | 6 ++++-- is/__snapshots__/required_of_test.ts.snap | 6 ++++-- is/partial_of_test.ts | 6 +++++- is/required_of_test.ts | 6 +++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/is/__snapshots__/partial_of_test.ts.snap b/is/__snapshots__/partial_of_test.ts.snap index d6109b0..315f88f 100644 --- a/is/__snapshots__/partial_of_test.ts.snap +++ b/is/__snapshots__/partial_of_test.ts.snap @@ -7,7 +7,8 @@ snapshot[`isPartialOf > returns properly named function 1`] = ` isString, isUndefined ])), - c: asOptional(isBoolean) + c: asOptional(isBoolean), + d: asOptional(asReadonly(isString)) })" `; @@ -18,6 +19,7 @@ snapshot[`isPartialOf > returns properly named function 2`] = ` isString, isUndefined ])), - c: asOptional(isBoolean) + c: asOptional(isBoolean), + d: asOptional(asReadonly(isString)) })" `; diff --git a/is/__snapshots__/required_of_test.ts.snap b/is/__snapshots__/required_of_test.ts.snap index 0f7010e..a858a1a 100644 --- a/is/__snapshots__/required_of_test.ts.snap +++ b/is/__snapshots__/required_of_test.ts.snap @@ -7,7 +7,8 @@ snapshot[`isRequiredOf > returns properly named function 1`] = ` isString, isUndefined ]), - c: isBoolean + c: isBoolean, + d: asReadonly(isString) })" `; @@ -18,6 +19,7 @@ snapshot[`isRequiredOf > returns properly named function 2`] = ` isString, isUndefined ]), - c: isBoolean + c: isBoolean, + d: asReadonly(isString) })" `; diff --git a/is/partial_of_test.ts b/is/partial_of_test.ts index 78483db..0de3e26 100644 --- a/is/partial_of_test.ts +++ b/is/partial_of_test.ts @@ -11,6 +11,7 @@ Deno.test("isPartialOf", async (t) => { a: is.Number, b: is.UnionOf([is.String, is.Undefined]), c: as.Optional(is.Boolean), + d: as.Readonly(is.String), }); await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isPartialOf(pred).name); @@ -21,7 +22,10 @@ Deno.test("isPartialOf", async (t) => { const a: unknown = { a: 0, b: "a", c: true }; if (isPartialOf(pred)(a)) { assertType< - Equal> + Equal< + typeof a, + Partial<{ a: number; b: string; c: boolean; readonly d: string }> + > >(true); } }); diff --git a/is/required_of_test.ts b/is/required_of_test.ts index b5b9743..11e947b 100644 --- a/is/required_of_test.ts +++ b/is/required_of_test.ts @@ -11,6 +11,7 @@ Deno.test("isRequiredOf", async (t) => { a: is.Number, b: is.UnionOf([is.String, is.Undefined]), c: as.Optional(is.Boolean), + d: as.Readonly(is.String), }); await t.step("returns properly named function", async (t) => { await assertSnapshot(t, isRequiredOf(pred).name); @@ -21,7 +22,10 @@ Deno.test("isRequiredOf", async (t) => { const a: unknown = { a: 0, b: "a", c: true }; if (isRequiredOf(pred)(a)) { assertType< - Equal + Equal< + typeof a, + { a: number; b: string | undefined; c: boolean; readonly d: string } + > >(true); } }); From 82ebdb4147dfa8bef24dc13af206828b6fd0133e Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 05:42:03 +0900 Subject: [PATCH 15/22] :memo: Update document comments --- as/mod.ts | 3 +++ as/optional.ts | 2 -- as/readonly.ts | 4 +--- assert.ts | 18 +++++++++++++----- ensure.ts | 3 ++- is/any.ts | 1 - is/array.ts | 1 - is/array_of.ts | 1 - is/async_function.ts | 1 - is/bigint.ts | 1 - is/boolean.ts | 1 - is/function.ts | 1 - is/instance_of.ts | 1 - is/intersection_of.ts | 2 -- is/literal_of.ts | 1 - is/literal_one_of.ts | 1 - is/map.ts | 1 - is/map_of.ts | 2 -- is/mod.ts | 3 +++ is/null.ts | 1 - is/nullish.ts | 1 - is/number.ts | 1 - is/object_of.ts | 11 ++++++----- is/omit_of.ts | 4 +--- is/parameters_of.ts | 5 +---- is/partial_of.ts | 2 -- is/pick_of.ts | 4 +--- is/primitive.ts | 1 - is/readonly_of.ts | 1 - is/record.ts | 3 +-- is/record_object.ts | 3 +-- is/record_object_of.ts | 4 +--- is/record_of.ts | 5 +++-- is/required_of.ts | 1 - is/set.ts | 1 - is/set_of.ts | 1 - is/strict_of.ts | 4 ---- is/string.ts | 1 - is/symbol.ts | 1 - is/sync_function.ts | 1 - is/tuple_of.ts | 3 --- is/undefined.ts | 1 - is/uniform_tuple_of.ts | 2 -- is/union_of.ts | 2 -- is/unknown.ts | 1 - type.ts | 21 ++++++++++++++++++--- 46 files changed, 55 insertions(+), 79 deletions(-) diff --git a/as/mod.ts b/as/mod.ts index c4619da..0f46786 100644 --- a/as/mod.ts +++ b/as/mod.ts @@ -1,6 +1,9 @@ import { asOptional, asUnoptional } from "./optional.ts"; import { asReadonly, asUnreadonly } from "./readonly.ts"; +/** + * Annotation collection for object predicate properties. + */ export const as = { Optional: asOptional, Readonly: asReadonly, diff --git a/as/optional.ts b/as/optional.ts index 25e563f..50347fd 100644 --- a/as/optional.ts +++ b/as/optional.ts @@ -20,7 +20,6 @@ import { * }); * const a: unknown = {}; * if (isMyType(a)) { - * // a is narrowed to {foo?: string} * const _: {foo?: string} = a; * } * ``` @@ -64,7 +63,6 @@ export function asOptional

>( * }); * const a: unknown = {foo: "a"}; * if (isMyType(a)) { - * // a is narrowed to {foo: string} * const _: {foo: string} = a; * } * ``` diff --git a/as/readonly.ts b/as/readonly.ts index 51a8357..2bc01f5 100644 --- a/as/readonly.ts +++ b/as/readonly.ts @@ -1,5 +1,5 @@ import { rewriteName } from "../_funcutil.ts"; -import type { Predicate, PredicateType } from "../type.ts"; +import type { Predicate } from "../type.ts"; import { annotate, hasAnnotation, @@ -20,7 +20,6 @@ import { * }); * const a: unknown = {}; * if (isMyType(a)) { - * // a is narrowed to {readonly foo: string} * const _: {readonly foo: string} = a; * } * ``` @@ -55,7 +54,6 @@ export function asReadonly

>( * }); * const a: unknown = {foo: "a"}; * if (isMyType(a)) { - * // a is narrowed to {foo: string} * const _: {foo: string} = a; * } * ``` diff --git a/assert.ts b/assert.ts index d413841..c76ad75 100644 --- a/assert.ts +++ b/assert.ts @@ -1,11 +1,17 @@ import type { Predicate } from "./type.ts"; +/** + * A factory function that generates assertion error messages. + */ export type AssertMessageFactory = ( x: unknown, pred: Predicate, name?: string, ) => string; +/** + * The default factory function used to generate assertion error messages. + */ export const defaultAssertMessageFactory: AssertMessageFactory = ( x, pred, @@ -26,7 +32,7 @@ let assertMessageFactory = defaultAssertMessageFactory; */ export class AssertError extends Error { /** - * Constructs a new `AssertError` instance. + * Constructs a new instance. * @param message The error message. */ constructor(message?: string) { @@ -42,8 +48,7 @@ export class AssertError extends Error { /** * Sets the factory function used to generate assertion error messages. - * @param factory The factory function. - * @example + * * ```ts * import { is, setAssertMessageFactory } from "@core/unknownutil"; * @@ -59,6 +64,8 @@ export class AssertError extends Error { * } * }); * ``` + * + * @param factory The factory function. */ export function setAssertMessageFactory(factory: AssertMessageFactory): void { assertMessageFactory = factory; @@ -67,6 +74,8 @@ export function setAssertMessageFactory(factory: AssertMessageFactory): void { /** * Asserts that the given value satisfies the provided predicate. * + * It throws {@linkcode AssertError} if the value does not satisfy the predicate. + * * ```ts * import { assert, is } from "@core/unknownutil"; * @@ -78,8 +87,7 @@ export function setAssertMessageFactory(factory: AssertMessageFactory): void { * @param x The value to be asserted. * @param pred The predicate function to test the value against. * @param options Optional configuration for the assertion. - * @returns Nothing. The function has a return type of `asserts x is T` to help TypeScript narrow down the type of `x` after the assertion. - * @throws {AssertError} If the value does not satisfy the predicate. + * @returns The function has a return type of `asserts x is T` to help TypeScript narrow down the type of `x` after the assertion. */ export function assert( x: unknown, diff --git a/ensure.ts b/ensure.ts index d2de89a..8587c03 100644 --- a/ensure.ts +++ b/ensure.ts @@ -4,6 +4,8 @@ import { assert } from "./assert.ts"; /** * Ensures that the given value satisfies the provided predicate. * + * It throws {@linkcode AssertError} if the value does not satisfy the predicate. + * * ```ts * import { ensure, is } from "@core/unknownutil"; * @@ -15,7 +17,6 @@ import { assert } from "./assert.ts"; * @param pred The predicate function to test the value against. * @param options Optional configuration for the assertion. * @returns The input value `x`. - * @throws {AssertError} If the value does not satisfy the predicate. */ export function ensure( x: unknown, diff --git a/is/any.ts b/is/any.ts index ce352d3..a993a9d 100644 --- a/is/any.ts +++ b/is/any.ts @@ -6,7 +6,6 @@ * * const a = "a"; * if (is.Any(a)) { - * // a is narrowed to any * const _: any = a; * } * ``` diff --git a/is/array.ts b/is/array.ts index 4df306a..2132acd 100644 --- a/is/array.ts +++ b/is/array.ts @@ -6,7 +6,6 @@ * * const a: unknown = [0, 1, 2]; * if (is.Array(a)) { - * // a is narrowed to unknown[] * const _: unknown[] = a; * } * ``` diff --git a/is/array_of.ts b/is/array_of.ts index 8ae5a6f..e411282 100644 --- a/is/array_of.ts +++ b/is/array_of.ts @@ -13,7 +13,6 @@ import { isArray } from "./array.ts"; * const isMyType = is.ArrayOf(is.String); * const a: unknown = ["a", "b", "c"]; * if (isMyType(a)) { - * // a is narrowed to string[] * const _: string[] = a; * } * ``` diff --git a/is/async_function.ts b/is/async_function.ts index 562e526..e63ca61 100644 --- a/is/async_function.ts +++ b/is/async_function.ts @@ -8,7 +8,6 @@ const objectToString = Object.prototype.toString; * * const a: unknown = async () => {}; * if (is.AsyncFunction(a)) { - * // a is narrowed to (...args: unknown[]) => Promise * const _: ((...args: unknown[]) => Promise) = a; * } * ``` diff --git a/is/bigint.ts b/is/bigint.ts index 78529ed..60b68e0 100644 --- a/is/bigint.ts +++ b/is/bigint.ts @@ -6,7 +6,6 @@ * * const a: unknown = 0n; * if (is.Bigint(a)) { - * // a is narrowed to bigint * const _: bigint = a; * } * ``` diff --git a/is/boolean.ts b/is/boolean.ts index b9daf8d..4c36b54 100644 --- a/is/boolean.ts +++ b/is/boolean.ts @@ -6,7 +6,6 @@ * * const a: unknown = true; * if (is.Boolean(a)) { - * // a is narrowed to boolean * const _: boolean = a; * } * ``` diff --git a/is/function.ts b/is/function.ts index 155f47a..e42400f 100644 --- a/is/function.ts +++ b/is/function.ts @@ -6,7 +6,6 @@ * * const a: unknown = () => {}; * if (is.Function(a)) { - * // a is narrowed to (...args: unknown[]) => unknown * const _: ((...args: unknown[]) => unknown) = a; * } * ``` diff --git a/is/instance_of.ts b/is/instance_of.ts index 9c2c00a..0af259e 100644 --- a/is/instance_of.ts +++ b/is/instance_of.ts @@ -12,7 +12,6 @@ import type { Predicate } from "../type.ts"; * const isMyType = is.InstanceOf(Date); * const a: unknown = new Date(); * if (isMyType(a)) { - * // a is narrowed to Date * const _: Date = a; * } * ``` diff --git a/is/intersection_of.ts b/is/intersection_of.ts index 3246a7f..47af3bb 100644 --- a/is/intersection_of.ts +++ b/is/intersection_of.ts @@ -17,7 +17,6 @@ import { isObjectOf } from "./object_of.ts"; * ]); * const a: unknown = { a: 0, b: "a" }; * if (isMyType(a)) { - * // a is narrowed to { a: number } & { b: string } * const _: { a: number } & { b: string } = a; * } * ``` @@ -35,7 +34,6 @@ import { isObjectOf } from "./object_of.ts"; * const isMyType = is.IntersectionOf(preds); * const a: unknown = { a: 0, b: "a" }; * if (isMyType(a)) { - * // a is narrowed to { a: number } & { b: string } * const _: { a: number } & { b: string } = a; * } * ``` diff --git a/is/literal_of.ts b/is/literal_of.ts index 1d43d8b..b7d2474 100644 --- a/is/literal_of.ts +++ b/is/literal_of.ts @@ -12,7 +12,6 @@ import type { Predicate, Primitive } from "../type.ts"; * const isMyType = is.LiteralOf("hello"); * const a: unknown = "hello"; * if (isMyType(a)) { - * // a is narrowed to "hello" * const _: "hello" = a; * } * ``` diff --git a/is/literal_one_of.ts b/is/literal_one_of.ts index 5ae6f2a..a155311 100644 --- a/is/literal_one_of.ts +++ b/is/literal_one_of.ts @@ -12,7 +12,6 @@ import type { Predicate, Primitive } from "../type.ts"; * const isMyType = is.LiteralOneOf(["hello", "world"] as const); * const a: unknown = "hello"; * if (isMyType(a)) { - * // a is narrowed to "hello" | "world" * const _: "hello" | "world" = a; * } * ``` diff --git a/is/map.ts b/is/map.ts index 0c045b6..ed579d6 100644 --- a/is/map.ts +++ b/is/map.ts @@ -6,7 +6,6 @@ * * const a: unknown = new Map([["a", 0], ["b", 1]]); * if (is.Map(a)) { - * // a is narrowed to Map * const _: Map = a; * } * ``` diff --git a/is/map_of.ts b/is/map_of.ts index 3c4b4f1..f30f64f 100644 --- a/is/map_of.ts +++ b/is/map_of.ts @@ -13,7 +13,6 @@ import { isMap } from "./map.ts"; * const isMyType = is.MapOf(is.Number); * const a: unknown = new Map([["a", 0], ["b", 1]]); * if (isMyType(a)) { - * // a is narrowed to Map * const _: Map = a; * } * ``` @@ -26,7 +25,6 @@ import { isMap } from "./map.ts"; * const isMyType = is.MapOf(is.Number, is.String); * const a: unknown = new Map([["a", 0], ["b", 1]]); * if (isMyType(a)) { - * // a is narrowed to Map * const _: Map = a; * } * ``` diff --git a/is/mod.ts b/is/mod.ts index a29adcf..f99ba41 100644 --- a/is/mod.ts +++ b/is/mod.ts @@ -38,6 +38,9 @@ import { isUniformTupleOf } from "./uniform_tuple_of.ts"; import { isUnionOf } from "./union_of.ts"; import { isUnknown } from "./unknown.ts"; +/** + * Type predicate function collection. + */ export const is = { Any: isAny, Array: isArray, diff --git a/is/null.ts b/is/null.ts index 703fcd8..212a6b4 100644 --- a/is/null.ts +++ b/is/null.ts @@ -6,7 +6,6 @@ * * const a: unknown = null; * if (is.Null(a)) { - * // a is narrowed to null * const _: null = a; * } * ``` diff --git a/is/nullish.ts b/is/nullish.ts index 0b476c5..1c0468a 100644 --- a/is/nullish.ts +++ b/is/nullish.ts @@ -6,7 +6,6 @@ * * const a: unknown = null; * if (is.Nullish(a)) { - * // a is narrowed to null | undefined * const _: (null | undefined) = a; * } * ``` diff --git a/is/number.ts b/is/number.ts index 9f2f429..30a2443 100644 --- a/is/number.ts +++ b/is/number.ts @@ -6,7 +6,6 @@ * * const a: unknown = 0; * if (is.Number(a)) { - * // a is narrowed to number * const _: number = a; * } * ``` diff --git a/is/object_of.ts b/is/object_of.ts index 1fa87d2..f66b134 100644 --- a/is/object_of.ts +++ b/is/object_of.ts @@ -13,9 +13,11 @@ import type { Predicate } from "../type.ts"; * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * - * If `as.Optional` is specified in the predicate function, the property becomes optional. + * If {@linkcode asOptional} is specified in the predicate function, the property becomes optional. + * If {@linkcode asReadonly} is specified in the predicate function, the property becomes readonly. * * The number of keys of `x` must be greater than or equal to the number of keys of `predObj`. + * Use {@linkcode isStrictOf} if you want to check the exact number of keys. * * ```ts * import { as, is } from "@core/unknownutil"; @@ -24,12 +26,11 @@ import type { Predicate } from "../type.ts"; * a: is.Number, * b: is.String, * c: as.Optional(is.Boolean), + * d: as.Readonly(is.String), * }); - * const a: unknown = { a: 0, b: "a", other: "other" }; + * const a: unknown = { a: 0, b: "a", d: "d" }; * if (isMyType(a)) { - * // "other" key in `a` is ignored because of `options.strict` is `false`. - * // a is narrowed to { a: number; b: string; c?: boolean | undefined } - * const _: { a: number; b: string; c?: boolean | undefined } = a; + * const _: { a: number; b: string; c?: boolean | undefined, readonly d: string } = a; * } * ``` */ diff --git a/is/omit_of.ts b/is/omit_of.ts index e35e3a5..610ef3f 100644 --- a/is/omit_of.ts +++ b/is/omit_of.ts @@ -16,10 +16,8 @@ import { isObjectOf } from "./object_of.ts"; * b: is.String, * c: as.Optional(is.Boolean), * }), ["a", "c"]); - * const a: unknown = { a: 0, b: "a", other: "other" }; + * const a: unknown = { a: 0, b: "a" }; * if (isMyType(a)) { - * // The "a", "c", and "other" key in `a` is ignored. - * // 'a' is narrowed to { b: string } * const _: { b: string } = a; * } * ``` diff --git a/is/parameters_of.ts b/is/parameters_of.ts index 242f0ae..1ec786e 100644 --- a/is/parameters_of.ts +++ b/is/parameters_of.ts @@ -7,7 +7,7 @@ import { isArray } from "./array.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `ParametersOf` or `ParametersOf`. * - * This is similar to `TupleOf` or `TupleOf`, but if `as.Optional()` is specified at the trailing, the trailing elements becomes optional and makes variable-length tuple. + * This is similar to {@linkcode isTupleOf}, but if {@linkcode asOptional} is specified at the trailing, the trailing elements becomes optional and makes variable-length tuple. * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * @@ -24,7 +24,6 @@ import { isArray } from "./array.ts"; * ] as const); * const a: unknown = [0, undefined, "a"]; * if (isMyType(a)) { - * // a is narrowed to [number, string | undefined, boolean, number?, string?, boolean?] * const _: [number, string | undefined, boolean, number?, string?, boolean?] = a; * } * ``` @@ -44,7 +43,6 @@ import { isArray } from "./array.ts"; * ); * const a: unknown = [0, "a", true, 0, 1, 2]; * if (isMyType(a)) { - * // a is narrowed to [number, string?, boolean?, ...number[]] * const _: [number, string?, boolean?, ...number[]] = a; * } * ``` @@ -59,7 +57,6 @@ import { isArray } from "./array.ts"; * const isMyType = is.ParametersOf(predTup); * const a: unknown = [0, "a"]; * if (isMyType(a)) { - * // a is narrowed to [number, string, boolean?] * const _: [number, string, boolean?] = a; * } * ``` diff --git a/is/partial_of.ts b/is/partial_of.ts index e8157f0..00cd25e 100644 --- a/is/partial_of.ts +++ b/is/partial_of.ts @@ -19,8 +19,6 @@ import { isObjectOf } from "./object_of.ts"; * })); * const a: unknown = { a: undefined, other: "other" }; * if (isMyType(a)) { - * // The "other" key in `a` is ignored. - * // 'a' is narrowed to { a?: number | undefined; b?: string | undefined; c?: boolean | undefined } * const _: { a?: number | undefined; b?: string | undefined; c?: boolean | undefined } = a; * } * ``` diff --git a/is/pick_of.ts b/is/pick_of.ts index 18f6e82..0a37820 100644 --- a/is/pick_of.ts +++ b/is/pick_of.ts @@ -16,10 +16,8 @@ import { isObjectOf } from "./object_of.ts"; * b: is.String, * c: as.Optional(is.Boolean), * }), ["a", "c"]); - * const a: unknown = { a: 0, b: "a", other: "other" }; + * const a: unknown = { a: 0, b: "a" }; * if (isMyType(a)) { - * // The "b" and "other" key in `a` is ignored. - * // 'a' is narrowed to { a: number; c?: boolean | undefined } * const _: { a: number; c?: boolean | undefined } = a; * } * ``` diff --git a/is/primitive.ts b/is/primitive.ts index 501a94b..4714ba1 100644 --- a/is/primitive.ts +++ b/is/primitive.ts @@ -16,7 +16,6 @@ const primitiveSet: Set = new Set([ * * const a: unknown = 0; * if (is.Primitive(a)) { - * // a is narrowed to Primitive * const _: Primitive = a; * } * ``` diff --git a/is/readonly_of.ts b/is/readonly_of.ts index 70ee62f..dc798e3 100644 --- a/is/readonly_of.ts +++ b/is/readonly_of.ts @@ -17,7 +17,6 @@ import type { Predicate } from "../type.ts"; * })); * const a: unknown = { a: 0, b: "b", c: true }; * if (isMyType(a)) { - * // 'a' is narrowed to { readonly a: number; readonly b: string | undefined; readonly c: boolean } * const _: { readonly a: number; readonly b: string | undefined; readonly c: boolean } = a; * } * ``` diff --git a/is/record.ts b/is/record.ts index 9e263f0..fc691f7 100644 --- a/is/record.ts +++ b/is/record.ts @@ -2,19 +2,18 @@ * Return `true` if the type of `x` satisfies `Record`. * * Note that this function returns `true` for ambiguous instances like `Set`, `Map`, `Date`, `Promise`, etc. + * Use {@linkcode isRecordObject} instead if you want to check if `x` is an instance of `Object`. * * ```ts * import { is } from "@core/unknownutil"; * * const a: unknown = {"a": 0, "b": 1}; * if (is.Record(a)) { - * // a is narrowed to Record * const _: Record = a; * } * * const b: unknown = new Set(); * if (is.Record(b)) { - * // b is narrowed to Record * const _: Record = b; * } * ``` diff --git a/is/record_object.ts b/is/record_object.ts index 3442142..2281809 100644 --- a/is/record_object.ts +++ b/is/record_object.ts @@ -2,14 +2,13 @@ * Return `true` if the type of `x` is an object instance that satisfies `Record`. * * Note that this function check if the `x` is an instance of `Object`. - * Use `isRecord` instead if you want to check if the `x` satisfies the `Record` type. + * Use {@linkcode isRecord} instead if you want to check if the `x` satisfies the `Record` type. * * ```ts * import { is } from "@core/unknownutil"; * * const a: unknown = {"a": 0, "b": 1}; * if (is.RecordObject(a)) { - * // a is narrowed to Record * const _: Record = a; * } * diff --git a/is/record_object_of.ts b/is/record_object_of.ts index 627a64c..a8cd8e3 100644 --- a/is/record_object_of.ts +++ b/is/record_object_of.ts @@ -8,7 +8,7 @@ import { isRecordObject } from "./record_object.ts"; * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * Note that this function check if the `x` is an instance of `Object`. - * Use `isRecordOf` instead if you want to check if the `x` satisfies the `Record` type. + * Use {@linkcode isRecordOf} instead if you want to check if the `x` satisfies the `Record` type. * * ```ts * import { is } from "@core/unknownutil"; @@ -16,7 +16,6 @@ import { isRecordObject } from "./record_object.ts"; * const isMyType = is.RecordObjectOf(is.Number); * const a: unknown = {"a": 0, "b": 1}; * if (isMyType(a)) { - * // a is narrowed to Record * const _: Record = a; * } * ``` @@ -29,7 +28,6 @@ import { isRecordObject } from "./record_object.ts"; * const isMyType = is.RecordObjectOf(is.Number, is.String); * const a: unknown = {"a": 0, "b": 1}; * if (isMyType(a)) { - * // a is narrowed to Record * const _: Record = a; * } * ``` diff --git a/is/record_of.ts b/is/record_of.ts index 399d5f9..2d0d3d6 100644 --- a/is/record_of.ts +++ b/is/record_of.ts @@ -7,13 +7,15 @@ import { isRecord } from "./record.ts"; * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * + * Note that this function only check if the `x` satisfies the `Record` type. + * Use {@linkcode isRecordObjectOf} instead if you want to check if the `x` is an instance of `Object`. + * * ```ts * import { is } from "@core/unknownutil"; * * const isMyType = is.RecordOf(is.Number); * const a: unknown = {"a": 0, "b": 1}; * if (isMyType(a)) { - * // a is narrowed to Record * const _: Record = a; * } * ``` @@ -26,7 +28,6 @@ import { isRecord } from "./record.ts"; * const isMyType = is.RecordOf(is.Number, is.String); * const a: unknown = {"a": 0, "b": 1}; * if (isMyType(a)) { - * // a is narrowed to Record * const _: Record = a; * } * ``` diff --git a/is/required_of.ts b/is/required_of.ts index 6f3167e..33c2a71 100644 --- a/is/required_of.ts +++ b/is/required_of.ts @@ -19,7 +19,6 @@ import { isObjectOf } from "./object_of.ts"; * })); * const a: unknown = { a: 0, b: "b", c: true, other: "other" }; * if (isMyType(a)) { - * // 'a' is narrowed to { a: number; b: string | undefined; c: boolean } * const _: { a: number; b: string | undefined; c: boolean } = a; * } * ``` diff --git a/is/set.ts b/is/set.ts index 5e3bb8a..e7a3341 100644 --- a/is/set.ts +++ b/is/set.ts @@ -6,7 +6,6 @@ * * const a: unknown = new Set([0, 1, 2]); * if (is.Set(a)) { - * // a is narrowed to Set * const _: Set = a; * } * ``` diff --git a/is/set_of.ts b/is/set_of.ts index 47de82d..9084f24 100644 --- a/is/set_of.ts +++ b/is/set_of.ts @@ -13,7 +13,6 @@ import { isSet } from "./set.ts"; * const isMyType = is.SetOf(is.String); * const a: unknown = new Set(["a", "b", "c"]); * if (isMyType(a)) { - * // a is narrowed to Set * const _: Set = a; * } * ``` diff --git a/is/strict_of.ts b/is/strict_of.ts index d2f0d62..e091771 100644 --- a/is/strict_of.ts +++ b/is/strict_of.ts @@ -7,10 +7,6 @@ import type { Predicate } from "../type.ts"; * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * - * If `as.Optional` is specified in the predicate function, the property becomes optional. - * - * The number of keys of `x` must be equal to the number of non optional keys of `predObj`. - * * ```ts * import { as, is } from "@core/unknownutil"; * diff --git a/is/string.ts b/is/string.ts index 1725bef..ffa8770 100644 --- a/is/string.ts +++ b/is/string.ts @@ -6,7 +6,6 @@ * * const a: unknown = "a"; * if (is.String(a)) { - * // a is narrowed to string * const _: string = a; * } * ``` diff --git a/is/symbol.ts b/is/symbol.ts index bb35a85..d13f00a 100644 --- a/is/symbol.ts +++ b/is/symbol.ts @@ -6,7 +6,6 @@ * * const a: unknown = Symbol("symbol"); * if (is.Symbol(a)) { - * // a is narrowed to symbol * const _: symbol = a; * } * ``` diff --git a/is/sync_function.ts b/is/sync_function.ts index c6f7fa0..bf5bca4 100644 --- a/is/sync_function.ts +++ b/is/sync_function.ts @@ -8,7 +8,6 @@ const objectToString = Object.prototype.toString; * * const a: unknown = () => {}; * if (is.SyncFunction(a)) { - * // a is narrowed to (...args: unknown[]) => unknown * const _: ((...args: unknown[]) => unknown) = a; * } * ``` diff --git a/is/tuple_of.ts b/is/tuple_of.ts index 9c86adc..65ff8d5 100644 --- a/is/tuple_of.ts +++ b/is/tuple_of.ts @@ -13,7 +13,6 @@ import { isArray } from "./array.ts"; * const isMyType = is.TupleOf([is.Number, is.String, is.Boolean]); * const a: unknown = [0, "a", true]; * if (isMyType(a)) { - * // a is narrowed to [number, string, boolean] * const _: [number, string, boolean] = a; * } * ``` @@ -29,7 +28,6 @@ import { isArray } from "./array.ts"; * ); * const a: unknown = [0, "a", true, 0, 1, 2]; * if (isMyType(a)) { - * // a is narrowed to [number, string, boolean, ...number[]] * const _: [number, string, boolean, ...number[]] = a; * } * ``` @@ -44,7 +42,6 @@ import { isArray } from "./array.ts"; * const isMyType = is.TupleOf(predTup); * const a: unknown = [0, "a", true]; * if (isMyType(a)) { - * // a is narrowed to [number, string, boolean] * const _: [number, string, boolean] = a; * } * ``` diff --git a/is/undefined.ts b/is/undefined.ts index 4a8ecd9..38dbd1c 100644 --- a/is/undefined.ts +++ b/is/undefined.ts @@ -6,7 +6,6 @@ * * const a: unknown = undefined; * if (is.Undefined(a)) { - * // a is narrowed to undefined * const _: undefined = a; * } * ``` diff --git a/is/uniform_tuple_of.ts b/is/uniform_tuple_of.ts index fd3eca6..7c428a9 100644 --- a/is/uniform_tuple_of.ts +++ b/is/uniform_tuple_of.ts @@ -13,7 +13,6 @@ import { isArray } from "./array.ts"; * const isMyType = is.UniformTupleOf(5); * const a: unknown = [0, 1, 2, 3, 4]; * if (isMyType(a)) { - * // a is narrowed to [unknown, unknown, unknown, unknown, unknown] * const _: [unknown, unknown, unknown, unknown, unknown] = a; * } * ``` @@ -26,7 +25,6 @@ import { isArray } from "./array.ts"; * const isMyType = is.UniformTupleOf(5, is.Number); * const a: unknown = [0, 1, 2, 3, 4]; * if (isMyType(a)) { - * // a is narrowed to [number, number, number, number, number] * const _: [number, number, number, number, number] = a; * } * ``` diff --git a/is/union_of.ts b/is/union_of.ts index 787e781..907880c 100644 --- a/is/union_of.ts +++ b/is/union_of.ts @@ -13,7 +13,6 @@ import type { Predicate } from "../type.ts"; * const isMyType = is.UnionOf([is.Number, is.String, is.Boolean]); * const a: unknown = 0; * if (isMyType(a)) { - * // a is narrowed to number | string | boolean * const _: number | string | boolean = a; * } * ``` @@ -28,7 +27,6 @@ import type { Predicate } from "../type.ts"; * const isMyType = is.UnionOf(preds); * const a: unknown = 0; * if (isMyType(a)) { - * // a is narrowed to number | string | boolean * const _: number | string | boolean = a; * } * ``` diff --git a/is/unknown.ts b/is/unknown.ts index b9e5a6d..9a72915 100644 --- a/is/unknown.ts +++ b/is/unknown.ts @@ -6,7 +6,6 @@ * * const a = "a"; * if (is.Unknown(a)) { - * // a is narrowed to unknown * const _: unknown = a; * } * ``` diff --git a/type.ts b/type.ts index 3079f2e..e53d27e 100644 --- a/type.ts +++ b/type.ts @@ -1,10 +1,25 @@ /** * A type predicate function. + * + * ```ts + * import { as, is, type Predicate } from "@core/unknownutil"; + * + * type Person = { + * name: string; + * age: number; + * address?: string; + * }; + * const isPerson = is.ObjectOf({ + * name: is.String, + * age: is.Number, + * address: as.Optional(is.String), + * }) satisfies Predicate; + * ``` */ export type Predicate = (x: unknown) => x is T; /** - * A type predicated by Predicate. + * A type predicated by {@linkcode Predicate}. * * ```ts * import { as, is, type PredicateType } from "@core/unknownutil"; @@ -16,12 +31,12 @@ export type Predicate = (x: unknown) => x is T; * }); * * type Person = PredicateType; - * // Above is equivalent to the following type * // type Person = { * // name: string; * // age: number; - * // address: string | undefined; + * // address?: string; * // }; + * ``` */ export type PredicateType

= P extends Predicate ? T : never; From 90bba5afb159a24f34984501178118aa7f7992ee Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 06:15:26 +0900 Subject: [PATCH 16/22] :coffee: Export individual modules for JSR --- deno.jsonc | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++- mod_test.ts | 60 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 mod_test.ts diff --git a/deno.jsonc b/deno.jsonc index f669f47..4fdbc4c 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,11 +1,76 @@ { "name": "@core/unknownutil", "version": "0.0.0", - "exports": "./mod.ts", + "exports": { + ".": "./mod.ts", + "./type": "./type.ts", + "./assert": "./assert.ts", + "./ensure": "./ensure.ts", + "./maybe": "./maybe.ts", + "./as": "./as/mod.ts", + "./as/optional": "./as/optional.ts", + "./as/readonly": "./as/readonly.ts", + "./is": "./is/mod.ts", + "./is/any": "./is/any.ts", + "./is/array": "./is/array.ts", + "./is/array-of": "./is/array_of.ts", + "./is/async-function": "./is/async_function.ts", + "./is/bigint": "./is/bigint.ts", + "./is/boolean": "./is/boolean.ts", + "./is/function": "./is/function.ts", + "./is/instance-of": "./is/instance_of.ts", + "./is/intersection-of": "./is/intersection_of.ts", + "./is/literal-of": "./is/literal_of.ts", + "./is/literal-one-of": "./is/literal_one_of.ts", + "./is/map": "./is/map.ts", + "./is/map-of": "./is/map_of.ts", + "./is/mod": "./is/mod.ts", + "./is/null": "./is/null.ts", + "./is/nullish": "./is/nullish.ts", + "./is/number": "./is/number.ts", + "./is/object-of": "./is/object_of.ts", + "./is/omit-of": "./is/omit_of.ts", + "./is/parameters-of": "./is/parameters_of.ts", + "./is/partial-of": "./is/partial_of.ts", + "./is/pick-of": "./is/pick_of.ts", + "./is/primitive": "./is/primitive.ts", + "./is/readonly-of": "./is/readonly_of.ts", + "./is/record": "./is/record.ts", + "./is/record-object": "./is/record_object.ts", + "./is/record-object-of": "./is/record_object_of.ts", + "./is/record-of": "./is/record_of.ts", + "./is/required-of": "./is/required_of.ts", + "./is/set": "./is/set.ts", + "./is/set-of": "./is/set_of.ts", + "./is/strict-of": "./is/strict_of.ts", + "./is/string": "./is/string.ts", + "./is/symbol": "./is/symbol.ts", + "./is/sync-function": "./is/sync_function.ts", + "./is/tuple-of": "./is/tuple_of.ts", + "./is/undefined": "./is/undefined.ts", + "./is/uniform-tuple-of": "./is/uniform_tuple_of.ts", + "./is/union-of": "./is/union_of.ts", + "./is/unknown": "./is/unknown.ts" + }, + "exclude": [ + ".coverage/**" + ], + "publish": { + "include": [ + "**/*.ts", + "README.md", + "LICENSE" + ], + "exclude": [ + "**/*_test.ts", + ".*" + ] + }, "imports": { "@core/unknownutil": "./mod.ts", "@deno/dnt": "jsr:@deno/dnt@^0.41.1", "@std/assert": "jsr:@std/assert@^0.221.0", + "@std/jsonc": "jsr:@std/jsonc@^1.0.0", "@std/path": "jsr:@std/path@^1.0.2", "@std/testing": "jsr:@std/testing@^0.221.0" }, diff --git a/mod_test.ts b/mod_test.ts new file mode 100644 index 0000000..87a64f0 --- /dev/null +++ b/mod_test.ts @@ -0,0 +1,60 @@ +import { assertArrayIncludes } from "@std/assert"; +import { basename, globToRegExp } from "@std/path"; +import { parse } from "@std/jsonc"; +import { ensure } from "./ensure.ts"; +import { is } from "./is/mod.ts"; + +const excludes = [ + "mod.ts", + "*_test.ts", +]; + +Deno.test("JSR exports must have all `as` modules", async () => { + const moduleNames = await listModuleNames( + new URL(import.meta.resolve("./as")), + ); + const jsrExports = await loadJsrExports(); + assertArrayIncludes(Object.entries(jsrExports.exports), [ + ["./as", "./as/mod.ts"], + ...moduleNames.map(( + v, + ) => [`./as/${v.replaceAll("_", "-")}`, `./as/${v}.ts`]), + ]); +}); + +Deno.test("JSR exports must have all `is` modules", async () => { + const moduleNames = await listModuleNames( + new URL(import.meta.resolve("./is")), + ); + const jsrExports = await loadJsrExports(); + assertArrayIncludes(Object.entries(jsrExports.exports), [ + ["./is", "./is/mod.ts"], + ...moduleNames.map(( + v, + ) => [`./is/${v.replaceAll("_", "-")}`, `./is/${v}.ts`]), + ]); +}); + +async function listModuleNames(path: URL | string): Promise { + const patterns = excludes.map((p) => globToRegExp(p)); + const names: string[] = []; + for await (const entry of Deno.readDir(path)) { + if (!entry.isFile || !entry.name.endsWith(".ts")) continue; + if (patterns.some((p) => p.test(entry.name))) continue; + names.push(basename(entry.name, ".ts")); + } + return names; +} + +async function loadJsrExports(): Promise<{ exports: Record }> { + const text = await Deno.readTextFile( + new URL(import.meta.resolve("./deno.jsonc")), + ); + const json = ensure( + parse(text), + is.ObjectOf({ + exports: is.RecordOf(is.String, is.String), + }), + ); + return { exports: json.exports }; +} From 3153d2706bfba36cbfe251e35ffa28b7bbe85349 Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 06:17:45 +0900 Subject: [PATCH 17/22] :coffee: Rename `udd.yml` to `update.yml` --- .github/workflows/{udd.yml => update.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{udd.yml => update.yml} (100%) diff --git a/.github/workflows/udd.yml b/.github/workflows/update.yml similarity index 100% rename from .github/workflows/udd.yml rename to .github/workflows/update.yml From 22232579929dd1e914da956ea0c932268aa1564a Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 06:18:16 +0900 Subject: [PATCH 18/22] :coffee: Stop publishing to npm --- .github/workflows/npm.yml | 33 --------------------------------- .github/workflows/test.yml | 14 -------------- deno.jsonc | 1 - 3 files changed, 48 deletions(-) delete mode 100644 .github/workflows/npm.yml diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml deleted file mode 100644 index d79ec5f..0000000 --- a/.github/workflows/npm.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: npm - -env: - DENO_VERSION: 1.x - NODE_VERSION: 16.x - -on: - push: - tags: - - "v*" - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: denoland/setup-deno@v1 - with: - deno-version: ${{ env.DENO_VERSION }} - - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - registry-url: "https://registry.npmjs.org" - - name: Build - run: deno task build-npm - - name: Publish - run: | - cd npm - npm publish - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b2ed39..59080f6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,20 +59,6 @@ jobs: deno bench timeout-minutes: 5 - build-npm: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v1 - with: - deno-version: ${{ env.DENO_VERSION }} - - uses: actions/setup-node@v4 - with: - node-version: ${{ env.NODE_VERSION }} - registry-url: "https://registry.npmjs.org" - - name: Build - run: deno task build-npm - jsr-publish: runs-on: ubuntu-latest steps: diff --git a/deno.jsonc b/deno.jsonc index 4fdbc4c..885e967 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -75,7 +75,6 @@ "@std/testing": "jsr:@std/testing@^0.221.0" }, "tasks": { - "build-npm": "deno run -A .scripts/build_npm.ts $(git describe --tags --always --dirty)", "check": "deno check **/*.ts", "test": "deno test -A --doc --parallel --shuffle", "test:coverage": "deno task test --coverage=.coverage", From 68be2f1a056a6a0c274ae25ce87f586ac399ecee Mon Sep 17 00:00:00 2001 From: Alisue Date: Fri, 2 Aug 2024 06:19:07 +0900 Subject: [PATCH 19/22] :memo: Remove npm/deno.land badges --- README.md | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/README.md b/README.md index daefa40..e45b921 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,11 @@ # unknownutil [![jsr](https://jsr.io/badges/@core/unknownutil)](https://jsr.io/@core/unknownutil) -[![npm](https://img.shields.io/npm/v/unknownutil?logo=npm&logoColor=white)](https://www.npmjs.com/package/unknownutil) -[![denoland](https://img.shields.io/github/v/release/jsr-core/unknownutil?logo=deno&label=denoland)](https://deno.land/x/unknownutil) -[![deno doc](https://doc.deno.land/badge.svg)](https://doc.deno.land/https/deno.land/x/unknownutil/mod.ts) [![test](https://github.com/jsr-core/unknownutil/workflows/Test/badge.svg)](https://github.com/jsr-core/unknownutil/actions?query=workflow%3ATest) [![codecov](https://codecov.io/github/jsr-core/unknownutil/graph/badge.svg?token=pfbLRGU5AM)](https://codecov.io/github/jsr-core/unknownutil) A utility pack for handling `unknown` type. -[deno]: https://deno.land/ - -> [!WARNING] -> -> The package on [deno.land] and [npm] is deprecated. Use the package on -> [jsr.io] instead. -> -> ``` -> deno add @core/unknownutil -> npx jsr add @core/unknownutil -> ``` - -[deno.land]: https://deno.land/x/unknownutil -[npm]: https://www.npmjs.com/package/unknownutil [jsr.io]: https://jsr.io/@core/unknownutil ## Usage From 34770c0ec7db4af4827a3c2a346f53ada54b5d67 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 3 Aug 2024 03:16:58 +0900 Subject: [PATCH 20/22] :muscle: Improve docs/tests/types of `as` module --- _annotation.ts | 4 +- as/__snapshots__/optional_test.ts.snap | 11 - as/optional.ts | 29 ++- as/optional_test.ts | 268 +++++++++++-------------- as/readonly.ts | 27 +-- as/readonly_test.ts | 169 +++++++++++++--- is/object_of.ts | 20 +- is/parameters_of.ts | 4 +- 8 files changed, 314 insertions(+), 218 deletions(-) delete mode 100644 as/__snapshots__/optional_test.ts.snap diff --git a/_annotation.ts b/_annotation.ts index 87d0106..3d96d6f 100644 --- a/_annotation.ts +++ b/_annotation.ts @@ -32,14 +32,14 @@ export function hasAnnotation( /** * Annotation for optional. */ -export type WithOptional = { +export type AsOptional = { optional: Predicate; }; /** * Annotation for readonly. */ -export type WithReadonly = { +export type AsReadonly = { readonly: Predicate; }; diff --git a/as/__snapshots__/optional_test.ts.snap b/as/__snapshots__/optional_test.ts.snap deleted file mode 100644 index 0cbc43d..0000000 --- a/as/__snapshots__/optional_test.ts.snap +++ /dev/null @@ -1,11 +0,0 @@ -export const snapshot = {}; - -snapshot[`asOptional > returns properly named function 1`] = `"asOptional(isNumber)"`; - -snapshot[`asOptional > returns properly named function 2`] = `"asOptional(isNumber)"`; - -snapshot[`asUnoptional > returns properly named function 1`] = `"isNumber"`; - -snapshot[`asUnoptional > returns properly named function 2`] = `"isNumber"`; - -snapshot[`asUnoptional > returns properly named function 3`] = `"isNumber"`; diff --git a/as/optional.ts b/as/optional.ts index 50347fd..acadffd 100644 --- a/as/optional.ts +++ b/as/optional.ts @@ -2,13 +2,21 @@ import { rewriteName } from "../_funcutil.ts"; import type { Predicate, PredicateType } from "../type.ts"; import { annotate, + type AsOptional, hasAnnotation, unannotate, - type WithOptional, } from "../_annotation.ts"; /** - * Return an `Optional` annotated type predicate function that returns `true` if the type of `x` is `T` or `undefined`. + * Annotate the given predicate function as optional. + * + * Use this function to annotate a predicate function of `predObj` in {@linkcode isObjectOf}. + * + * Note that the annotated predicate function will return `true` if the type of `x` is `T` or `undefined`, indicating that + * this function is not just for annotation but it also changes the behavior of the predicate function. + * + * Use {@linkcode asUnoptional} to remove the annotation. + * Use {@linkcode hasOptional} to check if a predicate function has annotated with this function. * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * @@ -29,16 +37,16 @@ export function asOptional

>( ): & Extract>> & Predicate | undefined> - & WithOptional> { + & AsOptional> { if (hasAnnotation(pred, "optional")) { return pred as & Extract>> & Predicate | undefined> - & WithOptional>; + & AsOptional>; } return rewriteName( annotate( - (x: unknown) => x === undefined || pred(x), + (x) => x === undefined || pred(x), "optional", pred, ), @@ -47,11 +55,16 @@ export function asOptional

>( ) as unknown as & Extract>> & Predicate | undefined> - & WithOptional>; + & AsOptional>; } /** - * Return an `Optional` un-annotated type predicate function that returns `true` if the type of `x` is `T`. + * Unannotate the annotated predicate function with {@linkcode asOptional}. + * + * Use this function to unannotate a predicate function of `predObj` in {@linkcode isObjectOf}. + * + * Note that the annotated predicate function will return `true` if the type of `x` is `T`, indicating that + * this function is not just for annotation but it also changes the behavior of the predicate function. * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * @@ -89,6 +102,6 @@ export function hasOptional< : never, >( pred: P, -): pred is P & WithOptional { +): pred is P & AsOptional { return hasAnnotation(pred, "optional"); } diff --git a/as/optional_test.ts b/as/optional_test.ts index 8c94bf6..f1e73a4 100644 --- a/as/optional_test.ts +++ b/as/optional_test.ts @@ -1,183 +1,155 @@ -import { assertSnapshot } from "@std/testing/snapshot"; +import { assertEquals } from "@std/assert"; import { assertType } from "@std/testing/types"; import { type Equal, testWithExamples } from "../_testutil.ts"; import { is } from "../is/mod.ts"; import { asOptional, asUnoptional } from "./optional.ts"; Deno.test("asOptional", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, asOptional(is.Number).name); - // Nesting does nothing - await assertSnapshot(t, asOptional(asOptional(is.Number)).name); + await t.step("returns a property named predicate function", () => { + const pred = asOptional(is.Number); + assertEquals(typeof pred, "function"); + assertEquals(pred.name, "asOptional(isNumber)"); }); - await t.step("returns proper type predicate", () => { - const a: unknown = undefined; - if (is.ObjectOf({ a: asOptional(is.Number) })(a)) { - assertType>(true); - } - }); - await t.step("with is.String", async (t) => { - await testWithExamples(t, asOptional(is.String), { - validExamples: ["string", "undefined"], - }); + + await t.step("returns a property named predicate function (nested)", () => { + const pred = asOptional(asOptional(is.Number)); + assertEquals(typeof pred, "function"); + assertEquals(pred.name, "asOptional(isNumber)"); }); - await t.step("with is.Number", async (t) => { - await testWithExamples(t, asOptional(is.Number), { + + await t.step("returns a proper predicate function", async (t) => { + const pred = asOptional(is.Number); + await testWithExamples(t, pred, { validExamples: ["number", "undefined"], }); }); - await t.step("with is.Bigint", async (t) => { - await testWithExamples(t, asOptional(is.Bigint), { - validExamples: ["bigint", "undefined"], - }); - }); - await t.step("with is.Boolean", async (t) => { - await testWithExamples(t, asOptional(is.Boolean), { - validExamples: ["boolean", "undefined"], - }); - }); - await t.step("with is.Array", async (t) => { - await testWithExamples(t, asOptional(is.Array), { - validExamples: ["array", "undefined"], - }); - }); - await t.step("with is.Set", async (t) => { - await testWithExamples(t, asOptional(is.Set), { - validExamples: ["set", "undefined"], - }); - }); - await t.step("with is.RecordObject", async (t) => { - await testWithExamples(t, asOptional(is.RecordObject), { - validExamples: ["record", "undefined"], - }); - }); - await t.step("with is.Function", async (t) => { - await testWithExamples(t, asOptional(is.Function), { - validExamples: ["syncFunction", "asyncFunction", "undefined"], - }); - }); - await t.step("with is.SyncFunction", async (t) => { - await testWithExamples(t, asOptional(is.SyncFunction), { - validExamples: ["syncFunction", "undefined"], - }); - }); - await t.step("with is.AsyncFunction", async (t) => { - await testWithExamples(t, asOptional(is.AsyncFunction), { - validExamples: ["asyncFunction", "undefined"], + + await t.step("with isObjectOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: asOptional(is.Number), + c: asOptional(asOptional(is.Number)), }); - }); - await t.step("with is.Null", async (t) => { - await testWithExamples(t, asOptional(is.Null), { - validExamples: ["null", "undefined"], + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType>( + true, + ); + } }); }); - await t.step("with is.Undefined", async (t) => { - await testWithExamples(t, asOptional(is.Undefined), { - validExamples: ["undefined"], + + await t.step("with isTupleOf", async (t) => { + const pred = is.TupleOf([ + is.Number, + asOptional(is.Number), + asOptional(asOptional(is.Number)), + ]); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } }); }); - await t.step("with is.Symbol", async (t) => { - await testWithExamples(t, asOptional(is.Symbol), { - validExamples: ["symbol", "undefined"], + + await t.step("with isParametersOf", async (t) => { + const pred = is.ParametersOf( + [ + is.Number, + asOptional(is.Number), + asOptional(asOptional(is.Number)), + ] as const, + ); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } }); }); }); Deno.test("asUnoptional", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, asUnoptional(asOptional(is.Number)).name); - // Non optional does nothing - await assertSnapshot(t, asUnoptional(is.Number).name); - // Nesting does nothing - await assertSnapshot( - t, - asUnoptional(asUnoptional(asOptional(is.Number))).name, - ); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = undefined; - if (is.ObjectOf({ a: asUnoptional(asOptional(is.Number)) })(a)) { - assertType>(true); - } - if (asUnoptional(is.Number)(a)) { - assertType>(true); - } + await t.step("returns a property named predicate function", () => { + const pred = asUnoptional(asOptional(is.Number)); + assertEquals(typeof pred, "function"); + assertEquals(pred.name, "isNumber"); }); - await t.step("with is.String", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.String)), { - validExamples: ["string"], - }); + + await t.step("returns a property named predicate function (nested)", () => { + const pred = asUnoptional(asUnoptional(asOptional(is.Number))); + assertEquals(typeof pred, "function"); + assertEquals(pred.name, "isNumber"); }); - await t.step("with is.Number", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Number)), { + + await t.step("returns a proper predicate function", async (t) => { + const pred = asUnoptional(asOptional(is.Number)); + await testWithExamples(t, pred, { validExamples: ["number"], }); }); - await t.step("with is.Bigint", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Bigint)), { - validExamples: ["bigint"], - }); - }); - await t.step("with is.Boolean", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Boolean)), { - validExamples: ["boolean"], - }); - }); - await t.step("with is.Array", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Array)), { - validExamples: ["array"], + + await t.step("with isObjectOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: asUnoptional(asOptional(is.Number)), + c: asUnoptional(asUnoptional(asOptional(is.Number))), }); - }); - await t.step("with is.Set", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Set)), { - validExamples: ["set"], + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType>( + true, + ); + } }); }); - await t.step("with is.RecordObject", async (t) => { - await testWithExamples( - t, - asUnoptional(asOptional(is.RecordObject)), - { - validExamples: ["record"], - }, - ); - }); - await t.step("with is.Function", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Function)), { - validExamples: ["syncFunction", "asyncFunction"], + + await t.step("with isTupleOf", async (t) => { + const pred = is.TupleOf([ + is.Number, + asUnoptional(asOptional(is.Number)), + asUnoptional(asUnoptional(asOptional(is.Number))), + ]); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } }); }); - await t.step("with is.SyncFunction", async (t) => { - await testWithExamples( - t, - asUnoptional(asOptional(is.SyncFunction)), - { - validExamples: ["syncFunction"], - }, - ); - }); - await t.step("with is.AsyncFunction", async (t) => { - await testWithExamples( - t, - asUnoptional(asOptional(is.AsyncFunction)), - { - validExamples: ["asyncFunction"], - }, + + await t.step("with isParametersOf", async (t) => { + const pred = is.ParametersOf( + [ + is.Number, + asUnoptional(asOptional(is.Number)), + asUnoptional(asUnoptional(asOptional(is.Number))), + ] as const, ); - }); - await t.step("with is.Null", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Null)), { - validExamples: ["null"], - }); - }); - await t.step("with is.Undefined", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Undefined)), { - validExamples: ["undefined"], - }); - }); - await t.step("with is.Symbol", async (t) => { - await testWithExamples(t, asUnoptional(asOptional(is.Symbol)), { - validExamples: ["symbol"], + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } }); }); }); diff --git a/as/readonly.ts b/as/readonly.ts index 2bc01f5..ea0293f 100644 --- a/as/readonly.ts +++ b/as/readonly.ts @@ -2,13 +2,18 @@ import { rewriteName } from "../_funcutil.ts"; import type { Predicate } from "../type.ts"; import { annotate, + type AsReadonly, hasAnnotation, unannotate, - type WithReadonly, } from "../_annotation.ts"; /** - * Return an `Readonly` annotated type predicate function that returns `true` if the type of `x` is `T`. + * Annotate the given predicate function as readonly. + * + * Use this function to annotate a predicate function of `predObj` in {@linkcode isObjectOf}. + * + * Use {@linkcode asUnreadonly} to remove the annotation. + * Use {@linkcode hasReadonly} to check if a predicate function has annotated with this function. * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * @@ -26,23 +31,21 @@ import { */ export function asReadonly

>( pred: P, -): P & WithReadonly { +): P & AsReadonly { if (hasAnnotation(pred, "readonly")) { - return pred as P & WithReadonly; + return pred as P & AsReadonly; } return rewriteName( - annotate( - (x: unknown) => pred(x), - "readonly", - pred, - ), + annotate((x) => pred(x), "readonly", pred), "asReadonly", pred, - ) as unknown as P & WithReadonly; + ) as unknown as P & AsReadonly; } /** - * Return an `Readonly` un-annotated type predicate function that returns `true` if the type of `x` is `T`. + * Unannotate the annotated predicate function with {@linkcode asReadonly}. + * + * Use this function to unannotate a predicate function of `predObj` in {@linkcode isObjectOf}. * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * @@ -75,6 +78,6 @@ export function hasReadonly< P extends Predicate, >( pred: P, -): pred is P & WithReadonly { +): pred is P & AsReadonly { return hasAnnotation(pred, "readonly"); } diff --git a/as/readonly_test.ts b/as/readonly_test.ts index 666913b..2bc6dd2 100644 --- a/as/readonly_test.ts +++ b/as/readonly_test.ts @@ -1,38 +1,157 @@ -import { assertSnapshot } from "@std/testing/snapshot"; +import { assertEquals } from "@std/assert"; import { assertType } from "@std/testing/types"; -import type { Equal } from "../_testutil.ts"; +import { type Equal, testWithExamples } from "../_testutil.ts"; import { is } from "../is/mod.ts"; import { asReadonly, asUnreadonly } from "./readonly.ts"; Deno.test("asReadonly", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, asReadonly(is.Number).name); - // Nesting does nothing - await assertSnapshot(t, asReadonly(asReadonly(is.Number)).name); + await t.step("returns a property named predicate function", () => { + const pred = asReadonly(is.Number); + assertEquals(typeof pred, "function"); + assertEquals(pred.name, "asReadonly(isNumber)"); }); - await t.step("returns proper type predicate", () => { - const a: unknown = undefined; - if (is.ObjectOf({ a: asReadonly(is.Number) })(a)) { - assertType>(true); - } + + await t.step("returns a property named predicate function (nested)", () => { + const pred = asReadonly(asReadonly(is.Number)); + assertEquals(typeof pred, "function"); + assertEquals(pred.name, "asReadonly(isNumber)"); + }); + + await t.step("returns a proper predicate function", async (t) => { + const pred = asReadonly(is.Number); + await testWithExamples(t, pred, { + validExamples: ["number"], + }); + }); + + await t.step("with isObjectOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: asReadonly(is.Number), + c: asReadonly(asReadonly(is.Number)), + }); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } + }); + }); + + await t.step("with isTupleOf", async (t) => { + const pred = is.TupleOf([ + is.Number, + asReadonly(is.Number), + asReadonly(asReadonly(is.Number)), + ]); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } + }); + }); + + await t.step("with isParametersOf", async (t) => { + const pred = is.ParametersOf( + [ + is.Number, + asReadonly(is.Number), + asReadonly(asReadonly(is.Number)), + ] as const, + ); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } + }); }); }); Deno.test("asUnreadonly", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, asUnreadonly(asReadonly(is.Number)).name); - // Non optional does nothing - await assertSnapshot(t, asUnreadonly(is.Number).name); - // Nesting does nothing - await assertSnapshot( - t, - asUnreadonly(asUnreadonly(asReadonly(is.Number))).name, - ); + await t.step("returns a property named predicate function", () => { + const pred = asUnreadonly(asReadonly(is.Number)); + assertEquals(typeof pred, "function"); + assertEquals(pred.name, "isNumber"); }); - await t.step("returns proper type predicate", () => { - const a: unknown = undefined; - if (is.ObjectOf({ a: asUnreadonly(asReadonly(is.Number)) })(a)) { - assertType>(true); - } + + await t.step("returns a property named predicate function (nested)", () => { + const pred = asUnreadonly(asUnreadonly(asReadonly(is.Number))); + assertEquals(typeof pred, "function"); + assertEquals(pred.name, "isNumber"); + }); + + await t.step("returns a proper predicate function", async (t) => { + const pred = asUnreadonly(asReadonly(is.Number)); + await testWithExamples(t, pred, { + validExamples: ["number"], + }); + }); + + await t.step("with isObjectOf", async (t) => { + const pred = is.ObjectOf({ + a: is.Number, + b: asUnreadonly(asReadonly(is.Number)), + c: asUnreadonly(asUnreadonly(asReadonly(is.Number))), + }); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType>( + true, + ); + } + }); + }); + + await t.step("with isTupleOf", async (t) => { + const pred = is.TupleOf([ + is.Number, + asUnreadonly(asReadonly(is.Number)), + asUnreadonly(asUnreadonly(asReadonly(is.Number))), + ]); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } + }); + }); + + await t.step("with isParametersOf", async (t) => { + const pred = is.ParametersOf( + [ + is.Number, + asUnreadonly(asReadonly(is.Number)), + asUnreadonly(asUnreadonly(asReadonly(is.Number))), + ] as const, + ); + await t.step("predicated type is correct", () => { + const v: unknown = undefined; + if (pred(v)) { + assertType< + Equal + >( + true, + ); + } + }); }); }); diff --git a/is/object_of.ts b/is/object_of.ts index f66b134..4a931cb 100644 --- a/is/object_of.ts +++ b/is/object_of.ts @@ -2,9 +2,9 @@ import type { FlatType } from "../_typeutil.ts"; import { rewriteName } from "../_funcutil.ts"; import { annotate, - type WithOptional, + type AsOptional, + type AsReadonly, type WithPredObj, - type WithReadonly, } from "../_annotation.ts"; import type { Predicate } from "../type.ts"; @@ -63,32 +63,32 @@ type ObjectOf>> = FlatType< // Readonly/Optional & { readonly [ - K in keyof T as T[K] extends WithReadonly - ? T[K] extends WithOptional ? K : never + K in keyof T as T[K] extends AsReadonly + ? T[K] extends AsOptional ? K : never : never ]?: T[K] extends Predicate ? U : never; } // Readonly/Non optional & { readonly [ - K in keyof T as T[K] extends WithReadonly - ? T[K] extends WithOptional ? never : K + K in keyof T as T[K] extends AsReadonly + ? T[K] extends AsOptional ? never : K : never ]: T[K] extends Predicate ? U : never; } // Non readonly/Optional & { [ - K in keyof T as T[K] extends WithReadonly ? never - : T[K] extends WithOptional ? K + K in keyof T as T[K] extends AsReadonly ? never + : T[K] extends AsOptional ? K : never ]?: T[K] extends Predicate ? U : never; } // Non readonly/Non optional & { [ - K in keyof T as T[K] extends WithReadonly ? never - : T[K] extends WithOptional ? never + K in keyof T as T[K] extends AsReadonly ? never + : T[K] extends AsOptional ? never : K ]: T[K] extends Predicate ? U : never; } diff --git a/is/parameters_of.ts b/is/parameters_of.ts index 1ec786e..819d95c 100644 --- a/is/parameters_of.ts +++ b/is/parameters_of.ts @@ -1,5 +1,5 @@ import { rewriteName } from "../_funcutil.ts"; -import type { WithOptional } from "../_annotation.ts"; +import type { AsOptional } from "../_annotation.ts"; import { hasOptional } from "../as/optional.ts"; import type { Predicate, PredicateType } from "../type.ts"; import { isArray } from "./array.ts"; @@ -116,7 +116,7 @@ type ParametersOf = T extends readonly [] ? [] : T extends readonly [...infer P, infer R] // Tuple of predicates ? P extends Predicate[] - ? R extends Predicate & WithOptional + ? R extends Predicate & AsOptional // Last parameter is optional ? [...ParametersOf

, PredicateType?] // Last parameter is NOT optional From e902b0acef06a5f6e5f3755eabff70fef462c8da Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 3 Aug 2024 05:26:35 +0900 Subject: [PATCH 21/22] :muscle: Improve tests/docs/types of `is` module --- _annotation.ts | 7 +- is/__snapshots__/array_of_test.ts.snap | 5 - is/__snapshots__/instance_of_test.ts.snap | 5 - is/__snapshots__/intersection_of_test.ts.snap | 8 +- is/__snapshots__/literal_of_test.ts.snap | 15 -- is/__snapshots__/literal_one_of_test.ts.snap | 3 - is/__snapshots__/map_of_test.ts.snap | 9 - is/__snapshots__/object_of_test.ts.snap | 6 +- is/__snapshots__/omit_of_test.ts.snap | 4 +- is/__snapshots__/parameters_of_test.ts.snap | 16 +- is/__snapshots__/partial_of_test.ts.snap | 4 +- is/__snapshots__/pick_of_test.ts.snap | 4 +- is/__snapshots__/readonly_of_test.ts.snap | 16 +- .../record_object_of_test.ts.snap | 8 +- is/__snapshots__/record_of_test.ts.snap | 8 +- is/__snapshots__/required_of_test.ts.snap | 4 +- is/__snapshots__/set_of_test.ts.snap | 4 +- is/__snapshots__/strict_of_test.ts.snap | 6 +- is/__snapshots__/tuple_of_test.ts.snap | 12 +- .../uniform_tuple_of_test.ts.snap | 6 +- is/__snapshots__/union_of_test.ts.snap | 2 +- is/any.ts | 2 + is/array.ts | 2 + is/array_of.ts | 2 + is/array_of_test.ts | 30 +-- is/async_function.ts | 3 + is/function.ts | 3 + is/instance_of_test.ts | 42 ++--- is/intersection_of.ts | 17 +- is/intersection_of_test.ts | 171 ++++++------------ is/literal_of.ts | 3 + is/literal_of_test.ts | 91 +++++++--- is/literal_one_of.ts | 3 + is/literal_one_of_test.ts | 35 ++-- is/map.ts | 2 + is/map_of.ts | 5 +- is/map_of_test.ts | 60 +++--- is/null.ts | 3 + is/nullish.ts | 3 + is/object_of.ts | 12 +- is/object_of_test.ts | 60 +++--- is/omit_of.ts | 19 +- is/omit_of_test.ts | 36 ++-- is/parameters_of_test.ts | 94 +++++----- is/partial_of.ts | 19 +- is/partial_of_test.ts | 29 +-- is/pick_of.ts | 19 +- is/pick_of_test.ts | 33 ++-- is/readonly_of.ts | 6 +- is/readonly_of_test.ts | 105 ++++++----- is/record_object_of.ts | 4 +- is/record_object_of_test.ts | 51 +++--- is/record_of.ts | 4 +- is/record_of_test.ts | 51 +++--- is/required_of.ts | 19 +- is/required_of_test.ts | 28 +-- is/set.ts | 2 + is/set_of.ts | 2 + is/set_of_test.ts | 20 +- is/strict_of.ts | 21 ++- is/strict_of_test.ts | 64 +++---- is/tuple_of.ts | 2 + is/tuple_of_test.ts | 57 +++--- is/uniform_tuple_of.ts | 2 + is/uniform_tuple_of_test.ts | 30 +-- is/union_of.ts | 2 + is/union_of_test.ts | 34 ++-- is/unknown.ts | 2 + 68 files changed, 752 insertions(+), 704 deletions(-) delete mode 100644 is/__snapshots__/array_of_test.ts.snap delete mode 100644 is/__snapshots__/instance_of_test.ts.snap delete mode 100644 is/__snapshots__/literal_of_test.ts.snap delete mode 100644 is/__snapshots__/literal_one_of_test.ts.snap delete mode 100644 is/__snapshots__/map_of_test.ts.snap diff --git a/_annotation.ts b/_annotation.ts index 3d96d6f..73fa314 100644 --- a/_annotation.ts +++ b/_annotation.ts @@ -46,6 +46,11 @@ export type AsReadonly = { /** * Annotation for predObj. */ -export type WithPredObj>> = { +export type IsPredObj< + T extends Record> = Record< + PropertyKey, + Predicate + >, +> = { predObj: T; }; diff --git a/is/__snapshots__/array_of_test.ts.snap b/is/__snapshots__/array_of_test.ts.snap deleted file mode 100644 index be5103b..0000000 --- a/is/__snapshots__/array_of_test.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -export const snapshot = {}; - -snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; - -snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; diff --git a/is/__snapshots__/instance_of_test.ts.snap b/is/__snapshots__/instance_of_test.ts.snap deleted file mode 100644 index 29206ee..0000000 --- a/is/__snapshots__/instance_of_test.ts.snap +++ /dev/null @@ -1,5 +0,0 @@ -export const snapshot = {}; - -snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; - -snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; diff --git a/is/__snapshots__/intersection_of_test.ts.snap b/is/__snapshots__/intersection_of_test.ts.snap index 004da6e..56278e4 100644 --- a/is/__snapshots__/intersection_of_test.ts.snap +++ b/is/__snapshots__/intersection_of_test.ts.snap @@ -1,15 +1,15 @@ export const snapshot = {}; -snapshot[`isIntersectionOf > returns properly named function 1`] = ` +snapshot[`isIntersectionOf > returns properly named predicate function 1`] = `"isString"`; + +snapshot[`isIntersectionOf > returns properly named predicate function 2`] = ` "isObjectOf({ a: isNumber, b: isString })" `; -snapshot[`isIntersectionOf > returns properly named function 2`] = `"isString"`; - -snapshot[`isIntersectionOf > returns properly named function 3`] = ` +snapshot[`isIntersectionOf > returns properly named predicate function 3`] = ` "isIntersectionOf([ isFunction, isObjectOf({b: isString}) diff --git a/is/__snapshots__/literal_of_test.ts.snap b/is/__snapshots__/literal_of_test.ts.snap deleted file mode 100644 index e7e6c72..0000000 --- a/is/__snapshots__/literal_of_test.ts.snap +++ /dev/null @@ -1,15 +0,0 @@ -export const snapshot = {}; - -snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; - -snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; - -snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; - -snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; - -snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; - -snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; - -snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; diff --git a/is/__snapshots__/literal_one_of_test.ts.snap b/is/__snapshots__/literal_one_of_test.ts.snap deleted file mode 100644 index 0bdf47c..0000000 --- a/is/__snapshots__/literal_one_of_test.ts.snap +++ /dev/null @@ -1,3 +0,0 @@ -export const snapshot = {}; - -snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; diff --git a/is/__snapshots__/map_of_test.ts.snap b/is/__snapshots__/map_of_test.ts.snap deleted file mode 100644 index 1f0a965..0000000 --- a/is/__snapshots__/map_of_test.ts.snap +++ /dev/null @@ -1,9 +0,0 @@ -export const snapshot = {}; - -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; - -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; - -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; - -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; diff --git a/is/__snapshots__/object_of_test.ts.snap b/is/__snapshots__/object_of_test.ts.snap index c915e6d..7524ffc 100644 --- a/is/__snapshots__/object_of_test.ts.snap +++ b/is/__snapshots__/object_of_test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`isObjectOf > returns properly named function 1`] = ` +snapshot[`isObjectOf > returns properly named predicate function 1`] = ` "isObjectOf({ a: isNumber, b: isString, @@ -8,9 +8,9 @@ snapshot[`isObjectOf > returns properly named function 1`] = ` })" `; -snapshot[`isObjectOf > returns properly named function 2`] = `"isObjectOf({a: a})"`; +snapshot[`isObjectOf > returns properly named predicate function 2`] = `"isObjectOf({a: a})"`; -snapshot[`isObjectOf > returns properly named function 3`] = ` +snapshot[`isObjectOf > returns properly named predicate function 3`] = ` "isObjectOf({ a: isObjectOf({ b: isObjectOf({c: isBoolean}) diff --git a/is/__snapshots__/omit_of_test.ts.snap b/is/__snapshots__/omit_of_test.ts.snap index dad991f..19c8629 100644 --- a/is/__snapshots__/omit_of_test.ts.snap +++ b/is/__snapshots__/omit_of_test.ts.snap @@ -1,10 +1,10 @@ export const snapshot = {}; -snapshot[`isOmitOf > returns properly named function 1`] = ` +snapshot[`isOmitOf > returns properly named predicate function 1`] = ` "isObjectOf({ a: isNumber, c: isBoolean })" `; -snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; +snapshot[`isOmitOf > returns properly named predicate function 2`] = `"isObjectOf({a: isNumber})"`; diff --git a/is/__snapshots__/parameters_of_test.ts.snap b/is/__snapshots__/parameters_of_test.ts.snap index e305ef2..d1de1ce 100644 --- a/is/__snapshots__/parameters_of_test.ts.snap +++ b/is/__snapshots__/parameters_of_test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`isParametersOf > returns properly named function 1`] = ` +snapshot[`isParametersOf > returns properly named predicate function 1`] = ` "isParametersOf([ isNumber, isString, @@ -8,11 +8,11 @@ snapshot[`isParametersOf > returns properly named function 1`] = ` ])" `; -snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)])"`; +snapshot[`isParametersOf > returns properly named predicate function 2`] = `"isParametersOf([(anonymous)])"`; -snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([])"`; +snapshot[`isParametersOf > returns properly named predicate function 3`] = `"isParametersOf([])"`; -snapshot[`isParametersOf > returns properly named function 4`] = ` +snapshot[`isParametersOf > returns properly named predicate function 4`] = ` "isParametersOf([ isParametersOf([ isParametersOf([ @@ -24,7 +24,7 @@ snapshot[`isParametersOf > returns properly named function 4`] = ` ])" `; -snapshot[`isParametersOf > returns properly named function 1`] = ` +snapshot[`isParametersOf > returns properly named predicate function 1`] = ` "isParametersOf([ isNumber, isString, @@ -32,11 +32,11 @@ snapshot[`isParametersOf > returns properly named function 1`] = ` ], isArray)" `; -snapshot[`isParametersOf > returns properly named function 2`] = `"isParametersOf([(anonymous)], isArrayOf(isString))"`; +snapshot[`isParametersOf > returns properly named predicate function 2`] = `"isParametersOf([(anonymous)], isArrayOf(isString))"`; -snapshot[`isParametersOf > returns properly named function 3`] = `"isParametersOf([], isArrayOf(isString))"`; +snapshot[`isParametersOf > returns properly named predicate function 3`] = `"isParametersOf([], isArrayOf(isString))"`; -snapshot[`isParametersOf > returns properly named function 4`] = ` +snapshot[`isParametersOf > returns properly named predicate function 4`] = ` "isParametersOf([ isParametersOf([ isParametersOf([ diff --git a/is/__snapshots__/partial_of_test.ts.snap b/is/__snapshots__/partial_of_test.ts.snap index 315f88f..6f96fc2 100644 --- a/is/__snapshots__/partial_of_test.ts.snap +++ b/is/__snapshots__/partial_of_test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`isPartialOf > returns properly named function 1`] = ` +snapshot[`isPartialOf > returns properly named predicate function 1`] = ` "isObjectOf({ a: asOptional(isNumber), b: asOptional(isUnionOf([ @@ -12,7 +12,7 @@ snapshot[`isPartialOf > returns properly named function 1`] = ` })" `; -snapshot[`isPartialOf > returns properly named function 2`] = ` +snapshot[`isPartialOf > returns properly named predicate function 2`] = ` "isObjectOf({ a: asOptional(isNumber), b: asOptional(isUnionOf([ diff --git a/is/__snapshots__/pick_of_test.ts.snap b/is/__snapshots__/pick_of_test.ts.snap index f76fbb4..911dc0e 100644 --- a/is/__snapshots__/pick_of_test.ts.snap +++ b/is/__snapshots__/pick_of_test.ts.snap @@ -1,10 +1,10 @@ export const snapshot = {}; -snapshot[`isPickOf > returns properly named function 1`] = ` +snapshot[`isPickOf > returns properly named predicate function 1`] = ` "isObjectOf({ a: isNumber, c: isBoolean })" `; -snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; +snapshot[`isPickOf > returns properly named predicate function 2`] = `"isObjectOf({a: isNumber})"`; diff --git a/is/__snapshots__/readonly_of_test.ts.snap b/is/__snapshots__/readonly_of_test.ts.snap index 54c352d..308a72d 100644 --- a/is/__snapshots__/readonly_of_test.ts.snap +++ b/is/__snapshots__/readonly_of_test.ts.snap @@ -1,10 +1,10 @@ export const snapshot = {}; -snapshot[`isReadonlyOf > with isRecord > returns properly named function 1`] = `"isReadonlyOf(isRecord)"`; +snapshot[`isReadonlyOf > with isRecord > returns properly named predicate function 1`] = `"isReadonlyOf(isRecord)"`; -snapshot[`isReadonlyOf > with isRecord > returns properly named function 2`] = `"isReadonlyOf(isRecord)"`; +snapshot[`isReadonlyOf > with isRecord > returns properly named predicate function 2`] = `"isReadonlyOf(isRecord)"`; -snapshot[`isReadonlyOf > with isObjectOf > returns properly named function 1`] = ` +snapshot[`isReadonlyOf > with isObjectOf > returns properly named predicate function 1`] = ` "isReadonlyOf(isObjectOf({ a: isNumber, b: isUnionOf([ @@ -15,7 +15,7 @@ snapshot[`isReadonlyOf > with isObjectOf > returns properly named function 1` }))" `; -snapshot[`isReadonlyOf > with isObjectOf > returns properly named function 2`] = ` +snapshot[`isReadonlyOf > with isObjectOf > returns properly named predicate function 2`] = ` "isReadonlyOf(isObjectOf({ a: isNumber, b: isUnionOf([ @@ -26,7 +26,7 @@ snapshot[`isReadonlyOf > with isObjectOf > returns properly named function 2` }))" `; -snapshot[`isReadonlyOf > with isTupleOf > returns properly named function 1`] = ` +snapshot[`isReadonlyOf > with isTupleOf > returns properly named predicate function 1`] = ` "isReadonlyOf(isTupleOf([ isNumber, isString, @@ -34,7 +34,7 @@ snapshot[`isReadonlyOf > with isTupleOf > returns properly named function 1`] ]))" `; -snapshot[`isReadonlyOf > with isTupleOf > returns properly named function 2`] = ` +snapshot[`isReadonlyOf > with isTupleOf > returns properly named predicate function 2`] = ` "isReadonlyOf(isTupleOf([ isNumber, isString, @@ -42,6 +42,6 @@ snapshot[`isReadonlyOf > with isTupleOf > returns properly named function 2`] ]))" `; -snapshot[`isReadonlyOf > with isUniformTupleOf > returns properly named function 1`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`; +snapshot[`isReadonlyOf > with isUniformTupleOf > returns properly named predicate function 1`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`; -snapshot[`isReadonlyOf > with isUniformTupleOf > returns properly named function 2`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`; +snapshot[`isReadonlyOf > with isUniformTupleOf > returns properly named predicate function 2`] = `"isReadonlyOf(isUniformTupleOf(3, isNumber))"`; diff --git a/is/__snapshots__/record_object_of_test.ts.snap b/is/__snapshots__/record_object_of_test.ts.snap index d62a5d2..1254e9f 100644 --- a/is/__snapshots__/record_object_of_test.ts.snap +++ b/is/__snapshots__/record_object_of_test.ts.snap @@ -1,9 +1,9 @@ export const snapshot = {}; -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; +snapshot[`isRecordObjectOf > returns properly named predicate function 1`] = `"isRecordObjectOf(isNumber, undefined)"`; -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; +snapshot[`isRecordObjectOf > returns properly named predicate function 2`] = `"isRecordObjectOf((anonymous), undefined)"`; -snapshot[`isRecordObjectOf > returns properly named function 1`] = `"isRecordObjectOf(isNumber, isString)"`; +snapshot[`isRecordObjectOf > returns properly named predicate function 1`] = `"isRecordObjectOf(isNumber, isString)"`; -snapshot[`isRecordObjectOf > returns properly named function 2`] = `"isRecordObjectOf((anonymous), isString)"`; +snapshot[`isRecordObjectOf > returns properly named predicate function 2`] = `"isRecordObjectOf((anonymous), isString)"`; diff --git a/is/__snapshots__/record_of_test.ts.snap b/is/__snapshots__/record_of_test.ts.snap index 90eeadd..aa1da92 100644 --- a/is/__snapshots__/record_of_test.ts.snap +++ b/is/__snapshots__/record_of_test.ts.snap @@ -1,9 +1,9 @@ export const snapshot = {}; -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; +snapshot[`isRecordOf > returns properly named predicate function 1`] = `"isRecordOf(isNumber, undefined)"`; -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; +snapshot[`isRecordOf > returns properly named predicate function 2`] = `"isRecordOf((anonymous), undefined)"`; -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; +snapshot[`isRecordOf > returns properly named predicate function 1`] = `"isRecordOf(isNumber, isString)"`; -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; +snapshot[`isRecordOf > returns properly named predicate function 2`] = `"isRecordOf((anonymous), isString)"`; diff --git a/is/__snapshots__/required_of_test.ts.snap b/is/__snapshots__/required_of_test.ts.snap index a858a1a..7c30fac 100644 --- a/is/__snapshots__/required_of_test.ts.snap +++ b/is/__snapshots__/required_of_test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`isRequiredOf > returns properly named function 1`] = ` +snapshot[`isRequiredOf > returns properly named predicate function 1`] = ` "isObjectOf({ a: isNumber, b: isUnionOf([ @@ -12,7 +12,7 @@ snapshot[`isRequiredOf > returns properly named function 1`] = ` })" `; -snapshot[`isRequiredOf > returns properly named function 2`] = ` +snapshot[`isRequiredOf > returns properly named predicate function 2`] = ` "isObjectOf({ a: isNumber, b: isUnionOf([ diff --git a/is/__snapshots__/set_of_test.ts.snap b/is/__snapshots__/set_of_test.ts.snap index 66104bd..b1dc926 100644 --- a/is/__snapshots__/set_of_test.ts.snap +++ b/is/__snapshots__/set_of_test.ts.snap @@ -1,5 +1,5 @@ export const snapshot = {}; -snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; +snapshot[`isSetOf > returns properly named predicate function 1`] = `"isSetOf(isNumber)"`; -snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; +snapshot[`isSetOf > returns properly named predicate function 2`] = `"isSetOf((anonymous))"`; diff --git a/is/__snapshots__/strict_of_test.ts.snap b/is/__snapshots__/strict_of_test.ts.snap index 597e1e7..add9027 100644 --- a/is/__snapshots__/strict_of_test.ts.snap +++ b/is/__snapshots__/strict_of_test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`isStrictOf > returns properly named function 1`] = ` +snapshot[`isStrictOf > returns properly named predicate function 1`] = ` "isStrictOf(isObjectOf({ a: isNumber, b: isString, @@ -8,9 +8,9 @@ snapshot[`isStrictOf > returns properly named function 1`] = ` }))" `; -snapshot[`isStrictOf > returns properly named function 2`] = `"isStrictOf(isObjectOf({a: a}))"`; +snapshot[`isStrictOf > returns properly named predicate function 2`] = `"isStrictOf(isObjectOf({a: a}))"`; -snapshot[`isStrictOf > returns properly named function 3`] = ` +snapshot[`isStrictOf > returns properly named predicate function 3`] = ` "isStrictOf(isObjectOf({ a: isStrictOf(isObjectOf({ b: isStrictOf(isObjectOf({c: isBoolean})) diff --git a/is/__snapshots__/tuple_of_test.ts.snap b/is/__snapshots__/tuple_of_test.ts.snap index 5673452..2098ae2 100644 --- a/is/__snapshots__/tuple_of_test.ts.snap +++ b/is/__snapshots__/tuple_of_test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`isTupleOf > returns properly named function 1`] = ` +snapshot[`isTupleOf > returns properly named predicate function 1`] = ` "isTupleOf([ isNumber, isString, @@ -8,9 +8,9 @@ snapshot[`isTupleOf > returns properly named function 1`] = ` ])" `; -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; +snapshot[`isTupleOf > returns properly named predicate function 2`] = `"isTupleOf([(anonymous)])"`; -snapshot[`isTupleOf > returns properly named function 3`] = ` +snapshot[`isTupleOf > returns properly named predicate function 3`] = ` "isTupleOf([ isTupleOf([ isTupleOf([ @@ -22,7 +22,7 @@ snapshot[`isTupleOf > returns properly named function 3`] = ` ])" `; -snapshot[`isTupleOf > returns properly named function 1`] = ` +snapshot[`isTupleOf > returns properly named predicate function 1`] = ` "isTupleOf([ isNumber, isString, @@ -30,9 +30,9 @@ snapshot[`isTupleOf > returns properly named function 1`] = ` ], isArray)" `; -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; +snapshot[`isTupleOf > returns properly named predicate function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; -snapshot[`isTupleOf > returns properly named function 3`] = ` +snapshot[`isTupleOf > returns properly named predicate function 3`] = ` "isTupleOf([ isTupleOf([ isTupleOf([ diff --git a/is/__snapshots__/uniform_tuple_of_test.ts.snap b/is/__snapshots__/uniform_tuple_of_test.ts.snap index 8f13f88..5f32b7c 100644 --- a/is/__snapshots__/uniform_tuple_of_test.ts.snap +++ b/is/__snapshots__/uniform_tuple_of_test.ts.snap @@ -1,7 +1,7 @@ export const snapshot = {}; -snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, undefined)"`; +snapshot[`isUniformTupleOf > returns properly named predicate function 1`] = `"isUniformTupleOf(3, undefined)"`; -snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; +snapshot[`isUniformTupleOf > returns properly named predicate function 2`] = `"isUniformTupleOf(3, isNumber)"`; -snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; +snapshot[`isUniformTupleOf > returns properly named predicate function 3`] = `"isUniformTupleOf(3, (anonymous))"`; diff --git a/is/__snapshots__/union_of_test.ts.snap b/is/__snapshots__/union_of_test.ts.snap index cc9cb3a..db9ae3f 100644 --- a/is/__snapshots__/union_of_test.ts.snap +++ b/is/__snapshots__/union_of_test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`isUnionOf > returns properly named function 1`] = ` +snapshot[`isUnionOf > returns properly named predicate function 1`] = ` "isUnionOf([ isNumber, isString, diff --git a/is/any.ts b/is/any.ts index a993a9d..e3a177a 100644 --- a/is/any.ts +++ b/is/any.ts @@ -1,6 +1,8 @@ /** * Assume `x is `any` and always return `true` regardless of the type of `x`. * + * Use {@linkcode isUnknown} to assume that a value is `unknown`. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/array.ts b/is/array.ts index 2132acd..9921c1d 100644 --- a/is/array.ts +++ b/is/array.ts @@ -1,6 +1,8 @@ /** * Return `true` if the type of `x` is `unknown[]`. * + * Use {@linkcode isArrayOf} to check if the type of `x` is an array of `T`. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/array_of.ts b/is/array_of.ts index e411282..e9b91a7 100644 --- a/is/array_of.ts +++ b/is/array_of.ts @@ -5,6 +5,8 @@ import { isArray } from "./array.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `T[]`. * + * Use {@linkcode isArray} to check if the type of `x` is an array of `unknown`. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts diff --git a/is/array_of_test.ts b/is/array_of_test.ts index 0be24d5..2944a77 100644 --- a/is/array_of_test.ts +++ b/is/array_of_test.ts @@ -1,32 +1,36 @@ import { assertEquals } from "@std/assert"; -import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { isArrayOf } from "./array_of.ts"; Deno.test("isArrayOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isArrayOf(is.Number).name); - await assertSnapshot(t, isArrayOf((_x): _x is string => false).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = [0, 1, 2]; - if (isArrayOf(is.Number)(a)) { - assertType>(true); - } + await t.step("returns properly named predicate function", () => { + assertEquals(typeof isArrayOf(is.Number), "function"); + assertEquals(isArrayOf(is.Number).name, "isArrayOf(isNumber)"); + assertEquals( + isArrayOf((_x): _x is unknown => true).name, + "isArrayOf((anonymous))", + ); }); + await t.step("returns true on T array", () => { assertEquals(isArrayOf(is.Number)([0, 1, 2]), true); assertEquals(isArrayOf(is.String)(["a", "b", "c"]), true); assertEquals(isArrayOf(is.Boolean)([true, false, true]), true); }); + await t.step("returns false on non T array", () => { assertEquals(isArrayOf(is.String)([0, 1, 2]), false); assertEquals(isArrayOf(is.Number)(["a", "b", "c"]), false); assertEquals(isArrayOf(is.String)([true, false, true]), false); }); - await testWithExamples(t, isArrayOf((_: unknown): _ is unknown => true), { - excludeExamples: ["array"], + + await t.step("predicated type is correct", () => { + const a: unknown = undefined; + + if (isArrayOf(is.Number)(a)) { + assertType>(true); + } }); }); diff --git a/is/async_function.ts b/is/async_function.ts index e63ca61..78db3eb 100644 --- a/is/async_function.ts +++ b/is/async_function.ts @@ -3,6 +3,9 @@ const objectToString = Object.prototype.toString; /** * Return `true` if the type of `x` is `function` (async function). * + * Use {@linkcode isFunction} to check if the type of `x` is a function. + * Use {@linkcode isSyncFunction} to check if the type of `x` is a synchronous function. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/function.ts b/is/function.ts index e42400f..9b6cc71 100644 --- a/is/function.ts +++ b/is/function.ts @@ -1,6 +1,9 @@ /** * Return `true` if the type of `x` is `function`. * + * Use {@linkcode isSyncFunction} to check if the type of `x` is a synchronous function. + * Use {@linkcode isAsyncFunction} to check if the type of `x` is an asynchronous function. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/instance_of_test.ts b/is/instance_of_test.ts index 2d45d1b..a8fa635 100644 --- a/is/instance_of_test.ts +++ b/is/instance_of_test.ts @@ -1,47 +1,43 @@ import { assertEquals } from "@std/assert"; -import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { isInstanceOf } from "./instance_of.ts"; Deno.test("isInstanceOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isInstanceOf(Date).name); - await assertSnapshot(t, isInstanceOf(class {}).name); + await t.step("returns properly named predicate function", () => { + assertEquals(typeof isInstanceOf(Date), "function"); + assertEquals(isInstanceOf(Date).name, "isInstanceOf(Date)"); + assertEquals(isInstanceOf(class {}).name, "isInstanceOf((anonymous))"); }); + await t.step("returns true on T instance", () => { class Cls {} assertEquals(isInstanceOf(Cls)(new Cls()), true); assertEquals(isInstanceOf(Date)(new Date()), true); assertEquals(isInstanceOf(Promise)(new Promise(() => {})), true); }); - await t.step("with user-defined class", async (t) => { + + await t.step("returns false on non T instance", () => { class Cls {} - await testWithExamples(t, isInstanceOf(Cls)); - }); - await t.step("with Date", async (t) => { - await testWithExamples(t, isInstanceOf(Date), { validExamples: ["date"] }); + assertEquals(isInstanceOf(Date)(new Cls()), false); + assertEquals(isInstanceOf(Promise)(new Date()), false); + assertEquals(isInstanceOf(Cls)(new Promise(() => {})), false); }); - await t.step("with Promise", async (t) => { - await testWithExamples(t, isInstanceOf(Promise), { - validExamples: ["promise"], - }); - }); - await t.step("returns proper type predicate", () => { + + await t.step("predicated type is correct", () => { class Cls {} - const a: unknown = new Cls(); + const a: unknown = undefined; + if (isInstanceOf(Cls)(a)) { assertType>(true); } - const b: unknown = new Date(); - if (isInstanceOf(Date)(b)) { - assertType>(true); + if (isInstanceOf(Date)(a)) { + assertType>(true); } - const c: unknown = new Promise(() => {}); - if (isInstanceOf(Promise)(c)) { - assertType>>(true); + if (isInstanceOf(Promise)(a)) { + assertType>>(true); } }); }); diff --git a/is/intersection_of.ts b/is/intersection_of.ts index 47af3bb..0b748d8 100644 --- a/is/intersection_of.ts +++ b/is/intersection_of.ts @@ -1,11 +1,13 @@ import { rewriteName } from "../_funcutil.ts"; -import { hasAnnotation, type WithPredObj } from "../_annotation.ts"; +import { hasAnnotation, type IsPredObj } from "../_annotation.ts"; import type { Predicate } from "../type.ts"; import { isObjectOf } from "./object_of.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `IntersectionOf`. * + * Use {@linkcode isUnionOf} to check if the type of `x` is a union of `T`. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts @@ -40,17 +42,14 @@ import { isObjectOf } from "./object_of.ts"; */ export function isIntersectionOf< T extends readonly [ - Predicate & WithPredObj>>, - ...( - & Predicate - & WithPredObj>> - )[], + Predicate & IsPredObj, + ...(Predicate & IsPredObj)[], ], >( preds: T, ): & Predicate> - & WithPredObj>>; + & IsPredObj; export function isIntersectionOf< T extends readonly [Predicate], >( @@ -62,7 +61,7 @@ export function isIntersectionOf< preds: T, ): & Predicate> - & WithPredObj>>; + & IsPredObj; export function isIntersectionOf< T extends readonly [Predicate, ...Predicate[]], >( @@ -70,7 +69,7 @@ export function isIntersectionOf< ): | Predicate | Predicate> - & WithPredObj>> { + & IsPredObj { const predObj = {}; const restPreds = preds.filter((pred) => { if (!hasAnnotation(pred, "predObj")) { diff --git a/is/intersection_of_test.ts b/is/intersection_of_test.ts index 56ead45..a22c4f7 100644 --- a/is/intersection_of_test.ts +++ b/is/intersection_of_test.ts @@ -1,52 +1,53 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { isIntersectionOf } from "./intersection_of.ts"; Deno.test("isIntersectionOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isIntersectionOf([ - is.ObjectOf({ a: is.Number }), - is.ObjectOf({ b: is.String }), - ]).name, - "Should return `isObjectOf`, if all predicates that", - ); - await assertSnapshot( - t, - isIntersectionOf([ - is.String, - ]).name, - "Should return as is, if there is only one predicate", - ); - await assertSnapshot( - t, - isIntersectionOf([ - is.Function, - is.ObjectOf({ b: is.String }), - ]).name, - ); + const objPreds = [ + is.ObjectOf({ a: is.Number }), + is.ObjectOf({ b: is.String }), + ] as const; + const mixPreds = [ + is.Function, + is.ObjectOf({ b: is.String }), + ] as const; + + await t.step("returns properly named predicate function", async (t) => { + assertEquals(typeof isIntersectionOf([is.String]), "function"); + await assertSnapshot(t, isIntersectionOf([is.String]).name); + await assertSnapshot(t, isIntersectionOf(objPreds).name); + await assertSnapshot(t, isIntersectionOf(mixPreds).name); }); - await t.step("returns proper type predicate", () => { - const objPreds = [ - is.ObjectOf({ a: is.Number }), - is.ObjectOf({ b: is.String }), - ] as const; - const funcPreds = [ - is.Function, - is.ObjectOf({ b: is.String }), - ] as const; - const a: unknown = { a: 0, b: "a" }; - if (isIntersectionOf(objPreds)(a)) { - assertType>(true); - } + + await t.step("returns true on all of T", () => { + const f = Object.assign(() => void 0, { b: "a" }); + assertEquals(isIntersectionOf([is.String])("a"), true); + assertEquals(isIntersectionOf(objPreds)({ a: 0, b: "a" }), true); + assertEquals(isIntersectionOf(mixPreds)(f), true); + }); + + await t.step("returns false on non of T", () => { + const f = Object.assign(() => void 0, { b: "a" }); + assertEquals(isIntersectionOf(objPreds)("a"), false); + assertEquals(isIntersectionOf(mixPreds)({ a: 0, b: "a" }), false); + assertEquals(isIntersectionOf([is.String])(f), false); + }); + + await t.step("predicated type is correct", () => { + const a: unknown = undefined; + if (isIntersectionOf([is.String])(a)) { assertType>(true); } - if (isIntersectionOf(funcPreds)(a)) { + + if (isIntersectionOf(objPreds)(a)) { + assertType>(true); + } + + if (isIntersectionOf(mixPreds)(a)) { assertType< Equal< typeof a, @@ -56,80 +57,24 @@ Deno.test("isIntersectionOf", async (t) => { >(true); } }); - await t.step("returns true on all of T", () => { - const objPreds = [ - is.ObjectOf({ a: is.Number }), - is.ObjectOf({ b: is.String }), - ] as const; - const funcPreds = [ - is.Function, - is.ObjectOf({ b: is.String }), - ] as const; - const f = Object.assign(() => void 0, { b: "a" }); - assertEquals(isIntersectionOf(objPreds)({ a: 0, b: "a" }), true); - assertEquals(isIntersectionOf([is.String])("a"), true); - assertEquals(isIntersectionOf(funcPreds)(f), true); - }); - await t.step("returns false on non of T", async (t) => { - const preds = [ - is.ObjectOf({ a: is.Number }), - is.ObjectOf({ b: is.String }), - ] as const; - assertEquals( - isIntersectionOf(preds)({ a: 0, b: 0 }), - false, - "Some properties has wrong type", - ); - assertEquals( - isIntersectionOf(preds)({ a: 0 }), - false, - "Some properties does not exists", - ); - await testWithExamples(t, isIntersectionOf(preds), { - excludeExamples: ["record"], - }); - }); - await t.step("returns false on non of T with any predicates", async (t) => { - const preds = [ - is.Function, - is.ObjectOf({ b: is.String }), - ] as const; - assertEquals( - isIntersectionOf(preds)({ b: "a" }), - false, - "Not a function object", - ); - assertEquals( - isIntersectionOf(preds)(() => void 0), - false, - "Some properties does not exists in Function object", - ); - await testWithExamples(t, isIntersectionOf(preds), { - excludeExamples: ["record"], - }); - }); - await t.step("asdf", async (t) => { - const preds = [ - is.ObjectOf({ a: is.String }), - is.ObjectOf({ b: is.String }), - ] as const; - assertEquals( - is.PickOf(isIntersectionOf(preds), ["a"])({ a: "a" }), - true, - "All properties has correct type", - ); - assertEquals( - isIntersectionOf(preds)({ b: "a" }), - false, - "Not a function object", - ); - assertEquals( - isIntersectionOf(preds)(() => void 0), - false, - "Some properties does not exists in Function object", - ); - await testWithExamples(t, isIntersectionOf(preds), { - excludeExamples: ["record"], - }); + + await t.step("predicated type is correct (#68)", () => { + const a: unknown = undefined; + const pred = isIntersectionOf([ + is.ObjectOf({ id: is.String }), + is.UnionOf([ + is.ObjectOf({ result: is.String }), + is.ObjectOf({ error: is.String }), + ]), + ]); + + if (pred(a)) { + assertType< + Equal< + typeof a, + { id: string } & ({ result: string } | { error: string }) + > + >(true); + } }); }); diff --git a/is/literal_of.ts b/is/literal_of.ts index b7d2474..1388675 100644 --- a/is/literal_of.ts +++ b/is/literal_of.ts @@ -4,6 +4,9 @@ import type { Predicate, Primitive } from "../type.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is a literal type of `pred`. * + * Use {@linkcode isLiteral} to check if the type of `x` is a literal type. + * Use {@linkcode isLiteralOneOf} to check if the type of `x` is one of the literal type of `Primitive[]`. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts diff --git a/is/literal_of_test.ts b/is/literal_of_test.ts index 424d828..f8a9b75 100644 --- a/is/literal_of_test.ts +++ b/is/literal_of_test.ts @@ -1,32 +1,79 @@ import { assertEquals } from "@std/assert"; -import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { isLiteralOf } from "./literal_of.ts"; Deno.test("isLiteralOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isLiteralOf("hello").name); - await assertSnapshot(t, isLiteralOf(100).name); - await assertSnapshot(t, isLiteralOf(100n).name); - await assertSnapshot(t, isLiteralOf(true).name); - await assertSnapshot(t, isLiteralOf(null).name); - await assertSnapshot(t, isLiteralOf(undefined).name); - await assertSnapshot(t, isLiteralOf(Symbol("asdf")).name); - }); - await t.step("returns proper type predicate", () => { - const pred = "hello"; - const a: unknown = "hello"; - if (isLiteralOf(pred)(a)) { - assertType>(true); - } + await t.step("returns properly named predicate function", () => { + assertEquals(typeof isLiteralOf("hello"), "function"); + assertEquals(isLiteralOf("hello").name, `isLiteralOf("hello")`); + assertEquals(isLiteralOf(100).name, `isLiteralOf(100)`); + assertEquals(isLiteralOf(100n).name, `isLiteralOf(100n)`); + assertEquals(isLiteralOf(true).name, `isLiteralOf(true)`); + assertEquals(isLiteralOf(false).name, `isLiteralOf(false)`); + assertEquals(isLiteralOf(null).name, `isLiteralOf(null)`); + assertEquals(isLiteralOf(undefined).name, `isLiteralOf(undefined)`); + assertEquals(isLiteralOf(Symbol("asdf")).name, `isLiteralOf(Symbol(asdf))`); }); + await t.step("returns true on literal T", () => { - const pred = "hello"; - assertEquals(isLiteralOf(pred)("hello"), true); + const s = Symbol("asdf"); + assertEquals(isLiteralOf("hello")("hello"), true); + assertEquals(isLiteralOf(100)(100), true); + assertEquals(isLiteralOf(100n)(100n), true); + assertEquals(isLiteralOf(true)(true), true); + assertEquals(isLiteralOf(false)(false), true); + assertEquals(isLiteralOf(null)(null), true); + assertEquals(isLiteralOf(undefined)(undefined), true); + assertEquals(isLiteralOf(s)(s), true); + }); + + await t.step("returns false on non literal T", () => { + const s = Symbol("asdf"); + assertEquals(isLiteralOf(100)("hello"), false); + assertEquals(isLiteralOf(100n)(100), false); + assertEquals(isLiteralOf(true)(100n), false); + assertEquals(isLiteralOf(false)(true), false); + assertEquals(isLiteralOf(null)(false), false); + assertEquals(isLiteralOf(undefined)(null), false); + assertEquals(isLiteralOf(s)(undefined), false); + assertEquals(isLiteralOf("hello")(s), false); }); - await t.step("returns false on non literal T", async (t) => { - const pred = "hello"; - await testWithExamples(t, isLiteralOf(pred)); + + await t.step("predicated type is correct", () => { + const s = Symbol("asdf"); + const a: unknown = undefined; + + if (isLiteralOf("hello")(a)) { + assertType>(true); + } + + if (isLiteralOf(100)(a)) { + assertType>(true); + } + + if (isLiteralOf(100n)(a)) { + assertType>(true); + } + + if (isLiteralOf(true)(a)) { + assertType>(true); + } + + if (isLiteralOf(false)(a)) { + assertType>(true); + } + + if (isLiteralOf(null)(a)) { + assertType>(true); + } + + if (isLiteralOf(undefined)(a)) { + assertType>(true); + } + + if (isLiteralOf(s)(a)) { + assertType>(true); + } }); }); diff --git a/is/literal_one_of.ts b/is/literal_one_of.ts index a155311..b60547b 100644 --- a/is/literal_one_of.ts +++ b/is/literal_one_of.ts @@ -4,6 +4,9 @@ import type { Predicate, Primitive } from "../type.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is one of literal type in `preds`. * + * Use {@linkcode isLiteral} to check if the type of `x` is a literal type. + * Use {@linkcode isLiteralOf} to check if the type of `x` is a literal type of `Primitive`. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts diff --git a/is/literal_one_of_test.ts b/is/literal_one_of_test.ts index 93d77f7..ecea097 100644 --- a/is/literal_one_of_test.ts +++ b/is/literal_one_of_test.ts @@ -1,27 +1,32 @@ import { assertEquals } from "@std/assert"; -import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { isLiteralOneOf } from "./literal_one_of.ts"; Deno.test("isLiteralOneOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isLiteralOneOf(["hello", "world"]).name); + const literals = ["hello", "world"] as const; + await t.step("returns properly named predicate function", () => { + assertEquals(typeof isLiteralOneOf(literals), "function"); + assertEquals( + isLiteralOneOf(literals).name, + `isLiteralOneOf(["hello", "world"])`, + ); }); + + await t.step("returns true on literal T", () => { + assertEquals(isLiteralOneOf(literals)("hello"), true); + assertEquals(isLiteralOneOf(literals)("world"), true); + }); + + await t.step("returns false on non literal T", () => { + assertEquals(isLiteralOneOf(literals)(""), false); + assertEquals(isLiteralOneOf(literals)(100), false); + }); + await t.step("returns proper type predicate", () => { - const preds = ["hello", "world"] as const; const a: unknown = "hello"; - if (isLiteralOneOf(preds)(a)) { + if (isLiteralOneOf(literals)(a)) { assertType>(true); } }); - await t.step("returns true on literal T", () => { - const preds = ["hello", "world"] as const; - assertEquals(isLiteralOneOf(preds)("hello"), true); - assertEquals(isLiteralOneOf(preds)("world"), true); - }); - await t.step("returns false on non literal T", async (t) => { - const preds = ["hello", "world"] as const; - await testWithExamples(t, isLiteralOneOf(preds)); - }); }); diff --git a/is/map.ts b/is/map.ts index ed579d6..545a993 100644 --- a/is/map.ts +++ b/is/map.ts @@ -1,6 +1,8 @@ /** * Return `true` if the type of `x` is `Map`. * + * Use {@linkcode isMapOf} to check if the type of `x` is a map of `T`. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/map_of.ts b/is/map_of.ts index f30f64f..cf4bc68 100644 --- a/is/map_of.ts +++ b/is/map_of.ts @@ -5,6 +5,8 @@ import { isMap } from "./map.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `Map`. * + * Use {@linkcode isMap} to check if the type of `x` is a map of `unknown`. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts @@ -36,8 +38,7 @@ export function isMapOf( return rewriteName( (x: unknown): x is Map => { if (!isMap(x)) return false; - for (const entry of x.entries()) { - const [k, v] = entry; + for (const [k, v] of x.entries()) { if (!pred(v)) return false; if (predKey && !predKey(k)) return false; } diff --git a/is/map_of_test.ts b/is/map_of_test.ts index 1b6d60f..2ebb16f 100644 --- a/is/map_of_test.ts +++ b/is/map_of_test.ts @@ -1,66 +1,74 @@ import { assertEquals } from "@std/assert"; -import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { isMapOf } from "./map_of.ts"; Deno.test("isMapOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isMapOf(is.Number).name); - await assertSnapshot(t, isMapOf((_x): _x is string => false).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = new Map([["a", 0]]); - if (isMapOf(is.Number)(a)) { - assertType>>(true); - } + await t.step("returns properly named predicate function", () => { + assertEquals(typeof isMapOf(is.Number), "function"); + assertEquals(isMapOf(is.Number).name, "isMapOf(isNumber, undefined)"); + assertEquals( + isMapOf((_x): _x is unknown => true).name, + "isMapOf((anonymous), undefined)", + ); }); + await t.step("returns true on T map", () => { assertEquals(isMapOf(is.Number)(new Map([["a", 0]])), true); assertEquals(isMapOf(is.String)(new Map([["a", "a"]])), true); assertEquals(isMapOf(is.Boolean)(new Map([["a", true]])), true); }); + await t.step("returns false on non T map", () => { assertEquals(isMapOf(is.String)(new Map([["a", 0]])), false); assertEquals(isMapOf(is.Number)(new Map([["a", "a"]])), false); assertEquals(isMapOf(is.String)(new Map([["a", true]])), false); }); - await testWithExamples(t, isMapOf((_: unknown): _ is unknown => true), { - excludeExamples: ["map"], + + await t.step("returns proper type predicate", () => { + const a: unknown = undefined; + if (isMapOf(is.Number)(a)) { + assertType>>(true); + } }); }); Deno.test("isMapOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isMapOf(is.Number, is.String).name); - await assertSnapshot( - t, - isMapOf((_x): _x is string => false, is.String).name, + await t.step("returns properly named predicate function", () => { + assertEquals(typeof isMapOf(is.Number, is.String), "function"); + assertEquals( + isMapOf(is.Number, is.String).name, + "isMapOf(isNumber, isString)", + ); + assertEquals( + isMapOf((_x): _x is unknown => true, is.String).name, + "isMapOf((anonymous), isString)", ); }); - await t.step("returns proper type predicate", () => { - const a: unknown = new Map([["a", 0]]); - if (isMapOf(is.Number, is.String)(a)) { - assertType>>(true); - } - }); + await t.step("returns true on T map", () => { assertEquals(isMapOf(is.Number, is.String)(new Map([["a", 0]])), true); assertEquals(isMapOf(is.String, is.String)(new Map([["a", "a"]])), true); assertEquals(isMapOf(is.Boolean, is.String)(new Map([["a", true]])), true); }); + await t.step("returns false on non T map", () => { assertEquals(isMapOf(is.String, is.String)(new Map([["a", 0]])), false); assertEquals(isMapOf(is.Number, is.String)(new Map([["a", "a"]])), false); assertEquals(isMapOf(is.String, is.String)(new Map([["a", true]])), false); }); + await t.step("returns false on non K map", () => { assertEquals(isMapOf(is.Number, is.Number)(new Map([["a", 0]])), false); assertEquals(isMapOf(is.String, is.Number)(new Map([["a", "a"]])), false); assertEquals(isMapOf(is.Boolean, is.Number)(new Map([["a", true]])), false); }); - await testWithExamples(t, isMapOf((_: unknown): _ is unknown => true), { - excludeExamples: ["map"], + + await t.step("predicated type is correct", () => { + const a: unknown = new Map([["a", 0]]); + if (isMapOf(is.Number, is.String)(a)) { + assertType>>(true); + } }); }); diff --git a/is/null.ts b/is/null.ts index 212a6b4..a4d8a15 100644 --- a/is/null.ts +++ b/is/null.ts @@ -1,6 +1,9 @@ /** * Return `true` if the type of `x` is `null`. * + * Use {@linkcode isUndefined} to check if the type of `x` is `undefined`. + * Use {@linkcode isNullish} to check if the type of `x` is `null` or `undefined`. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/nullish.ts b/is/nullish.ts index 1c0468a..d462fba 100644 --- a/is/nullish.ts +++ b/is/nullish.ts @@ -1,6 +1,9 @@ /** * Return `true` if the type of `x` is `null` or `undefined`. * + * Use {@linkcode isNull} to check if the type of `x` is `null`. + * Use {@linkcode isUndefined} to check if the type of `x` is `undefined`. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/object_of.ts b/is/object_of.ts index 4a931cb..6a7633c 100644 --- a/is/object_of.ts +++ b/is/object_of.ts @@ -4,21 +4,23 @@ import { annotate, type AsOptional, type AsReadonly, - type WithPredObj, + type IsPredObj, } from "../_annotation.ts"; import type { Predicate } from "../type.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `ObjectOf`. * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * Use {@linkcode isRecordOf} if you want to check if the type of `x` is a record of `T`. * - * If {@linkcode asOptional} is specified in the predicate function, the property becomes optional. - * If {@linkcode asReadonly} is specified in the predicate function, the property becomes readonly. + * If {@linkcode asOptional} is specified in the predicate function in `predObj`, the property becomes optional. + * If {@linkcode asReadonly} is specified in the predicate function in `predObj`, the property becomes readonly. * * The number of keys of `x` must be greater than or equal to the number of keys of `predObj`. * Use {@linkcode isStrictOf} if you want to check the exact number of keys. * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * * ```ts * import { as, is } from "@core/unknownutil"; * @@ -36,7 +38,7 @@ import type { Predicate } from "../type.ts"; */ export function isObjectOf< T extends Record>, ->(predObj: T): Predicate> & WithPredObj { +>(predObj: T): Predicate> & IsPredObj { return annotate( rewriteName( (x: unknown): x is ObjectOf => { diff --git a/is/object_of_test.ts b/is/object_of_test.ts index e951ef6..7379377 100644 --- a/is/object_of_test.ts +++ b/is/object_of_test.ts @@ -1,64 +1,46 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { as } from "../as/mod.ts"; import { isObjectOf } from "./object_of.ts"; Deno.test("isObjectOf", async (t) => { - await t.step("returns properly named function", async (t) => { - await assertSnapshot( - t, - isObjectOf({ a: is.Number, b: is.String, c: is.Boolean }).name, - ); + const predObj = { + a: is.Number, + b: is.String, + c: is.Boolean, + }; + + await t.step("returns properly named predicate function", async (t) => { + assertEquals(typeof isObjectOf({}), "function"); + await assertSnapshot(t, isObjectOf(predObj).name); await assertSnapshot( t, isObjectOf({ a: (_x): _x is string => false }).name, ); - // Nested await assertSnapshot( t, isObjectOf({ a: isObjectOf({ b: isObjectOf({ c: is.Boolean }) }) }).name, ); }); - await t.step("returns proper type predicate", () => { - const predObj = { - a: is.Number, - b: is.String, - c: is.Boolean, - }; - const a: unknown = { a: 0, b: "a", c: true }; - if (isObjectOf(predObj)(a)) { - assertType>(true); - } - }); + await t.step("returns true on T object", () => { - const predObj = { - a: is.Number, - b: is.String, - c: is.Boolean, - }; assertEquals(isObjectOf(predObj)({ a: 0, b: "a", c: true }), true); assertEquals( isObjectOf(predObj)({ a: 0, b: "a", c: true, d: "ignored" }), true, - "Object have an unknown property", ); assertEquals( isObjectOf(predObj)( Object.assign(() => void 0, { a: 0, b: "a", c: true }), ), true, - "Function object", ); }); + await t.step("returns false on non T object", () => { - const predObj = { - a: is.Number, - b: is.String, - c: is.Boolean, - }; assertEquals(isObjectOf(predObj)("a"), false, "Value is not an object"); assertEquals( isObjectOf(predObj)({ a: 0, b: "a", c: "" }), @@ -76,6 +58,7 @@ Deno.test("isObjectOf", async (t) => { "Value is not an object", ); }); + await t.step("returns true on T instance", () => { const date = new Date(); const predObj = { @@ -83,8 +66,9 @@ Deno.test("isObjectOf", async (t) => { }; assertEquals(isObjectOf(predObj)(date), true, "Value is not an object"); }); - await t.step("with asOptional/asReadonly", () => { - const predObj = { + + await t.step("predicated type is correct", () => { + const predObj2 = { a: as.Readonly(as.Optional(is.String)), b: as.Optional(as.Readonly(is.String)), c: as.Readonly(is.String), @@ -92,8 +76,13 @@ Deno.test("isObjectOf", async (t) => { e: as.Unreadonly(as.Unoptional(as.Readonly(as.Optional(is.String)))), f: as.Unoptional(as.Unreadonly(as.Optional(as.Readonly(is.String)))), }; - const a: unknown = undefined; + const a: unknown = { a: 0, b: "a", c: true }; + if (isObjectOf(predObj)(a)) { + assertType>(true); + } + + if (isObjectOf(predObj2)(a)) { assertType< Equal< typeof a, @@ -109,9 +98,4 @@ Deno.test("isObjectOf", async (t) => { >(true); } }); - await testWithExamples( - t, - isObjectOf({ a: (_: unknown): _ is unknown => false }), - { excludeExamples: ["record"] }, - ); }); diff --git a/is/omit_of.ts b/is/omit_of.ts index 610ef3f..6ef562d 100644 --- a/is/omit_of.ts +++ b/is/omit_of.ts @@ -1,11 +1,22 @@ import type { FlatType } from "../_typeutil.ts"; -import type { WithPredObj } from "../_annotation.ts"; +import type { IsPredObj } from "../_annotation.ts"; import type { Predicate } from "../type.ts"; import { isObjectOf } from "./object_of.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `Omit, K>`. * + * It only supports modifing a predicate function annotated with `IsPredObj`, usually returned by the followings + * + * - {@linkcode isIntersectionOf} + * - {@linkcode isObjectOf} + * - {@linkcode isOmitOf} + * - {@linkcode isPartialOf} + * - {@linkcode isPickOf} + * - {@linkcode isReadonlyOf} + * - {@linkcode isRequiredOf} + * - {@linkcode isStrictOf} + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```typescript @@ -27,16 +38,16 @@ export function isOmitOf< P extends Record>, K extends keyof T, >( - pred: Predicate & WithPredObj

, + pred: Predicate & IsPredObj

, keys: K[], ): & Predicate>> - & WithPredObj

{ + & IsPredObj

{ const s = new Set(keys); const predObj = Object.fromEntries( Object.entries(pred.predObj).filter(([k]) => !s.has(k as K)), ); return isObjectOf(predObj) as & Predicate>> - & WithPredObj

; + & IsPredObj

; } diff --git a/is/omit_of_test.ts b/is/omit_of_test.ts index d87787e..107d4f2 100644 --- a/is/omit_of_test.ts +++ b/is/omit_of_test.ts @@ -11,24 +11,13 @@ Deno.test("isOmitOf", async (t) => { b: is.String, c: is.Boolean, }); - await t.step("returns properly named function", async (t) => { + + await t.step("returns properly named predicate function", async (t) => { + assertEquals(typeof isOmitOf(pred, ["b"]), "function"); await assertSnapshot(t, isOmitOf(pred, ["b"]).name); - // Nestable await assertSnapshot(t, isOmitOf(isOmitOf(pred, ["b"]), ["c"]).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isOmitOf(pred, ["b"])(a)) { - assertType< - Equal - >(true); - } - if (isOmitOf(isOmitOf(pred, ["b"]), ["c"])(a)) { - assertType< - Equal - >(true); - } - }); + await t.step("returns true on Omit object", () => { assertEquals( isOmitOf(pred, ["b"])({ a: 0, b: undefined, c: true }), @@ -36,6 +25,7 @@ Deno.test("isOmitOf", async (t) => { ); assertEquals(isOmitOf(pred, ["b", "c"])({ a: 0 }), true); }); + await t.step("returns false on non Omit object", () => { assertEquals( isOmitOf(pred, ["b"])("a"), @@ -48,4 +38,20 @@ Deno.test("isOmitOf", async (t) => { "Object have a different type property", ); }); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0, b: "a", c: true }; + + if (isOmitOf(pred, ["b"])(a)) { + assertType< + Equal + >(true); + } + + if (isOmitOf(isOmitOf(pred, ["b"]), ["c"])(a)) { + assertType< + Equal + >(true); + } + }); }); diff --git a/is/parameters_of_test.ts b/is/parameters_of_test.ts index f0dfffa..6bae4b2 100644 --- a/is/parameters_of_test.ts +++ b/is/parameters_of_test.ts @@ -1,13 +1,14 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { as } from "../as/mod.ts"; import { is } from "./mod.ts"; import { isParametersOf } from "./parameters_of.ts"; Deno.test("isParametersOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { + assertEquals(typeof isParametersOf([]), "function"); await assertSnapshot( t, isParametersOf([is.Number, is.String, as.Optional(is.Boolean)]).name, @@ -20,7 +21,6 @@ Deno.test("isParametersOf", async (t) => { t, isParametersOf([]).name, ); - // Nested await assertSnapshot( t, isParametersOf([ @@ -30,7 +30,20 @@ Deno.test("isParametersOf", async (t) => { ]).name, ); }); - await t.step("returns proper type predicate", () => { + + await t.step("returns true on T tuple", () => { + const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; + assertEquals(isParametersOf(predTup)([0, "a", true]), true); + assertEquals(isParametersOf(predTup)([0, "a"]), true); + }); + + await t.step("returns false on non T tuple", () => { + const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; + assertEquals(isParametersOf(predTup)([0, 1, 2]), false); + assertEquals(isParametersOf(predTup)([0, "a", true, 0]), false); + }); + + await t.step("predicated type is correct", () => { const predTup = [ as.Optional(is.Number), is.String, @@ -44,27 +57,11 @@ Deno.test("isParametersOf", async (t) => { >(true); } }); - await t.step("returns true on T tuple", () => { - const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; - assertEquals(isParametersOf(predTup)([0, "a", true]), true); - assertEquals(isParametersOf(predTup)([0, "a"]), true); - }); - await t.step("returns false on non T tuple", () => { - const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; - assertEquals(isParametersOf(predTup)([0, 1, 2]), false); - assertEquals(isParametersOf(predTup)([0, "a", true, 0]), false); - }); - await testWithExamples( - t, - isParametersOf([(_: unknown): _ is unknown => true]), - { - excludeExamples: ["array"], - }, - ); }); Deno.test("isParametersOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { + assertEquals(typeof isParametersOf([], is.Array), "function"); await assertSnapshot( t, isParametersOf([is.Number, is.String, as.Optional(is.Boolean)], is.Array) @@ -75,12 +72,10 @@ Deno.test("isParametersOf", async (t) => { isParametersOf([(_x): _x is string => false], is.ArrayOf(is.String)) .name, ); - // Empty await assertSnapshot( t, isParametersOf([], is.ArrayOf(is.String)).name, ); - // Nested await assertSnapshot( t, isParametersOf([ @@ -94,26 +89,7 @@ Deno.test("isParametersOf", async (t) => { ]).name, ); }); - await t.step("returns proper type predicate", () => { - const predTup = [ - as.Optional(is.Number), - is.String, - as.Optional(is.String), - as.Optional(is.Boolean), - ] as const; - const predElse = is.ArrayOf(is.Number); - const a: unknown = [0, "a"]; - if (isParametersOf(predTup, predElse)(a)) { - assertType< - Equal< - typeof a, - [number | undefined, string, string?, boolean?, ...number[]] - > - >( - true, - ); - } - }); + await t.step("returns true on T tuple", () => { const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; const predElse = is.ArrayOf(is.Number); @@ -127,6 +103,7 @@ Deno.test("isParametersOf", async (t) => { ); assertEquals(isParametersOf(predTup, predElse)([0, "a"]), true); }); + await t.step("returns false on non T tuple", () => { const predTup = [is.Number, is.String, as.Optional(is.Boolean)] as const; const predElse = is.ArrayOf(is.String); @@ -142,12 +119,25 @@ Deno.test("isParametersOf", async (t) => { ); assertEquals(isParametersOf(predTup, predElse)([0, "a", "b"]), false); }); - const predElse = is.Array; - await testWithExamples( - t, - isParametersOf([(_: unknown): _ is unknown => true], predElse), - { - excludeExamples: ["array"], - }, - ); + + await t.step("predicated type is correct", () => { + const predTup = [ + as.Optional(is.Number), + is.String, + as.Optional(is.String), + as.Optional(is.Boolean), + ] as const; + const predElse = is.ArrayOf(is.Number); + const a: unknown = [0, "a"]; + if (isParametersOf(predTup, predElse)(a)) { + assertType< + Equal< + typeof a, + [number | undefined, string, string?, boolean?, ...number[]] + > + >( + true, + ); + } + }); }); diff --git a/is/partial_of.ts b/is/partial_of.ts index 00cd25e..440db32 100644 --- a/is/partial_of.ts +++ b/is/partial_of.ts @@ -1,5 +1,5 @@ import type { FlatType } from "../_typeutil.ts"; -import type { WithPredObj } from "../_annotation.ts"; +import type { IsPredObj } from "../_annotation.ts"; import { asOptional } from "../as/optional.ts"; import type { Predicate } from "../type.ts"; import { isObjectOf } from "./object_of.ts"; @@ -7,6 +7,17 @@ import { isObjectOf } from "./object_of.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `Partial>`. * + * It only supports modifing a predicate function annotated with `IsPredObj`, usually returned by the followings + * + * - {@linkcode isIntersectionOf} + * - {@linkcode isObjectOf} + * - {@linkcode isOmitOf} + * - {@linkcode isPartialOf} + * - {@linkcode isPickOf} + * - {@linkcode isReadonlyOf} + * - {@linkcode isRequiredOf} + * - {@linkcode isStrictOf} + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```typescript @@ -27,14 +38,14 @@ export function isPartialOf< T extends Record, P extends Record>, >( - pred: Predicate & WithPredObj

, + pred: Predicate & IsPredObj

, ): & Predicate>> - & WithPredObj

{ + & IsPredObj

{ const predObj = Object.fromEntries( Object.entries(pred.predObj).map(([k, v]) => [k, asOptional(v)]), ) as Record>; return isObjectOf(predObj) as & Predicate>> - & WithPredObj

; + & IsPredObj

; } diff --git a/is/partial_of_test.ts b/is/partial_of_test.ts index 0de3e26..de0a584 100644 --- a/is/partial_of_test.ts +++ b/is/partial_of_test.ts @@ -13,22 +13,12 @@ Deno.test("isPartialOf", async (t) => { c: as.Optional(is.Boolean), d: as.Readonly(is.String), }); - await t.step("returns properly named function", async (t) => { + + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isPartialOf(pred).name); - // Nestable (no effect) await assertSnapshot(t, isPartialOf(isPartialOf(pred)).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isPartialOf(pred)(a)) { - assertType< - Equal< - typeof a, - Partial<{ a: number; b: string; c: boolean; readonly d: string }> - > - >(true); - } - }); + await t.step("returns true on Partial object", () => { assertEquals( isPartialOf(pred)({ a: undefined, b: undefined, c: undefined }), @@ -36,6 +26,7 @@ Deno.test("isPartialOf", async (t) => { ); assertEquals(isPartialOf(pred)({}), true); }); + await t.step("returns false on non Partial object", () => { assertEquals(isPartialOf(pred)("a"), false, "Value is not an object"); assertEquals( @@ -44,4 +35,16 @@ Deno.test("isPartialOf", async (t) => { "Object have a different type property", ); }); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isPartialOf(pred)(a)) { + assertType< + Equal< + typeof a, + Partial<{ a: number; b: string; c: boolean; readonly d: string }> + > + >(true); + } + }); }); diff --git a/is/pick_of.ts b/is/pick_of.ts index 0a37820..e605ceb 100644 --- a/is/pick_of.ts +++ b/is/pick_of.ts @@ -1,11 +1,22 @@ import type { FlatType } from "../_typeutil.ts"; -import type { WithPredObj } from "../_annotation.ts"; +import type { IsPredObj } from "../_annotation.ts"; import type { Predicate } from "../type.ts"; import { isObjectOf } from "./object_of.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `Pick, K>`. * + * It only supports modifing a predicate function annotated with `IsPredObj`, usually returned by the followings + * + * - {@linkcode isIntersectionOf} + * - {@linkcode isObjectOf} + * - {@linkcode isOmitOf} + * - {@linkcode isPartialOf} + * - {@linkcode isPickOf} + * - {@linkcode isReadonlyOf} + * - {@linkcode isRequiredOf} + * - {@linkcode isStrictOf} + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```typescript @@ -27,16 +38,16 @@ export function isPickOf< P extends Record>, K extends keyof T, >( - pred: Predicate & WithPredObj

, + pred: Predicate & IsPredObj

, keys: K[], ): & Predicate>> - & WithPredObj

{ + & IsPredObj

{ const s = new Set(keys); const predObj = Object.fromEntries( Object.entries(pred.predObj).filter(([k]) => s.has(k as K)), ); return isObjectOf(predObj) as & Predicate>> - & WithPredObj

; + & IsPredObj

; } diff --git a/is/pick_of_test.ts b/is/pick_of_test.ts index 5213e5a..9f79564 100644 --- a/is/pick_of_test.ts +++ b/is/pick_of_test.ts @@ -11,24 +11,12 @@ Deno.test("isPickOf", async (t) => { b: is.String, c: is.Boolean, }); - await t.step("returns properly named function", async (t) => { + + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isPickOf(pred, ["a", "c"]).name); - // Nestable await assertSnapshot(t, isPickOf(isPickOf(pred, ["a", "c"]), ["a"]).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isPickOf(pred, ["a", "c"])(a)) { - assertType< - Equal - >(true); - } - if (isPickOf(isPickOf(pred, ["a", "c"]), ["a"])(a)) { - assertType< - Equal - >(true); - } - }); + await t.step("returns true on Pick object", () => { assertEquals( isPickOf(pred, ["a", "c"])({ a: 0, b: undefined, c: true }), @@ -36,6 +24,7 @@ Deno.test("isPickOf", async (t) => { ); assertEquals(isPickOf(pred, ["a"])({ a: 0 }), true); }); + await t.step("returns false on non Pick object", () => { assertEquals( isPickOf(pred, ["a", "c"])("a"), @@ -48,4 +37,18 @@ Deno.test("isPickOf", async (t) => { "Object have a different type property", ); }); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isPickOf(pred, ["a", "c"])(a)) { + assertType< + Equal + >(true); + } + if (isPickOf(isPickOf(pred, ["a", "c"]), ["a"])(a)) { + assertType< + Equal + >(true); + } + }); }); diff --git a/is/readonly_of.ts b/is/readonly_of.ts index dc798e3..c9e2671 100644 --- a/is/readonly_of.ts +++ b/is/readonly_of.ts @@ -1,5 +1,5 @@ import { rewriteName } from "../_funcutil.ts"; -import type { WithPredObj } from "../_annotation.ts"; +import type { IsPredObj } from "../_annotation.ts"; import type { Predicate } from "../type.ts"; /** @@ -25,10 +25,10 @@ export function isReadonlyOf< T extends Record, P extends Record>, >( - pred: Predicate & WithPredObj

, + pred: Predicate & IsPredObj

, ): & Predicate> - & WithPredObj

; + & IsPredObj

; export function isReadonlyOf< T extends Record, >( diff --git a/is/readonly_of_test.ts b/is/readonly_of_test.ts index 473ab8a..0661542 100644 --- a/is/readonly_of_test.ts +++ b/is/readonly_of_test.ts @@ -9,22 +9,11 @@ import { isReadonlyOf } from "./readonly_of.ts"; Deno.test("isReadonlyOf", async (t) => { await t.step("with isRecord", async (t) => { const pred = is.Record; - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isReadonlyOf(pred).name); - // Nestable (no effect) await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isReadonlyOf(pred)(a)) { - assertType< - Equal< - typeof a, - Readonly> - > - >(true); - } - }); + await t.step("returns true on Readonly object", () => { assertEquals( isReadonlyOf(pred)({ a: 0, b: "b", c: true } as const), @@ -35,6 +24,7 @@ Deno.test("isReadonlyOf", async (t) => { true, ); }); + await t.step("returns false on non Readonly object", () => { assertEquals(isReadonlyOf(pred)("a"), false, "Value is not an object"); assertEquals( @@ -43,29 +33,31 @@ Deno.test("isReadonlyOf", async (t) => { "Object have a different type property", ); }); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isReadonlyOf(pred)(a)) { + assertType< + Equal< + typeof a, + Readonly> + > + >(true); + } + }); }); + await t.step("with isObjectOf", async (t) => { const pred = is.ObjectOf({ a: is.Number, b: is.UnionOf([is.String, is.Undefined]), c: as.Readonly(is.Boolean), }); - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isReadonlyOf(pred).name); - // Nestable (no effect) await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isReadonlyOf(pred)(a)) { - assertType< - Equal< - typeof a, - Readonly<{ a: number; b: string | undefined; c: boolean }> - > - >(true); - } - }); + await t.step("returns true on Readonly object", () => { assertEquals( isReadonlyOf(pred)({ a: 0, b: "b", c: true } as const), @@ -76,6 +68,7 @@ Deno.test("isReadonlyOf", async (t) => { true, ); }); + await t.step("returns false on non Readonly object", () => { assertEquals(isReadonlyOf(pred)("a"), false, "Value is not an object"); assertEquals( @@ -84,25 +77,27 @@ Deno.test("isReadonlyOf", async (t) => { "Object have a different type property", ); }); - }); - await t.step("with isTupleOf", async (t) => { - const pred = is.TupleOf([is.Number, is.String, as.Readonly(is.Boolean)]); - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isReadonlyOf(pred).name); - // Nestable (no effect) - await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); - }); - await t.step("returns proper type predicate", () => { - const a: unknown = []; + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0, b: "a", c: true }; if (isReadonlyOf(pred)(a)) { assertType< Equal< typeof a, - Readonly<[number, string, boolean]> + Readonly<{ a: number; b: string | undefined; c: boolean }> > >(true); } }); + }); + + await t.step("with isTupleOf", async (t) => { + const pred = is.TupleOf([is.Number, is.String, as.Readonly(is.Boolean)]); + await t.step("returns properly named predicate function", async (t) => { + await assertSnapshot(t, isReadonlyOf(pred).name); + await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); + }); + await t.step("returns true on Readonly object", () => { assertEquals( isReadonlyOf(pred)([0, "b", true] as const), @@ -113,6 +108,7 @@ Deno.test("isReadonlyOf", async (t) => { true, ); }); + await t.step("returns false on non Readonly object", () => { assertEquals(isReadonlyOf(pred)("a"), false, "Value is not an object"); assertEquals( @@ -121,25 +117,27 @@ Deno.test("isReadonlyOf", async (t) => { "Object have a different type property", ); }); - }); - await t.step("with isUniformTupleOf", async (t) => { - const pred = is.UniformTupleOf(3, is.Number); - await t.step("returns properly named function", async (t) => { - await assertSnapshot(t, isReadonlyOf(pred).name); - // Nestable (no effect) - await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); - }); - await t.step("returns proper type predicate", () => { + + await t.step("predicated type is correct", () => { const a: unknown = []; if (isReadonlyOf(pred)(a)) { assertType< Equal< typeof a, - Readonly<[number, number, number]> + Readonly<[number, string, boolean]> > >(true); } }); + }); + + await t.step("with isUniformTupleOf", async (t) => { + const pred = is.UniformTupleOf(3, is.Number); + await t.step("returns properly named predicate function", async (t) => { + await assertSnapshot(t, isReadonlyOf(pred).name); + await assertSnapshot(t, isReadonlyOf(isReadonlyOf(pred)).name); + }); + await t.step("returns true on Readonly object", () => { assertEquals( isReadonlyOf(pred)([0, 1, 2] as const), @@ -150,6 +148,7 @@ Deno.test("isReadonlyOf", async (t) => { true, ); }); + await t.step("returns false on non Readonly object", () => { assertEquals(isReadonlyOf(pred)("a"), false, "Value is not an object"); assertEquals( @@ -158,5 +157,17 @@ Deno.test("isReadonlyOf", async (t) => { "Object have a different type property", ); }); + + await t.step("predicated type is correct", () => { + const a: unknown = []; + if (isReadonlyOf(pred)(a)) { + assertType< + Equal< + typeof a, + Readonly<[number, number, number]> + > + >(true); + } + }); }); }); diff --git a/is/record_object_of.ts b/is/record_object_of.ts index a8cd8e3..dbd05f8 100644 --- a/is/record_object_of.ts +++ b/is/record_object_of.ts @@ -5,11 +5,11 @@ import { isRecordObject } from "./record_object.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is an Object instance that satisfies `Record`. * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * * Note that this function check if the `x` is an instance of `Object`. * Use {@linkcode isRecordOf} instead if you want to check if the `x` satisfies the `Record` type. * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/record_object_of_test.ts b/is/record_object_of_test.ts index b209932..596ea05 100644 --- a/is/record_object_of_test.ts +++ b/is/record_object_of_test.ts @@ -1,74 +1,67 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { isRecordObjectOf } from "./record_object_of.ts"; Deno.test("isRecordObjectOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isRecordObjectOf(is.Number).name); await assertSnapshot(t, isRecordObjectOf((_x): _x is string => false).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordObjectOf(is.Number)(a)) { - assertType>>(true); - } - }); + await t.step("returns true on T record", () => { assertEquals(isRecordObjectOf(is.Number)({ a: 0 }), true); assertEquals(isRecordObjectOf(is.String)({ a: "a" }), true); assertEquals(isRecordObjectOf(is.Boolean)({ a: true }), true); }); + await t.step("returns false on non T record", () => { assertEquals(isRecordObjectOf(is.String)({ a: 0 }), false); assertEquals(isRecordObjectOf(is.Number)({ a: "a" }), false); assertEquals(isRecordObjectOf(is.String)({ a: true }), false); }); - await testWithExamples( - t, - isRecordObjectOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record"], - }, - ); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0 }; + if (isRecordObjectOf(is.Number)(a)) { + assertType>>(true); + } + }); }); Deno.test("isRecordObjectOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isRecordObjectOf(is.Number, is.String).name); await assertSnapshot( t, isRecordObjectOf((_x): _x is string => false, is.String).name, ); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordObjectOf(is.Number, is.String)(a)) { - assertType>>(true); - } - }); + await t.step("returns true on T record", () => { assertEquals(isRecordObjectOf(is.Number, is.String)({ a: 0 }), true); assertEquals(isRecordObjectOf(is.String, is.String)({ a: "a" }), true); assertEquals(isRecordObjectOf(is.Boolean, is.String)({ a: true }), true); }); + await t.step("returns false on non T record", () => { assertEquals(isRecordObjectOf(is.String, is.String)({ a: 0 }), false); assertEquals(isRecordObjectOf(is.Number, is.String)({ a: "a" }), false); assertEquals(isRecordObjectOf(is.String, is.String)({ a: true }), false); }); + await t.step("returns false on non K record", () => { assertEquals(isRecordObjectOf(is.Number, is.Number)({ a: 0 }), false); assertEquals(isRecordObjectOf(is.String, is.Number)({ a: "a" }), false); assertEquals(isRecordObjectOf(is.Boolean, is.Number)({ a: true }), false); }); - await testWithExamples( - t, - isRecordObjectOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record"], - }, - ); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0 }; + if (isRecordObjectOf(is.Number, is.String)(a)) { + assertType>>(true); + } + }); }); diff --git a/is/record_of.ts b/is/record_of.ts index 2d0d3d6..4676f71 100644 --- a/is/record_of.ts +++ b/is/record_of.ts @@ -5,11 +5,11 @@ import { isRecord } from "./record.ts"; /** * Return a type predicate function that returns `true` if the type of `x` satisfies `Record`. * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * * Note that this function only check if the `x` satisfies the `Record` type. * Use {@linkcode isRecordObjectOf} instead if you want to check if the `x` is an instance of `Object`. * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/record_of_test.ts b/is/record_of_test.ts index 3c30c13..91cf7d6 100644 --- a/is/record_of_test.ts +++ b/is/record_of_test.ts @@ -1,74 +1,67 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { isRecordOf } from "./record_of.ts"; Deno.test("isRecordOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isRecordOf(is.Number).name); await assertSnapshot(t, isRecordOf((_x): _x is string => false).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordOf(is.Number)(a)) { - assertType>>(true); - } - }); + await t.step("returns true on T record", () => { assertEquals(isRecordOf(is.Number)({ a: 0 }), true); assertEquals(isRecordOf(is.String)({ a: "a" }), true); assertEquals(isRecordOf(is.Boolean)({ a: true }), true); }); + await t.step("returns false on non T record", () => { assertEquals(isRecordOf(is.String)({ a: 0 }), false); assertEquals(isRecordOf(is.Number)({ a: "a" }), false); assertEquals(isRecordOf(is.String)({ a: true }), false); }); - await testWithExamples( - t, - isRecordOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record", "date", "promise", "set", "map"], - }, - ); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0 }; + if (isRecordOf(is.Number)(a)) { + assertType>>(true); + } + }); }); Deno.test("isRecordOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isRecordOf(is.Number, is.String).name); await assertSnapshot( t, isRecordOf((_x): _x is string => false, is.String).name, ); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0 }; - if (isRecordOf(is.Number, is.String)(a)) { - assertType>>(true); - } - }); + await t.step("returns true on T record", () => { assertEquals(isRecordOf(is.Number, is.String)({ a: 0 }), true); assertEquals(isRecordOf(is.String, is.String)({ a: "a" }), true); assertEquals(isRecordOf(is.Boolean, is.String)({ a: true }), true); }); + await t.step("returns false on non T record", () => { assertEquals(isRecordOf(is.String, is.String)({ a: 0 }), false); assertEquals(isRecordOf(is.Number, is.String)({ a: "a" }), false); assertEquals(isRecordOf(is.String, is.String)({ a: true }), false); }); + await t.step("returns false on non K record", () => { assertEquals(isRecordOf(is.Number, is.Number)({ a: 0 }), false); assertEquals(isRecordOf(is.String, is.Number)({ a: "a" }), false); assertEquals(isRecordOf(is.Boolean, is.Number)({ a: true }), false); }); - await testWithExamples( - t, - isRecordOf((_: unknown): _ is unknown => true), - { - excludeExamples: ["record", "date", "promise", "set", "map"], - }, - ); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0 }; + if (isRecordOf(is.Number, is.String)(a)) { + assertType>>(true); + } + }); }); diff --git a/is/required_of.ts b/is/required_of.ts index 33c2a71..3b7fba7 100644 --- a/is/required_of.ts +++ b/is/required_of.ts @@ -1,5 +1,5 @@ import type { FlatType } from "../_typeutil.ts"; -import type { WithPredObj } from "../_annotation.ts"; +import type { IsPredObj } from "../_annotation.ts"; import { asUnoptional } from "../as/optional.ts"; import type { Predicate } from "../type.ts"; import { isObjectOf } from "./object_of.ts"; @@ -7,6 +7,17 @@ import { isObjectOf } from "./object_of.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `Required>`. * + * It only supports modifing a predicate function annotated with `IsPredObj`, usually returned by the followings + * + * - {@linkcode isIntersectionOf} + * - {@linkcode isObjectOf} + * - {@linkcode isOmitOf} + * - {@linkcode isPartialOf} + * - {@linkcode isPickOf} + * - {@linkcode isReadonlyOf} + * - {@linkcode isRequiredOf} + * - {@linkcode isStrictOf} + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```typescript @@ -27,14 +38,14 @@ export function isRequiredOf< T extends Record, P extends Record>, >( - pred: Predicate & WithPredObj

, + pred: Predicate & IsPredObj

, ): & Predicate>> - & WithPredObj

{ + & IsPredObj

{ const predObj = Object.fromEntries( Object.entries(pred.predObj).map(([k, v]) => [k, asUnoptional(v)]), ); return isObjectOf(predObj) as & Predicate>> - & WithPredObj

; + & IsPredObj

; } diff --git a/is/required_of_test.ts b/is/required_of_test.ts index 11e947b..16c00e2 100644 --- a/is/required_of_test.ts +++ b/is/required_of_test.ts @@ -13,22 +13,11 @@ Deno.test("isRequiredOf", async (t) => { c: as.Optional(is.Boolean), d: as.Readonly(is.String), }); - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isRequiredOf(pred).name); - // Nestable (no effect) await assertSnapshot(t, isRequiredOf(isRequiredOf(pred)).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = { a: 0, b: "a", c: true }; - if (isRequiredOf(pred)(a)) { - assertType< - Equal< - typeof a, - { a: number; b: string | undefined; c: boolean; readonly d: string } - > - >(true); - } - }); + await t.step("returns true on Required object", () => { assertEquals( isRequiredOf(pred)({ a: undefined, b: undefined, c: undefined }), @@ -41,6 +30,7 @@ Deno.test("isRequiredOf", async (t) => { "Object does not have required properties", ); }); + await t.step("returns false on non Required object", () => { assertEquals(isRequiredOf(pred)("a"), false, "Value is not an object"); assertEquals( @@ -49,4 +39,16 @@ Deno.test("isRequiredOf", async (t) => { "Object have a different type property", ); }); + + await t.step("predicated type is correct", () => { + const a: unknown = { a: 0, b: "a", c: true }; + if (isRequiredOf(pred)(a)) { + assertType< + Equal< + typeof a, + { a: number; b: string | undefined; c: boolean; readonly d: string } + > + >(true); + } + }); }); diff --git a/is/set.ts b/is/set.ts index e7a3341..fe5a308 100644 --- a/is/set.ts +++ b/is/set.ts @@ -1,6 +1,8 @@ /** * Return `true` if the type of `x` is `Set`. * + * Use {@linkcode isSetOf} to check if the type of `x` is a set of `T`. + * * ```ts * import { is } from "@core/unknownutil"; * diff --git a/is/set_of.ts b/is/set_of.ts index 9084f24..0f45ffb 100644 --- a/is/set_of.ts +++ b/is/set_of.ts @@ -5,6 +5,8 @@ import { isSet } from "./set.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `Set`. * + * Use {@linkcode isSet} to check if the type of `x` is a set of `unknown`. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts diff --git a/is/set_of_test.ts b/is/set_of_test.ts index 48aacd1..84498b4 100644 --- a/is/set_of_test.ts +++ b/is/set_of_test.ts @@ -1,32 +1,32 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { isSetOf } from "./set_of.ts"; Deno.test("isSetOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isSetOf(is.Number).name); await assertSnapshot(t, isSetOf((_x): _x is string => false).name); }); - await t.step("returns proper type predicate", () => { - const a: unknown = new Set([0, 1, 2]); - if (isSetOf(is.Number)(a)) { - assertType>>(true); - } - }); + await t.step("returns true on T set", () => { assertEquals(isSetOf(is.Number)(new Set([0, 1, 2])), true); assertEquals(isSetOf(is.String)(new Set(["a", "b", "c"])), true); assertEquals(isSetOf(is.Boolean)(new Set([true, false, true])), true); }); + await t.step("returns false on non T set", () => { assertEquals(isSetOf(is.String)(new Set([0, 1, 2])), false); assertEquals(isSetOf(is.Number)(new Set(["a", "b", "c"])), false); assertEquals(isSetOf(is.String)(new Set([true, false, true])), false); }); - await testWithExamples(t, isSetOf((_: unknown): _ is unknown => true), { - excludeExamples: ["set"], + + await t.step("predicated type is correct", () => { + const a: unknown = new Set([0, 1, 2]); + if (isSetOf(is.Number)(a)) { + assertType>>(true); + } }); }); diff --git a/is/strict_of.ts b/is/strict_of.ts index e091771..0181c2d 100644 --- a/is/strict_of.ts +++ b/is/strict_of.ts @@ -1,10 +1,21 @@ import { rewriteName } from "../_funcutil.ts"; -import type { WithPredObj } from "../_annotation.ts"; +import type { IsPredObj } from "../_annotation.ts"; import type { Predicate } from "../type.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is strictly follow the `ObjectOf`. * + * It only supports modifing a predicate function annotated with `IsPredObj`, usually returned by the followings + * + * - {@linkcode isIntersectionOf} + * - {@linkcode isObjectOf} + * - {@linkcode isOmitOf} + * - {@linkcode isPartialOf} + * - {@linkcode isPickOf} + * - {@linkcode isReadonlyOf} + * - {@linkcode isRequiredOf} + * - {@linkcode isStrictOf} + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts @@ -25,12 +36,10 @@ export function isStrictOf< T extends Record, P extends Record>, >( - pred: - & Predicate - & WithPredObj

, + pred: Predicate & IsPredObj

, ): & Predicate - & WithPredObj

{ + & IsPredObj

{ const s = new Set(Object.keys(pred.predObj)); return rewriteName( (x: unknown): x is T => { @@ -41,5 +50,5 @@ export function isStrictOf< }, "isStrictOf", pred, - ) as Predicate & WithPredObj

; + ) as Predicate & IsPredObj

; } diff --git a/is/strict_of_test.ts b/is/strict_of_test.ts index f542901..2b93f5a 100644 --- a/is/strict_of_test.ts +++ b/is/strict_of_test.ts @@ -1,13 +1,13 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { as } from "../as/mod.ts"; import { is } from "./mod.ts"; import { isStrictOf } from "./strict_of.ts"; Deno.test("isStrictOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot( t, isStrictOf(is.ObjectOf({ a: is.Number, b: is.String, c: is.Boolean })) @@ -17,7 +17,6 @@ Deno.test("isStrictOf", async (t) => { t, isStrictOf(is.ObjectOf({ a: (_x): _x is string => false })).name, ); - // Nested await assertSnapshot( t, isStrictOf( @@ -29,17 +28,7 @@ Deno.test("isStrictOf", async (t) => { ).name, ); }); - await t.step("returns proper type predicate", () => { - const predObj = { - a: is.Number, - b: is.String, - c: is.Boolean, - }; - const a: unknown = { a: 0, b: "a", c: true }; - if (isStrictOf(is.ObjectOf(predObj))(a)) { - assertType>(true); - } - }); + await t.step("returns true on T object", () => { const predObj = { a: is.Number, @@ -51,6 +40,7 @@ Deno.test("isStrictOf", async (t) => { true, ); }); + await t.step("returns false on non T object", () => { const predObj = { a: is.Number, @@ -78,25 +68,20 @@ Deno.test("isStrictOf", async (t) => { "Object have an unknown property", ); }); - await testWithExamples( - t, - isStrictOf(is.ObjectOf({ a: (_: unknown): _ is unknown => false })), - { excludeExamples: ["record"] }, - ); + + await t.step("predicated type is correct", () => { + const predObj = { + a: is.Number, + b: is.String, + c: is.Boolean, + }; + const a: unknown = { a: 0, b: "a", c: true }; + if (isStrictOf(is.ObjectOf(predObj))(a)) { + assertType>(true); + } + }); + await t.step("with optional properties", async (t) => { - await t.step("returns proper type predicate", () => { - const predObj = { - a: is.Number, - b: is.UnionOf([is.String, is.Undefined]), - c: as.Optional(is.Boolean), - }; - const a: unknown = { a: 0, b: "a" }; - if (isStrictOf(is.ObjectOf(predObj))(a)) { - assertType< - Equal - >(true); - } - }); await t.step("returns true on T object", () => { const predObj = { a: is.Number, @@ -118,6 +103,7 @@ Deno.test("isStrictOf", async (t) => { "Object has `undefined` as value of optional property", ); }); + await t.step("returns false on non T object", () => { const predObj = { a: is.Number, @@ -154,5 +140,19 @@ Deno.test("isStrictOf", async (t) => { "Object have the same number of properties but an unknown property exists", ); }); + + await t.step("predicated type is correct", () => { + const predObj = { + a: is.Number, + b: is.UnionOf([is.String, is.Undefined]), + c: as.Optional(is.Boolean), + }; + const a: unknown = { a: 0, b: "a" }; + if (isStrictOf(is.ObjectOf(predObj))(a)) { + assertType< + Equal + >(true); + } + }); }); }); diff --git a/is/tuple_of.ts b/is/tuple_of.ts index 65ff8d5..ac0d2f6 100644 --- a/is/tuple_of.ts +++ b/is/tuple_of.ts @@ -5,6 +5,8 @@ import { isArray } from "./array.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `TupleOf` or `TupleOf`. * + * Use {@linkcode isUniformTupleOf} to check if the type of `x` is a tuple of uniform types. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts diff --git a/is/tuple_of_test.ts b/is/tuple_of_test.ts index 8aabb02..c930390 100644 --- a/is/tuple_of_test.ts +++ b/is/tuple_of_test.ts @@ -1,12 +1,12 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { isTupleOf } from "./tuple_of.ts"; Deno.test("isTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot( t, isTupleOf([is.Number, is.String, is.Boolean]).name, @@ -15,37 +15,36 @@ Deno.test("isTupleOf", async (t) => { t, isTupleOf([(_x): _x is string => false]).name, ); - // Nested await assertSnapshot( t, isTupleOf([isTupleOf([isTupleOf([is.Number, is.String, is.Boolean])])]) .name, ); }); - await t.step("returns proper type predicate", () => { - const predTup = [is.Number, is.String, is.Boolean] as const; - const a: unknown = [0, "a", true]; - if (isTupleOf(predTup)(a)) { - assertType>(true); - } - }); + await t.step("returns true on T tuple", () => { const predTup = [is.Number, is.String, is.Boolean] as const; assertEquals(isTupleOf(predTup)([0, "a", true]), true); }); + await t.step("returns false on non T tuple", () => { const predTup = [is.Number, is.String, is.Boolean] as const; assertEquals(isTupleOf(predTup)([0, 1, 2]), false); assertEquals(isTupleOf(predTup)([0, "a"]), false); assertEquals(isTupleOf(predTup)([0, "a", true, 0]), false); }); - await testWithExamples(t, isTupleOf([(_: unknown): _ is unknown => true]), { - excludeExamples: ["array"], + + await t.step("predicated type is correct", () => { + const predTup = [is.Number, is.String, is.Boolean] as const; + const a: unknown = [0, "a", true]; + if (isTupleOf(predTup)(a)) { + assertType>(true); + } }); }); Deno.test("isTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot( t, isTupleOf([is.Number, is.String, is.Boolean], is.Array).name, @@ -55,7 +54,6 @@ Deno.test("isTupleOf", async (t) => { isTupleOf([(_x): _x is string => false], is.ArrayOf(is.String)) .name, ); - // Nested await assertSnapshot( t, isTupleOf([ @@ -66,21 +64,13 @@ Deno.test("isTupleOf", async (t) => { ]).name, ); }); - await t.step("returns proper type predicate", () => { - const predTup = [is.Number, is.String, is.Boolean] as const; - const predElse = is.ArrayOf(is.Number); - const a: unknown = [0, "a", true, 0, 1, 2]; - if (isTupleOf(predTup, predElse)(a)) { - assertType>( - true, - ); - } - }); + await t.step("returns true on T tuple", () => { const predTup = [is.Number, is.String, is.Boolean] as const; const predElse = is.ArrayOf(is.Number); assertEquals(isTupleOf(predTup, predElse)([0, "a", true, 0, 1, 2]), true); }); + await t.step("returns false on non T tuple", () => { const predTup = [is.Number, is.String, is.Boolean] as const; const predElse = is.ArrayOf(is.String); @@ -92,12 +82,15 @@ Deno.test("isTupleOf", async (t) => { ); assertEquals(isTupleOf(predTup, predElse)([0, "a", true, 0, 1, 2]), false); }); - const predElse = is.Array; - await testWithExamples( - t, - isTupleOf([(_: unknown): _ is unknown => true], predElse), - { - excludeExamples: ["array"], - }, - ); + + await t.step("predicated type is correct", () => { + const predTup = [is.Number, is.String, is.Boolean] as const; + const predElse = is.ArrayOf(is.Number); + const a: unknown = [0, "a", true, 0, 1, 2]; + if (isTupleOf(predTup, predElse)(a)) { + assertType>( + true, + ); + } + }); }); diff --git a/is/uniform_tuple_of.ts b/is/uniform_tuple_of.ts index 7c428a9..d007912 100644 --- a/is/uniform_tuple_of.ts +++ b/is/uniform_tuple_of.ts @@ -5,6 +5,8 @@ import { isArray } from "./array.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `UniformTupleOf`. * + * Use {@linkcode isTupleOf} to check if the type of `x` is a tuple of `T`. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts diff --git a/is/uniform_tuple_of_test.ts b/is/uniform_tuple_of_test.ts index 78ebbb3..3b4574f 100644 --- a/is/uniform_tuple_of_test.ts +++ b/is/uniform_tuple_of_test.ts @@ -1,12 +1,12 @@ import { assertEquals } from "@std/assert"; import { assertSnapshot } from "@std/testing/snapshot"; import { assertType } from "@std/testing/types"; -import { type Equal, testWithExamples } from "../_testutil.ts"; +import type { Equal } from "../_testutil.ts"; import { is } from "./mod.ts"; import { isUniformTupleOf } from "./uniform_tuple_of.ts"; Deno.test("isUniformTupleOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isUniformTupleOf(3).name); await assertSnapshot(t, isUniformTupleOf(3, is.Number).name); await assertSnapshot( @@ -14,7 +14,19 @@ Deno.test("isUniformTupleOf", async (t) => { isUniformTupleOf(3, (_x): _x is string => false).name, ); }); - await t.step("returns proper type predicate", () => { + + await t.step("returns true on mono-typed T tuple", () => { + assertEquals(isUniformTupleOf(3)([0, 1, 2]), true); + assertEquals(isUniformTupleOf(3, is.Number)([0, 1, 2]), true); + }); + + await t.step("returns false on non mono-typed T tuple", () => { + assertEquals(isUniformTupleOf(4)([0, 1, 2]), false); + assertEquals(isUniformTupleOf(4)([0, 1, 2, 3, 4]), false); + assertEquals(isUniformTupleOf(3, is.Number)(["a", "b", "c"]), false); + }); + + await t.step("predicated type is correct", () => { const a: unknown = [0, 1, 2, 3, 4]; if (isUniformTupleOf(5)(a)) { assertType< @@ -28,16 +40,4 @@ Deno.test("isUniformTupleOf", async (t) => { ); } }); - await t.step("returns true on mono-typed T tuple", () => { - assertEquals(isUniformTupleOf(3)([0, 1, 2]), true); - assertEquals(isUniformTupleOf(3, is.Number)([0, 1, 2]), true); - }); - await t.step("returns false on non mono-typed T tuple", () => { - assertEquals(isUniformTupleOf(4)([0, 1, 2]), false); - assertEquals(isUniformTupleOf(4)([0, 1, 2, 3, 4]), false); - assertEquals(isUniformTupleOf(3, is.Number)(["a", "b", "c"]), false); - }); - await testWithExamples(t, isUniformTupleOf(4), { - excludeExamples: ["array"], - }); }); diff --git a/is/union_of.ts b/is/union_of.ts index 907880c..9e898ec 100644 --- a/is/union_of.ts +++ b/is/union_of.ts @@ -5,6 +5,8 @@ import type { Predicate } from "../type.ts"; /** * Return a type predicate function that returns `true` if the type of `x` is `UnionOf`. * + * Use {@linkcode isIntersectionOf} to check if the type of `x` is an intersection of `T`. + * * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. * * ```ts diff --git a/is/union_of_test.ts b/is/union_of_test.ts index e159b78..8d54953 100644 --- a/is/union_of_test.ts +++ b/is/union_of_test.ts @@ -7,17 +7,33 @@ import { is } from "./mod.ts"; import { isUnionOf } from "./union_of.ts"; Deno.test("isUnionOf", async (t) => { - await t.step("returns properly named function", async (t) => { + await t.step("returns properly named predicate function", async (t) => { await assertSnapshot(t, isUnionOf([is.Number, is.String, is.Boolean]).name); }); - await t.step("returns proper type predicate", () => { + + await t.step("returns true on one of T", () => { + const preds = [is.Number, is.String, is.Boolean] as const; + assertEquals(isUnionOf(preds)(0), true); + assertEquals(isUnionOf(preds)("a"), true); + assertEquals(isUnionOf(preds)(true), true); + }); + + await t.step("returns false on non of T", async (t) => { + const preds = [is.Number, is.String, is.Boolean] as const; + await testWithExamples(t, isUnionOf(preds), { + excludeExamples: ["number", "string", "boolean"], + }); + }); + + await t.step("predicated type is correct", () => { const preds = [is.Number, is.String, is.Boolean] as const; const a: unknown = [0, "a", true]; if (isUnionOf(preds)(a)) { assertType>(true); } }); - await t.step("returns proper type predicate (#49)", () => { + + await t.step("predicated type is correct (#49)", () => { const isFoo = is.ObjectOf({ foo: is.String }); const isBar = is.ObjectOf({ foo: is.String, bar: is.Number }); type Foo = PredicateType; @@ -28,16 +44,4 @@ Deno.test("isUnionOf", async (t) => { assertType>(true); } }); - await t.step("returns true on one of T", () => { - const preds = [is.Number, is.String, is.Boolean] as const; - assertEquals(isUnionOf(preds)(0), true); - assertEquals(isUnionOf(preds)("a"), true); - assertEquals(isUnionOf(preds)(true), true); - }); - await t.step("returns false on non of T", async (t) => { - const preds = [is.Number, is.String, is.Boolean] as const; - await testWithExamples(t, isUnionOf(preds), { - excludeExamples: ["number", "string", "boolean"], - }); - }); }); diff --git a/is/unknown.ts b/is/unknown.ts index 9a72915..e5de964 100644 --- a/is/unknown.ts +++ b/is/unknown.ts @@ -1,6 +1,8 @@ /** * Assume `x` is `unknown` and always return `true` regardless of the type of `x`. * + * Use {@linkcode isAny} to assume that the type of `x` is `any`. + * * ```ts * import { is } from "@core/unknownutil"; * From ea8956ed2d3c6e2c81f2b962f975d7345b178593 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 3 Aug 2024 05:38:05 +0900 Subject: [PATCH 22/22] :memo: Update usage --- README.md | 14 +++++--------- mod.ts | 5 +++-- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index e45b921..b9d92be 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ A utility pack for handling `unknown` type. ## Usage -It provides `is` module for type predicate functions and `assert`, `ensure`, and -`maybe` helper functions. +It provides `is` and `as` module for type predicate functions and `assert`, +`ensure`, and `maybe` helper functions. -### is\* +### is\* and as\* Type predicate function is a function which returns `true` if a given value is expected type. For example, `isString` (or `is.String`) returns `true` if a @@ -28,7 +28,8 @@ if (is.String(a)) { } ``` -For more complex types, you can use `is*Of` (or `is.*Of`) functions like: +For more complex types, you can use `is*Of` (or `is.*Of`) functions and `as*` +(or `as.*`) like: ```typescript import { as, is, PredicateType } from "@core/unknownutil"; @@ -231,11 +232,6 @@ const a: unknown = "Hello"; const _: string = maybe(a, is.String) ?? "default value"; ``` -## Migration - -See [GitHub Wiki](https://github.com/jsr-core/unknownutil/wiki) for migration to -v3 from v2 or v2 from v1. - ## License The code follows MIT license written in [LICENSE](./LICENSE). Contributors need diff --git a/mod.ts b/mod.ts index 4507418..8f2d971 100644 --- a/mod.ts +++ b/mod.ts @@ -6,7 +6,7 @@ * It provides `is` module for type predicate functions and `assert`, `ensure`, and * `maybe` helper functions. * - * ### is\* + * ### is\* and as\* * * Type predicate function is a function which returns `true` if a given value is * expected type. For example, `isString` (or `is.String`) returns `true` if a @@ -21,7 +21,8 @@ * } * ``` * - * For more complex types, you can use `is*Of` (or `is.*Of`) functions like: + * For more complex types, you can use `is*Of` (or `is.*Of`) functions and `as*` + * (or `as.*`) functions like: * * ```typescript * import {