From f1f22c8242b67582fbd69708718c68201c207ac5 Mon Sep 17 00:00:00 2001 From: Alisue Date: Mon, 12 Feb 2024 21:26:17 +0900 Subject: [PATCH] :+1: Add `isOmitOf` --- is/__snapshots__/factory_test.ts.snap | 134 +++++++++++++------------- is/__snapshots__/utility_test.ts.snap | 63 ++++++------ is/utility.ts | 41 ++++++++ is/utility_test.ts | 46 +++++++++ 4 files changed, 190 insertions(+), 94 deletions(-) diff --git a/is/__snapshots__/factory_test.ts.snap b/is/__snapshots__/factory_test.ts.snap index 8c4fdd0..12410ef 100644 --- a/is/__snapshots__/factory_test.ts.snap +++ b/is/__snapshots__/factory_test.ts.snap @@ -1,131 +1,143 @@ export const snapshot = {}; -snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; +snapshot[`isReadonlyUniformTupleOf > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`; -snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; +snapshot[`isReadonlyUniformTupleOf > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`; -snapshot[`isTupleOf > returns properly named function 1`] = ` +snapshot[`isReadonlyUniformTupleOf > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`; + +snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; + +snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; + +snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; + +snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; + +snapshot[`isTupleOf > returns properly named function 1`] = ` "isTupleOf([ isNumber, isString, isBoolean -], isArray)" +])" `; -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; -snapshot[`isTupleOf > returns properly named function 3`] = ` +snapshot[`isTupleOf > returns properly named function 3`] = ` "isTupleOf([ isTupleOf([ isTupleOf([ isNumber, isString, isBoolean - ], isArray) - ], isArray) + ]) + ]) ])" `; -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; +snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; +snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; -snapshot[`isSetOf > returns properly named function 1`] = `"isSetOf(isNumber)"`; +snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; -snapshot[`isSetOf > returns properly named function 2`] = `"isSetOf((anonymous))"`; +snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; -snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, isString)"`; +snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; -snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), isString)"`; +snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; -snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; +snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; -snapshot[`isReadonlyTupleOf > returns properly named function 1`] = ` +snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; + +snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; + +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; + +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; + +snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; + +snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; + +snapshot[`isReadonlyTupleOf > returns properly named function 1`] = ` "isReadonlyTupleOf([ isNumber, isString, isBoolean -])" +], isArray)" `; -snapshot[`isReadonlyTupleOf > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)])"`; +snapshot[`isReadonlyTupleOf > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)], isArrayOf(isString))"`; -snapshot[`isReadonlyTupleOf > returns properly named function 3`] = ` +snapshot[`isReadonlyTupleOf > returns properly named function 3`] = ` "isReadonlyTupleOf([ isReadonlyTupleOf([ isReadonlyTupleOf([ isNumber, isString, isBoolean - ]) - ]) -])" + ], isArray) + ], isArray) +], isArray)" `; -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, undefined)"`; +snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`; -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), undefined)"`; +snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; -snapshot[`isTupleOf > returns properly named function 1`] = ` +snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; + +snapshot[`isLiteralOneOf > returns properly named function 1`] = `'isLiteralOneOf(["hello", "world"])'`; + +snapshot[`isTupleOf > returns properly named function 1`] = ` "isTupleOf([ isNumber, isString, isBoolean -])" +], isArray)" `; -snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)])"`; +snapshot[`isTupleOf > returns properly named function 2`] = `"isTupleOf([(anonymous)], isArrayOf(isString))"`; -snapshot[`isTupleOf > returns properly named function 3`] = ` +snapshot[`isTupleOf > returns properly named function 3`] = ` "isTupleOf([ isTupleOf([ isTupleOf([ isNumber, isString, isBoolean - ]) - ]) + ], isArray) + ], isArray) ])" `; -snapshot[`isMapOf > returns properly named function 1`] = `"isMapOf(isNumber, isString)"`; - -snapshot[`isMapOf > returns properly named function 2`] = `"isMapOf((anonymous), isString)"`; - -snapshot[`isLiteralOf > returns properly named function 1`] = `'isLiteralOf("hello")'`; - -snapshot[`isLiteralOf > returns properly named function 2`] = `"isLiteralOf(100)"`; - -snapshot[`isLiteralOf > returns properly named function 3`] = `"isLiteralOf(100n)"`; - -snapshot[`isLiteralOf > returns properly named function 4`] = `"isLiteralOf(true)"`; - -snapshot[`isLiteralOf > returns properly named function 5`] = `"isLiteralOf(null)"`; - -snapshot[`isLiteralOf > returns properly named function 6`] = `"isLiteralOf(undefined)"`; +snapshot[`isRecordOf > returns properly named function 1`] = `"isRecordOf(isNumber, undefined)"`; -snapshot[`isLiteralOf > returns properly named function 7`] = `"isLiteralOf(Symbol(asdf))"`; +snapshot[`isRecordOf > returns properly named function 2`] = `"isRecordOf((anonymous), undefined)"`; -snapshot[`isReadonlyTupleOf > returns properly named function 1`] = ` +snapshot[`isReadonlyTupleOf > returns properly named function 1`] = ` "isReadonlyTupleOf([ isNumber, isString, isBoolean -], isArray)" +])" `; -snapshot[`isReadonlyTupleOf > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)], isArrayOf(isString))"`; +snapshot[`isReadonlyTupleOf > returns properly named function 2`] = `"isReadonlyTupleOf([(anonymous)])"`; -snapshot[`isReadonlyTupleOf > returns properly named function 3`] = ` +snapshot[`isReadonlyTupleOf > returns properly named function 3`] = ` "isReadonlyTupleOf([ isReadonlyTupleOf([ isReadonlyTupleOf([ isNumber, isString, isBoolean - ], isArray) - ], isArray) -], isArray)" + ]) + ]) +])" `; snapshot[`isObjectOf > returns properly named function 1`] = ` @@ -146,18 +158,6 @@ snapshot[`isObjectOf > returns properly named function 3`] = ` })" `; -snapshot[`isUniformTupleOf > returns properly named function 1`] = `"isUniformTupleOf(3, isAny)"`; - -snapshot[`isUniformTupleOf > returns properly named function 2`] = `"isUniformTupleOf(3, isNumber)"`; - -snapshot[`isUniformTupleOf > returns properly named function 3`] = `"isUniformTupleOf(3, (anonymous))"`; - -snapshot[`isReadonlyUniformTupleOf > returns properly named function 1`] = `"isReadonlyUniformTupleOf(3, isAny)"`; - -snapshot[`isReadonlyUniformTupleOf > returns properly named function 2`] = `"isReadonlyUniformTupleOf(3, isNumber)"`; - -snapshot[`isReadonlyUniformTupleOf > returns properly named function 3`] = `"isReadonlyUniformTupleOf(3, (anonymous))"`; - -snapshot[`isArrayOf > returns properly named function 1`] = `"isArrayOf(isNumber)"`; +snapshot[`isInstanceOf > returns properly named function 1`] = `"isInstanceOf(Date)"`; -snapshot[`isArrayOf > returns properly named function 2`] = `"isArrayOf((anonymous))"`; +snapshot[`isInstanceOf > returns properly named function 2`] = `"isInstanceOf((anonymous))"`; diff --git a/is/__snapshots__/utility_test.ts.snap b/is/__snapshots__/utility_test.ts.snap index 7424e96..77c3176 100644 --- a/is/__snapshots__/utility_test.ts.snap +++ b/is/__snapshots__/utility_test.ts.snap @@ -1,35 +1,31 @@ export const snapshot = {}; -snapshot[`isAllOf > returns properly named function 1`] = ` -"isAllOf([ - isObjectOf({a: isNumber}), - isObjectOf({b: isString}) +snapshot[`isOneOf > returns properly named function 1`] = ` +"isOneOf([ + isNumber, + isString, + isBoolean ])" `; -snapshot[`isPickOf > returns properly named function 1`] = ` +snapshot[`isOmitOf > 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 2`] = `"isObjectOf({a: isNumber})"`; -snapshot[`isPartialOf > returns properly named function 1`] = ` -"isObjectOf({ - a: isOptionalOf(isNumber), - b: isOptionalOf(isString), - c: isOptionalOf(isBoolean) -})" -`; +snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; -snapshot[`isPartialOf > returns properly named function 2`] = ` -"isObjectOf({ - a: isOptionalOf(isNumber), - b: isOptionalOf(isString), - c: isOptionalOf(isBoolean) -})" +snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(isNumber)"`; + +snapshot[`isAllOf > returns properly named function 1`] = ` +"isAllOf([ + isObjectOf({a: isNumber}), + isObjectOf({b: isString}) +])" `; snapshot[`isStrictOf > returns properly named function 1`] = ` @@ -50,14 +46,27 @@ snapshot[`isStrictOf > returns properly named function 3`] = ` }))" `; -snapshot[`isOptionalOf > returns properly named function 1`] = `"isOptionalOf(isNumber)"`; +snapshot[`isPartialOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isOptionalOf(isNumber), + b: isOptionalOf(isString), + c: isOptionalOf(isBoolean) +})" +`; -snapshot[`isOptionalOf > returns properly named function 2`] = `"isOptionalOf(isNumber)"`; +snapshot[`isPartialOf > returns properly named function 2`] = ` +"isObjectOf({ + a: isOptionalOf(isNumber), + b: isOptionalOf(isString), + c: isOptionalOf(isBoolean) +})" +`; -snapshot[`isOneOf > returns properly named function 1`] = ` -"isOneOf([ - isNumber, - isString, - isBoolean -])" +snapshot[`isPickOf > returns properly named function 1`] = ` +"isObjectOf({ + a: isNumber, + c: isBoolean +})" `; + +snapshot[`isPickOf > returns properly named function 2`] = `"isObjectOf({a: isNumber})"`; diff --git a/is/utility.ts b/is/utility.ts index a8a2aa0..b66031f 100644 --- a/is/utility.ts +++ b/is/utility.ts @@ -283,8 +283,49 @@ export function isPickOf< & 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 } = getPredicateMetadata(pred); + const predObj = Object.fromEntries( + Object.entries(args[0]).filter(([k]) => !s.has(k as K)), + ); + return isObjectOf(predObj) as + & Predicate>> + & WithMetadata; +} + export default { AllOf: isAllOf, + OmitOf: isOmitOf, OneOf: isOneOf, OptionalOf: isOptionalOf, PartialOf: isPartialOf, diff --git a/is/utility_test.ts b/is/utility_test.ts index a1d2717..08e2bcd 100644 --- a/is/utility_test.ts +++ b/is/utility_test.ts @@ -26,6 +26,7 @@ import { import { isObjectOf } from "./factory.ts"; import is, { isAllOf, + isOmitOf, isOneOf, isOptionalOf, isPartialOf, @@ -475,6 +476,51 @@ Deno.test("isPickOf", async (t) => { }); }); +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)