From 6a9bf99c56e79575ae1fe83500ee9b467460ca16 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 6 Apr 2024 12:23:35 +0900 Subject: [PATCH 1/2] :coffee: Run `deno publish --dry-run` on CI --- .github/workflows/test.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 55dc85e..60bb098 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,3 +71,14 @@ jobs: registry-url: "https://registry.npmjs.org" - name: Build run: deno task build-npm + + jsr-publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: denoland/setup-deno@v1 + with: + deno-version: ${{ env.DENO_VERSION }} + - name: Publish (dry-run) + run: | + deno publish --dry-run From 9857615a5587c23ccc26f2dded6429e02253593b Mon Sep 17 00:00:00 2001 From: Alisue Date: Sat, 6 Apr 2024 13:04:01 +0900 Subject: [PATCH 2/2] :muscle: Merge `is/*.ts` into `is.ts` Owing to the principle outlined below, defining everything within the `is.ts` file is considerably more straightforward. https://jsr.io/docs/about-slow-types#types-must-be-simply-inferred-or-explicit --- .../is_test.ts.snap | 110 ++ is/_testutil.ts => _testutil.ts | 0 is.ts | 1685 ++++++++++++++++- is/__snapshots__/_deprecated_test.ts.snap | 16 - is/__snapshots__/annotation_test.ts.snap | 19 - is/__snapshots__/utility_test.ts.snap | 78 - is/_deprecated.ts | 47 - is/_deprecated_test.ts | 164 -- is/annotation.ts | 184 -- is/annotation_test.ts | 340 ---- is/core.ts | 403 ---- is/core_test.ts | 237 --- is/factory.ts | 776 -------- is/type.ts | 26 - is/utility.ts | 281 --- is/utility_test.ts | 339 ---- is_bench.ts | 2 +- is/factory_test.ts => is_test.ts | 765 +++++++- metadata.ts | 2 +- mod.ts | 3 - 20 files changed, 2531 insertions(+), 2946 deletions(-) rename is/__snapshots__/factory_test.ts.snap => __snapshots__/is_test.ts.snap (71%) rename is/_testutil.ts => _testutil.ts (100%) delete mode 100644 is/__snapshots__/_deprecated_test.ts.snap delete mode 100644 is/__snapshots__/annotation_test.ts.snap delete mode 100644 is/__snapshots__/utility_test.ts.snap delete mode 100644 is/_deprecated.ts delete mode 100644 is/_deprecated_test.ts delete mode 100644 is/annotation.ts delete mode 100644 is/annotation_test.ts delete mode 100644 is/core.ts delete mode 100644 is/core_test.ts delete mode 100644 is/factory.ts delete mode 100644 is/type.ts delete mode 100644 is/utility.ts delete mode 100644 is/utility_test.ts rename is/factory_test.ts => is_test.ts (61%) diff --git a/is/__snapshots__/factory_test.ts.snap b/__snapshots__/is_test.ts.snap similarity index 71% rename from is/__snapshots__/factory_test.ts.snap rename to __snapshots__/is_test.ts.snap index c8c0eca..dbb9164 100644 --- a/is/__snapshots__/factory_test.ts.snap +++ b/__snapshots__/is_test.ts.snap @@ -1,5 +1,23 @@ export const snapshot = {}; +snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; + +snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(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[`isReadonlyOf > returns properly named function 1`] = `"isReadonlyOf(isNumber)"`; + +snapshot[`isReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isReadonlyOf(isNumber))"`; + +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))"`; @@ -195,3 +213,95 @@ 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[`isUnionOf > returns properly named function 1`] = ` +"isUnionOf([ + isNumber, + isString, + isBoolean +])" +`; + +snapshot[`isIntersectionOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isString +})" +`; + +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[`isPartialOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isOptionalOf(isNumber), + b: isOptionalOf(isUnionOf([ + isString, + isUndefined + ])), + c: isOptionalOf(isBoolean) +})" +`; + +snapshot[`isPartialOf > returns properly named function 2`] = ` +"isObjectOf({ + a: isOptionalOf(isNumber), + b: isOptionalOf(isUnionOf([ + isString, + isUndefined + ])), + c: isOptionalOf(isBoolean) +})" +`; + +snapshot[`isPickOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" +`; + +snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; + +snapshot[`isOmitOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" +`; + +snapshot[`isOmitOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; + +snapshot[`isOneOf > returns properly named function 1`] = ` +"isUnionOf([ + isNumber, + isString, + isBoolean +])" +`; + +snapshot[`isAllOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + b: isString +})" +`; diff --git a/is/_testutil.ts b/_testutil.ts similarity index 100% rename from is/_testutil.ts rename to _testutil.ts diff --git a/is.ts b/is.ts index e26f86d..7b30729 100644 --- a/is.ts +++ b/is.ts @@ -1,20 +1,1667 @@ -import _deprecated from "./is/_deprecated.ts"; -import annotation from "./is/annotation.ts"; -import core from "./is/core.ts"; -import factory from "./is/factory.ts"; -import utility from "./is/utility.ts"; - -export * from "./is/_deprecated.ts"; -export * from "./is/annotation.ts"; -export * from "./is/core.ts"; -export * from "./is/factory.ts"; -export * from "./is/utility.ts"; -export type * from "./is/type.ts"; - -export default { - ..._deprecated, - ...annotation, - ...core, - ...factory, - ...utility, +import type { FlatType, UnionToIntersection, Writable } from "./_typeutil.ts"; +import { + type GetMetadata, + getMetadata, + getPredicateFactoryMetadata, + type PredicateFactoryMetadata, + setPredicateFactoryMetadata, + type WithMetadata, +} from "./metadata.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 { is, type PredicateType } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const isPerson = is.ObjectOf({ + * name: is.String, + * age: is.Number, + * address: is.OptionalOf(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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 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`. + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 `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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 Predicate => 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 `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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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[]`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 & WithMetadata { + return setPredicateFactoryMetadata( + (x: unknown): x is T[] => isArray(x) && x.every(pred), + { name: "isArrayOf", args: [pred] }, + ); +} + +type IsArrayOfMetadata = { + name: "isArrayOf"; + args: Parameters; +}; + +/** + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + */ +export function isSetOf( + pred: Predicate, +): Predicate> & WithMetadata { + return setPredicateFactoryMetadata( + (x: unknown): x is Set => { + if (!isSet(x)) return false; + for (const v of x.values()) { + if (!pred(v)) return false; + } + return true; + }, + { name: "isSetOf", args: [pred] }, + ); +} + +type IsSetOfMetadata = { + name: "isSetOf"; + args: Parameters; +}; + +/** + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + * + * With `predElse`: + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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> & WithMetadata; +export function isTupleOf< + T extends readonly [Predicate, ...Predicate[]], + E extends Predicate, +>( + predTup: T, + predElse: E, +): + & Predicate<[...TupleOf, ...PredicateType]> + & WithMetadata; +export function isTupleOf< + T extends readonly [Predicate, ...Predicate[]], + E extends Predicate, +>( + predTup: T, + predElse?: E, +): + & Predicate | [...TupleOf, ...PredicateType]> + & WithMetadata { + if (!predElse) { + return setPredicateFactoryMetadata( + (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] }, + ); + } else { + return setPredicateFactoryMetadata( + (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); + }, + { name: "isTupleOf", args: [predTup, predElse] }, + ); + } +} + +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 `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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + * + * With predicate function: + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + */ +export function isUniformTupleOf( + n: N, + pred: Predicate = isAny, +): Predicate> & WithMetadata { + return setPredicateFactoryMetadata( + (x: unknown): x is UniformTupleOf => { + if (!isArray(x) || x.length !== n) { + return false; + } + return x.every((v) => pred(v)); + }, + { name: "isUniformTupleOf", args: [n, pred] }, + ); +} + +// https://stackoverflow.com/a/71700658/1273406 +type UniformTupleOf< + T, + N extends number, + 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 `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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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`. + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + * + * With predicate function for keys: + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + */ +export function isRecordObjectOf( + pred: Predicate, + predKey?: Predicate, +): Predicate> & WithMetadata { + return setPredicateFactoryMetadata( + (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; + }, + { name: "isRecordObjectOf", args: [pred, predKey] }, + ); +} + +type IsRecordObjectOfMetadata = { + name: "isRecordObjectOf"; + args: Parameters; +}; + +/** + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + */ +export function isRecordOf( + pred: Predicate, + predKey?: Predicate, +): Predicate> & WithMetadata { + return setPredicateFactoryMetadata( + (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; + }, + { name: "isRecordOf", args: [pred, predKey] }, + ); +} + +type IsRecordOfMetadata = { + name: "isRecordOf"; + 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`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + * + * With predicate function for keys: + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + */ +export function isMapOf( + pred: Predicate, + predKey?: Predicate, +): Predicate> & WithMetadata { + return setPredicateFactoryMetadata( + (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; + }, + { name: "isMapOf", args: [pred, predKey] }, + ); +} + +type IsMapOfMetadata = { + name: "isMapOf"; + args: Parameters; +}; + +/** + * 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 `is.OptionalOf()` 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const isMyType = is.ObjectOf({ + * a: is.Number, + * b: is.String, + * c: is.OptionalOf(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; + * } + * ``` + * + * The `option.strict` is deprecated. Use `isStrictOf()` instead. + */ +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 (x == null || typeof x !== "object" || 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] }, + ); +} + +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; + } + // Optional + & { + [K in keyof T as T[K] extends WithOptional ? K : never]?: T[K] extends + Predicate ? U : never; + } +>; + +type IsObjectOfMetadata = { + name: "isObjectOf"; + args: [Parameters[0]]; +}; + +/** + * 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 `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()`. + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const isMyType = is.StrictOf(is.ObjectOf({ + * a: is.Number, + * b: is.String, + * c: is.OptionalOf(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>( + pred: + & Predicate + & WithMetadata, +): + & Predicate + & WithMetadata { + const { args } = getPredicateFactoryMetadata(pred); + const s = new Set(Object.keys(args[0])); + return setPredicateFactoryMetadata( + (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] }, + ); +} + +type IsStrictOfMetadata = { + name: "isStrictOf"; + args: Parameters; +}; + +/** + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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> & WithMetadata { + return setPredicateFactoryMetadata( + (x: unknown): x is InstanceType => x instanceof ctor, + { name: "isInstanceOf", args: [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`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 & WithMetadata { + return setPredicateFactoryMetadata( + (x: unknown): x is T => x === literal, + { name: "isLiteralOf", args: [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`. + * + * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. + * + * ```ts + * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + */ +export function isLiteralOneOf( + literals: T, +): Predicate & WithMetadata { + const s = new Set(literals); + return setPredicateFactoryMetadata( + (x: unknown): x is T[number] => s.has(x as T[number]), + { name: "isLiteralOneOf", args: [literals] }, + ); +} + +type IsLiteralOneOfMetadata = { + name: "isLiteralOneOf"; + args: Parameters; +}; + +/** + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; + * } + * ``` + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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> & WithMetadata { + return setPredicateFactoryMetadata( + (x: unknown): x is UnionOf => preds.some((pred) => pred(x)), + { name: "isUnionOf", args: [preds] }, + ); +} + +type UnionOf = T extends readonly [Predicate, ...infer R] + ? U | UnionOf + : never; + +type IsUnionOfMetadata = { + name: "isUnionOf"; + args: Parameters; +}; + +/** + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * 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 & WithMetadata, + ...(Predicate & WithMetadata)[], + ], +>( + preds: T, +): Predicate> & WithMetadata { + const predObj = {}; + preds.forEach((pred) => { + Object.assign(predObj, getPredicateFactoryMetadata(pred).args[0]); + }); + return isObjectOf(predObj) as + & Predicate> + & WithMetadata; +} + +type IntersectionOf = UnionToIntersection>; + +/** + * 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 { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const isMyType = is.RequiredOf(is.ObjectOf({ + * a: is.Number, + * b: is.UnionOf([is.String, is.Undefined]), + * c: is.OptionalOf(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, +>( + pred: Predicate & WithMetadata, +): + & Predicate>> + & WithMetadata { + const { args } = getPredicateFactoryMetadata(pred); + const predObj = Object.fromEntries( + Object.entries(args[0]).map(([k, v]) => [k, isUnwrapOptionalOf(v)]), + ); + return isObjectOf(predObj) as + & Predicate>> + & WithMetadata; +} + +/** + * 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 { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const isMyType = is.PartialOf(is.ObjectOf({ + * a: is.Number, + * b: is.UnionOf([is.String, is.Undefined]), + * c: is.OptionalOf(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, +>( + pred: Predicate & WithMetadata, +): + & Predicate>> + & WithMetadata { + const { args } = getPredicateFactoryMetadata(pred); + const predObj = Object.fromEntries( + Object.entries(args[0]).map(([k, v]) => [k, isOptionalOf(v)]), + ); + return isObjectOf(predObj) as + & Predicate>> + & WithMetadata; +} + +/** + * 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 { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const isMyType = is.PickOf(is.ObjectOf({ + * a: is.Number, + * b: is.String, + * c: is.OptionalOf(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, + K extends keyof T, +>( + pred: Predicate & WithMetadata, + keys: K[], +): + & Predicate>> + & WithMetadata { + const s = new Set(keys); + const { args } = getPredicateFactoryMetadata(pred); + const predObj = Object.fromEntries( + Object.entries(args[0]).filter(([k]) => s.has(k as K)), + ); + return isObjectOf(predObj) as + & Predicate>> + & WithMetadata; +} + +/** + * 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 { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; + * + * const isMyType = is.OmitOf(is.ObjectOf({ + * a: is.Number, + * b: is.String, + * c: is.OptionalOf(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, + K extends keyof T, +>( + pred: Predicate & WithMetadata, + keys: K[], +): + & Predicate>> + & WithMetadata { + const s = new Set(keys); + const { args } = getPredicateFactoryMetadata(pred); + const predObj = Object.fromEntries( + Object.entries(args[0]).filter(([k]) => !s.has(k as K)), + ); + return isObjectOf(predObj) as + & Predicate>> + & 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 = UnionToIntersection>; + +export const is = { + AllOf: isAllOf, + 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, + OneOf: isOneOf, + Optional: isOptional, + OptionalOf: isOptionalOf, + PartialOf: isPartialOf, + PickOf: isPickOf, + Primitive: isPrimitive, + Readonly: isReadonly, + ReadonlyOf: isReadonlyOf, + ReadonlyTupleOf: isReadonlyTupleOf, + ReadonlyUniformTupleOf: isReadonlyUniformTupleOf, + Record: isRecord, + RecordLike: isRecordLike, + RecordLikeOf: isRecordLikeOf, + 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, + UnwrapOptionalOf: isUnwrapOptionalOf, + UnwrapReadonlyOf: isUnwrapReadonlyOf, }; diff --git a/is/__snapshots__/_deprecated_test.ts.snap b/is/__snapshots__/_deprecated_test.ts.snap deleted file mode 100644 index 032a40c..0000000 --- a/is/__snapshots__/_deprecated_test.ts.snap +++ /dev/null @@ -1,16 +0,0 @@ -export const snapshot = {}; - -snapshot[`isOneOf > returns properly named function 1`] = ` -"isUnionOf([ - isNumber, - isString, - isBoolean -])" -`; - -snapshot[`isAllOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString -})" -`; diff --git a/is/__snapshots__/annotation_test.ts.snap b/is/__snapshots__/annotation_test.ts.snap deleted file mode 100644 index 41450a7..0000000 --- a/is/__snapshots__/annotation_test.ts.snap +++ /dev/null @@ -1,19 +0,0 @@ -export const snapshot = {}; - -snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; - -snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(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[`isReadonlyOf > returns properly named function 1`] = `"isReadonlyOf(isNumber)"`; - -snapshot[`isReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isReadonlyOf(isNumber))"`; - -snapshot[`isUnwrapReadonlyOf > returns properly named function 1`] = `"isNumber"`; - -snapshot[`isUnwrapReadonlyOf > returns properly named function 2`] = `"isReadonlyOf(isNumber)"`; diff --git a/is/__snapshots__/utility_test.ts.snap b/is/__snapshots__/utility_test.ts.snap deleted file mode 100644 index 0dfd261..0000000 --- a/is/__snapshots__/utility_test.ts.snap +++ /dev/null @@ -1,78 +0,0 @@ -export const snapshot = {}; - -snapshot[`isUnionOf > returns properly named function 1`] = ` -"isUnionOf([ - isNumber, - isString, - isBoolean -])" -`; - -snapshot[`isIntersectionOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - b: isString -})" -`; - -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[`isPartialOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isOptionalOf(isNumber), - b: isOptionalOf(isUnionOf([ - isString, - isUndefined - ])), - c: isOptionalOf(isBoolean) -})" -`; - -snapshot[`isPartialOf > returns properly named function 2`] = ` -"isObjectOf({ - a: isOptionalOf(isNumber), - b: isOptionalOf(isUnionOf([ - isString, - isUndefined - ])), - c: isOptionalOf(isBoolean) -})" -`; - -snapshot[`isPickOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isNumber, - c: isBoolean -})" -`; - -snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; - -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/_deprecated.ts b/is/_deprecated.ts deleted file mode 100644 index 5bc7030..0000000 --- a/is/_deprecated.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { UnionToIntersection } from "../_typeutil.ts"; -import type { Predicate } from "./type.ts"; -import type { isObjectOf } from "./factory.ts"; -import type { GetMetadata, WithMetadata } from "../metadata.ts"; -import { isIntersectionOf, isUnionOf } from "./utility.ts"; - -type IsObjectOfMetadata = GetMetadata>; - -/** - * 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 = UnionToIntersection>; - -export default { - AllOf: isAllOf, - OneOf: isOneOf, -}; diff --git a/is/_deprecated_test.ts b/is/_deprecated_test.ts deleted file mode 100644 index 8eae5af..0000000 --- a/is/_deprecated_test.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { - assertEquals, - assertStrictEquals, -} from "https://deno.land/std@0.211.0/assert/mod.ts"; -import { - assertSnapshot, -} from "https://deno.land/std@0.211.0/testing/snapshot.ts"; -import { assertType } from "https://deno.land/std@0.211.0/testing/types.ts"; -import { type Equal, stringify } from "./_testutil.ts"; -import type { Predicate, PredicateType } from "./type.ts"; -import { isBoolean, isNumber, isString } from "./core.ts"; -import { isObjectOf } from "./factory.ts"; -import is, { isAllOf, isOneOf } from "./_deprecated.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("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("./_deprecated.ts"); - const casesOfAliasAndIsFunction = Object.entries(mod) - .filter(([k, _]) => k.startsWith("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/is/annotation.ts b/is/annotation.ts deleted file mode 100644 index 60f3290..0000000 --- a/is/annotation.ts +++ /dev/null @@ -1,184 +0,0 @@ -import type { Predicate } from "./type.ts"; -import type { Writable } from "../_typeutil.ts"; -import { - getMetadata, - getPredicateFactoryMetadata, - type PredicateFactoryMetadata, - setPredicateFactoryMetadata, - type WithMetadata, -} from "../metadata.ts"; - -/** - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 Predicate => 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 `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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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; - -export default { - Optional: isOptional, - OptionalOf: isOptionalOf, - Readonly: isReadonly, - ReadonlyOf: isReadonlyOf, - UnwrapOptionalOf: isUnwrapOptionalOf, - UnwrapReadonlyOf: isUnwrapReadonlyOf, -}; diff --git a/is/annotation_test.ts b/is/annotation_test.ts deleted file mode 100644 index ceeed47..0000000 --- a/is/annotation_test.ts +++ /dev/null @@ -1,340 +0,0 @@ -import { - assertEquals, - assertStrictEquals, -} from "https://deno.land/std@0.211.0/assert/mod.ts"; -import { - assertSnapshot, -} from "https://deno.land/std@0.211.0/testing/snapshot.ts"; -import { assertType } from "https://deno.land/std@0.211.0/testing/types.ts"; -import { type Equal, stringify } from "./_testutil.ts"; -import type { Predicate } from "./type.ts"; -import { - isArray, - isAsyncFunction, - isBigInt, - isBoolean, - isFunction, - isNull, - isNumber, - isRecordObject, - isSet, - isString, - isSymbol, - isSyncFunction, - isUndefined, -} from "./core.ts"; -import { isObjectOf, isTupleOf, isUniformTupleOf } from "./factory.ts"; -import is, { - isOptionalOf, - isReadonlyOf, - isUnwrapOptionalOf, - isUnwrapReadonlyOf, -} from "./annotation.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("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("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("is", async (t) => { - const mod = await import("./annotation.ts"); - const casesOfAliasAndIsFunction = Object.entries(mod) - .filter(([k, _]) => k.startsWith("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/is/core.ts b/is/core.ts deleted file mode 100644 index 6992bfa..0000000 --- a/is/core.ts +++ /dev/null @@ -1,403 +0,0 @@ -const objectToString = Object.prototype.toString; - -/** - * Assume `x is `any` and always return `true` regardless of the type of `x`. - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 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`. - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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); -} - -const primitiveSet = new Set([ - "string", - "number", - "bigint", - "boolean", - "symbol", -]); - -export default { - Any: isAny, - Array: isArray, - AsyncFunction: isAsyncFunction, - BigInt: isBigInt, - Boolean: isBoolean, - Function: isFunction, - Map: isMap, - Null: isNull, - Nullish: isNullish, - Number: isNumber, - Primitive: isPrimitive, - Record: isRecord, - RecordLike: isRecordLike, - RecordObject: isRecordObject, - Set: isSet, - String: isString, - Symbol: isSymbol, - SyncFunction: isSyncFunction, - Undefined: isUndefined, - Unknown: isUnknown, -}; diff --git a/is/core_test.ts b/is/core_test.ts deleted file mode 100644 index 3b15eb9..0000000 --- a/is/core_test.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { - assertEquals, - assertStrictEquals, -} from "https://deno.land/std@0.211.0/assert/mod.ts"; -import { stringify } from "./_testutil.ts"; -import type { Predicate } from "./type.ts"; -import is, { - isAny, - isArray, - isAsyncFunction, - isBigInt, - isBoolean, - isFunction, - isMap, - isNull, - isNullish, - isNumber, - isPrimitive, - isRecord, - isRecordLike, - isRecordObject, - isSet, - isString, - isSymbol, - isSyncFunction, - isUndefined, - isUnknown, -} from "./core.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("isRecordLike", async (t) => { - await testWithExamples(t, isRecordLike, { - 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("is", async (t) => { - const mod = await import("./core.ts"); - const casesOfAliasAndIsFunction = Object.entries(mod) - .filter(([k, _]) => k.startsWith("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/is/factory.ts b/is/factory.ts deleted file mode 100644 index a18c467..0000000 --- a/is/factory.ts +++ /dev/null @@ -1,776 +0,0 @@ -import type { FlatType } from "../_typeutil.ts"; -import type { Predicate, PredicateType } from "./type.ts"; -import { type isOptionalOf, isReadonlyOf } from "./annotation.ts"; -import { - isAny, - isArray, - isMap, - isRecord, - isRecordObject, - isSet, - type Primitive, -} from "./core.ts"; -import { - type GetMetadata, - getPredicateFactoryMetadata, - setPredicateFactoryMetadata, - type WithMetadata, -} from "../metadata.ts"; - -type IsReadonlyOfMetadata = GetMetadata>; - -/** - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is T[] => isArray(x) && x.every(pred), - { name: "isArrayOf", args: [pred] }, - ); -} - -type IsArrayOfMetadata = { - name: "isArrayOf"; - args: Parameters; -}; - -/** - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - */ -export function isSetOf( - pred: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is Set => { - if (!isSet(x)) return false; - for (const v of x.values()) { - if (!pred(v)) return false; - } - return true; - }, - { name: "isSetOf", args: [pred] }, - ); -} - -type IsSetOfMetadata = { - name: "isSetOf"; - args: Parameters; -}; - -/** - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - * - * With `predElse`: - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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> & WithMetadata; -export function isTupleOf< - T extends readonly [Predicate, ...Predicate[]], - E extends Predicate, ->( - predTup: T, - predElse: E, -): - & Predicate<[...TupleOf, ...PredicateType]> - & WithMetadata; -export function isTupleOf< - T extends readonly [Predicate, ...Predicate[]], - E extends Predicate, ->( - predTup: T, - predElse?: E, -): - & Predicate | [...TupleOf, ...PredicateType]> - & WithMetadata { - if (!predElse) { - return setPredicateFactoryMetadata( - (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] }, - ); - } else { - return setPredicateFactoryMetadata( - (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); - }, - { name: "isTupleOf", args: [predTup, predElse] }, - ); - } -} - -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 `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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - * - * With predicate function: - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - */ -export function isUniformTupleOf( - n: N, - pred: Predicate = isAny, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is UniformTupleOf => { - if (!isArray(x) || x.length !== n) { - return false; - } - return x.every((v) => pred(v)); - }, - { name: "isUniformTupleOf", args: [n, pred] }, - ); -} - -// https://stackoverflow.com/a/71700658/1273406 -type UniformTupleOf< - T, - N extends number, - 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 `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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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`. - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - * - * With predicate function for keys: - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - */ -export function isRecordObjectOf( - pred: Predicate, - predKey?: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( - (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; - }, - { name: "isRecordObjectOf", args: [pred, predKey] }, - ); -} - -type IsRecordObjectOfMetadata = { - name: "isRecordObjectOf"; - args: Parameters; -}; - -/** - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - */ -export function isRecordOf( - pred: Predicate, - predKey?: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( - (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; - }, - { name: "isRecordOf", args: [pred, predKey] }, - ); -} - -type IsRecordOfMetadata = { - name: "isRecordOf"; - 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`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - * - * With predicate function for keys: - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - */ -export function isMapOf( - pred: Predicate, - predKey?: Predicate, -): Predicate> & WithMetadata { - return setPredicateFactoryMetadata( - (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; - }, - { name: "isMapOf", args: [pred, predKey] }, - ); -} - -type IsMapOfMetadata = { - name: "isMapOf"; - args: Parameters; -}; - -/** - * 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 `is.OptionalOf()` 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * const isMyType = is.ObjectOf({ - * a: is.Number, - * b: is.String, - * c: is.OptionalOf(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; - * } - * ``` - * - * The `option.strict` is deprecated. Use `isStrictOf()` instead. - */ -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 (x == null || typeof x !== "object" || 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] }, - ); -} - -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; - } - // Optional - & { - [K in keyof T as T[K] extends WithOptional ? K : never]?: T[K] extends - Predicate ? U : never; - } ->; - -type IsObjectOfMetadata = { - name: "isObjectOf"; - args: [Parameters[0]]; -}; - -/** - * 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 `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()`. - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * const isMyType = is.StrictOf(is.ObjectOf({ - * a: is.Number, - * b: is.String, - * c: is.OptionalOf(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>( - pred: - & Predicate - & WithMetadata, -): - & Predicate - & WithMetadata { - const { args } = getPredicateFactoryMetadata(pred); - const s = new Set(Object.keys(args[0])); - return setPredicateFactoryMetadata( - (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] }, - ); -} - -type IsStrictOfMetadata = { - name: "isStrictOf"; - args: Parameters; -}; - -/** - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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> & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is InstanceType => x instanceof ctor, - { name: "isInstanceOf", args: [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`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is T => x === literal, - { name: "isLiteralOf", args: [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`. - * - * To enhance performance, users are advised to cache the return value of this function and mitigate the creation cost. - * - * ```ts - * import { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - */ -export function isLiteralOneOf( - literals: T, -): Predicate & WithMetadata { - const s = new Set(literals); - return setPredicateFactoryMetadata( - (x: unknown): x is T[number] => s.has(x as T[number]), - { name: "isLiteralOneOf", args: [literals] }, - ); -} - -type IsLiteralOneOfMetadata = { - name: "isLiteralOneOf"; - args: Parameters; -}; - -export default { - ArrayOf: isArrayOf, - InstanceOf: isInstanceOf, - LiteralOf: isLiteralOf, - LiteralOneOf: isLiteralOneOf, - MapOf: isMapOf, - ObjectOf: isObjectOf, - ReadonlyTupleOf: isReadonlyTupleOf, - ReadonlyUniformTupleOf: isReadonlyUniformTupleOf, - RecordLikeOf: isRecordLikeOf, - RecordObjectOf: isRecordObjectOf, - RecordOf: isRecordOf, - SetOf: isSetOf, - StrictOf: isStrictOf, - TupleOf: isTupleOf, - UniformTupleOf: isUniformTupleOf, -}; diff --git a/is/type.ts b/is/type.ts deleted file mode 100644 index 73cb587..0000000 --- a/is/type.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * A type predicate function. - */ -export type Predicate = (x: unknown) => x is T; - -/** - * A type predicated by Predicate. - * - * ```ts - * import { is, type PredicateType } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * const isPerson = is.ObjectOf({ - * name: is.String, - * age: is.Number, - * address: is.OptionalOf(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; diff --git a/is/utility.ts b/is/utility.ts deleted file mode 100644 index 7b936d9..0000000 --- a/is/utility.ts +++ /dev/null @@ -1,281 +0,0 @@ -import type { FlatType, UnionToIntersection } from "../_typeutil.ts"; -import type { Predicate } from "./type.ts"; -import { isOptionalOf, isUnwrapOptionalOf } from "./annotation.ts"; -import { isObjectOf } from "./factory.ts"; -import { - type GetMetadata, - getPredicateFactoryMetadata, - setPredicateFactoryMetadata, - type WithMetadata, -} from "../metadata.ts"; - -type IsObjectOfMetadata = GetMetadata>; - -/** - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.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; - * } - * ``` - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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> & WithMetadata { - return setPredicateFactoryMetadata( - (x: unknown): x is UnionOf => preds.some((pred) => pred(x)), - { name: "isUnionOf", args: [preds] }, - ); -} - -type UnionOf = T extends readonly [Predicate, ...infer R] - ? U | UnionOf - : never; - -type IsUnionOfMetadata = { - name: "isUnionOf"; - args: Parameters; -}; - -/** - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * 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 & WithMetadata, - ...(Predicate & WithMetadata)[], - ], ->( - preds: T, -): Predicate> & WithMetadata { - const predObj = {}; - preds.forEach((pred) => { - Object.assign(predObj, getPredicateFactoryMetadata(pred).args[0]); - }); - return isObjectOf(predObj) as - & Predicate> - & WithMetadata; -} - -type IntersectionOf = UnionToIntersection>; - -/** - * 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 { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * const isMyType = is.RequiredOf(is.ObjectOf({ - * a: is.Number, - * b: is.UnionOf([is.String, is.Undefined]), - * c: is.OptionalOf(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, ->( - pred: Predicate & WithMetadata, -): - & Predicate>> - & WithMetadata { - const { args } = getPredicateFactoryMetadata(pred); - const predObj = Object.fromEntries( - Object.entries(args[0]).map(([k, v]) => [k, isUnwrapOptionalOf(v)]), - ); - return isObjectOf(predObj) as - & Predicate>> - & WithMetadata; -} - -/** - * 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 { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * const isMyType = is.PartialOf(is.ObjectOf({ - * a: is.Number, - * b: is.UnionOf([is.String, is.Undefined]), - * c: is.OptionalOf(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, ->( - pred: Predicate & WithMetadata, -): - & Predicate>> - & WithMetadata { - const { args } = getPredicateFactoryMetadata(pred); - const predObj = Object.fromEntries( - Object.entries(args[0]).map(([k, v]) => [k, isOptionalOf(v)]), - ); - return isObjectOf(predObj) as - & Predicate>> - & WithMetadata; -} - -/** - * 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 { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * const isMyType = is.PickOf(is.ObjectOf({ - * a: is.Number, - * b: is.String, - * c: is.OptionalOf(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, - K extends keyof T, ->( - pred: Predicate & WithMetadata, - keys: K[], -): - & Predicate>> - & WithMetadata { - const s = new Set(keys); - const { args } = getPredicateFactoryMetadata(pred); - const predObj = Object.fromEntries( - Object.entries(args[0]).filter(([k]) => s.has(k as K)), - ); - return isObjectOf(predObj) as - & Predicate>> - & WithMetadata; -} - -/** - * 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 { is } from "https://deno.land/x/unknownutil@$MODULE_VERSION/mod.ts"; - * - * const isMyType = is.OmitOf(is.ObjectOf({ - * a: is.Number, - * b: is.String, - * c: is.OptionalOf(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, - K extends keyof T, ->( - pred: Predicate & WithMetadata, - keys: K[], -): - & Predicate>> - & WithMetadata { - const s = new Set(keys); - const { args } = getPredicateFactoryMetadata(pred); - const predObj = Object.fromEntries( - Object.entries(args[0]).filter(([k]) => !s.has(k as K)), - ); - return isObjectOf(predObj) as - & Predicate>> - & WithMetadata; -} - -export default { - IntersectionOf: isIntersectionOf, - OmitOf: isOmitOf, - PartialOf: isPartialOf, - PickOf: isPickOf, - RequiredOf: isRequiredOf, - UnionOf: isUnionOf, -}; diff --git a/is/utility_test.ts b/is/utility_test.ts deleted file mode 100644 index dfff944..0000000 --- a/is/utility_test.ts +++ /dev/null @@ -1,339 +0,0 @@ -import { - assertEquals, - assertStrictEquals, -} from "https://deno.land/std@0.211.0/assert/mod.ts"; -import { - assertSnapshot, -} from "https://deno.land/std@0.211.0/testing/snapshot.ts"; -import { assertType } from "https://deno.land/std@0.211.0/testing/types.ts"; -import { type Equal, stringify } from "./_testutil.ts"; -import type { Predicate, PredicateType } from "./type.ts"; -import { isOptionalOf } from "./annotation.ts"; -import { isBoolean, isNumber, isString, isUndefined } from "./core.ts"; -import { isObjectOf } from "./factory.ts"; -import is, { - isIntersectionOf, - isOmitOf, - isPartialOf, - isPickOf, - isRequiredOf, - isUnionOf, -} from "./utility.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("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, - ); - }); - 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 (isIntersectionOf(preds)(a)) { - assertType>(true); - } - }); - await t.step("returns true on all of T", () => { - const preds = [ - isObjectOf({ a: isNumber }), - isObjectOf({ b: isString }), - ] as const; - assertEquals(isIntersectionOf(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( - 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"], - }); - }); -}); - -Deno.test("isRequiredOf", async (t) => { - const pred = isObjectOf({ - a: isNumber, - b: isUnionOf([isString, isUndefined]), - c: isOptionalOf(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: isOptionalOf(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("./utility.ts"); - const casesOfAliasAndIsFunction = Object.entries(mod) - .filter(([k, _]) => k.startsWith("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/is_bench.ts b/is_bench.ts index d814879..14f1750 100644 --- a/is_bench.ts +++ b/is_bench.ts @@ -1,4 +1,4 @@ -import is from "./is.ts"; +import { is } from "./is.ts"; const cs: unknown[] = [ "Hello world", diff --git a/is/factory_test.ts b/is_test.ts similarity index 61% rename from is/factory_test.ts rename to is_test.ts index 5fea614..9b9dccc 100644 --- a/is/factory_test.ts +++ b/is_test.ts @@ -7,34 +7,57 @@ import { } from "https://deno.land/std@0.211.0/testing/snapshot.ts"; import { assertType } from "https://deno.land/std@0.211.0/testing/types.ts"; import { type Equal, stringify } from "./_testutil.ts"; -import type { Predicate } from "./type.ts"; -import { isOptionalOf } from "./annotation.ts"; +import type { Predicate, PredicateType } from "./is.ts"; import { + is, + isAllOf, + isAny, isArray, + isArrayOf, + isAsyncFunction, + isBigInt, isBoolean, isFunction, - isNumber, - isString, - isUndefined, -} from "./core.ts"; -import is, { - isArrayOf, isInstanceOf, + isIntersectionOf, isLiteralOf, isLiteralOneOf, + isMap, isMapOf, + isNull, + isNullish, + isNumber, isObjectOf, + isOmitOf, + isOneOf, + isOptionalOf, + isPartialOf, + isPickOf, + isPrimitive, + isReadonlyOf, isReadonlyTupleOf, isReadonlyUniformTupleOf, + isRecord, + isRecordLike, isRecordLikeOf, + isRecordObject, isRecordObjectOf, isRecordOf, + isRequiredOf, + isSet, isSetOf, isStrictOf, + isString, + isSymbol, + isSyncFunction, isTupleOf, + isUndefined, isUniformTupleOf, -} from "./factory.ts"; -import { isUnionOf } from "./utility.ts"; + isUnionOf, + isUnknown, + isUnwrapOptionalOf, + isUnwrapReadonlyOf, +} from "./is.ts"; const examples = { string: ["", "Hello world"], @@ -84,6 +107,389 @@ async function testWithExamples( } } +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("isRecordLike", async (t) => { + await testWithExamples(t, isRecordLike, { + 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("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("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); @@ -1025,10 +1431,345 @@ Deno.test("isLiteralOneOf", async (t) => { }); }); +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, + ); + }); + 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 (isIntersectionOf(preds)(a)) { + assertType>(true); + } + }); + await t.step("returns true on all of T", () => { + const preds = [ + isObjectOf({ a: isNumber }), + isObjectOf({ b: isString }), + ] as const; + assertEquals(isIntersectionOf(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( + 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"], + }); + }); +}); + +Deno.test("isRequiredOf", async (t) => { + const pred = isObjectOf({ + a: isNumber, + b: isUnionOf([isString, isUndefined]), + c: isOptionalOf(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: isOptionalOf(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("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("./factory.ts"); + const mod = await import("./is.ts"); const casesOfAliasAndIsFunction = Object.entries(mod) - .filter(([k, _]) => k.startsWith("is")) + .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`, () => { diff --git a/metadata.ts b/metadata.ts index 584173e..8c5da06 100644 --- a/metadata.ts +++ b/metadata.ts @@ -1,4 +1,4 @@ -import type { Predicate } from "./is/type.ts"; +import type { Predicate } from "./is.ts"; import { inspect } from "./inspect.ts"; /** diff --git a/mod.ts b/mod.ts index 51b7fc6..d3f109e 100644 --- a/mod.ts +++ b/mod.ts @@ -242,6 +242,3 @@ export * from "./is.ts"; export * from "./metadata.ts"; export * from "./util.ts"; - -import is from "./is.ts"; -export { is };