From 613c41925e958346dc82c01394cc459285cf075f Mon Sep 17 00:00:00 2001 From: Justin Yao Du Date: Wed, 8 Feb 2023 21:49:30 -0800 Subject: [PATCH 1/3] Make the number Cake reject NaN --- README.md | 8 ++++ etc/caketype.api.md | 12 +++++- src/cake/NumberCake.ts | 71 ++++++++++++++++++++++++++++++++ src/cake/TypeGuardCake.ts | 15 ------- src/cake/index-internal.ts | 1 + src/cake/index.ts | 4 +- src/type-guards.ts | 5 --- tests/cake/Cake-withName.test.ts | 2 + tests/cake/NumberCake.test.ts | 14 +++++++ tests/cake/TypeGuardCake.test.ts | 5 --- 10 files changed, 110 insertions(+), 27 deletions(-) create mode 100644 src/cake/NumberCake.ts create mode 100644 tests/cake/NumberCake.test.ts diff --git a/README.md b/README.md index 5a9c7bd..6336898 100644 --- a/README.md +++ b/README.md @@ -829,6 +829,13 @@ number.is(5); // true number.is("5"); // false ``` +Although `typeof NaN === "number"`, this Cake does not accept `NaN`: + +```ts +number.is(NaN); // false +number.as(NaN); // TypeError: Value is NaN. +``` + --- #### `string` @@ -2015,6 +2022,7 @@ const c: Class = Date; #### Changed +- [number](#number) no longer accepts `NaN` ([#61](https://github.com/justinyaodu/caketype/pull/61)) - Built-in named Cakes (e.g. [number](#number)) now have type `Cake` instead of `TypeGuardCake` ([#60](https://github.com/justinyaodu/caketype/pull/60)) - Replaced `TypeGuardFailedCakeError` with `WrongTypeCakeError`, which is more general and has a more concise error message ([#60](https://github.com/justinyaodu/caketype/pull/60)) diff --git a/etc/caketype.api.md b/etc/caketype.api.md index 526bb79..4477c71 100644 --- a/etc/caketype.api.md +++ b/etc/caketype.api.md @@ -367,7 +367,17 @@ export class NotAnObjectCakeError extends CakeError { } // @public -export const number: Cake; +export const number: NumberCake; + +// @public +export class NumberCake extends Cake { + // (undocumented) + dispatchCheck(value: unknown, context: CakeDispatchCheckContext): CakeError | null; + // (undocumented) + dispatchStringify(context: CakeDispatchStringifyContext): string; + // (undocumented) + withName(name: string | null): NumberCake; +} // @public export type ObjectBakeable = { diff --git a/src/cake/NumberCake.ts b/src/cake/NumberCake.ts new file mode 100644 index 0000000..ff16414 --- /dev/null +++ b/src/cake/NumberCake.ts @@ -0,0 +1,71 @@ +import { + Cake, + CakeDispatchCheckContext, + CakeDispatchStringifyContext, + CakeError, + CakeErrorDispatchFormatContext, + StringTree, + WrongTypeCakeError, +} from "./index-internal"; + +/** + * See {@link number}. + * + * @public + */ +class NumberCake extends Cake { + dispatchCheck( + value: unknown, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + context: CakeDispatchCheckContext + ): CakeError | null { + if (typeof value !== "number") { + return new WrongTypeCakeError(this, value); + } + if (Number.isNaN(value)) { + return new IsNaNCakeError(); + } + return null; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + dispatchStringify(context: CakeDispatchStringifyContext): string { + return "number"; + } + + withName(name: string | null): NumberCake { + return new NumberCake({ ...this, name }); + } +} + +class IsNaNCakeError extends CakeError { + constructor() { + super(); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + dispatchFormat(context: CakeErrorDispatchFormatContext): StringTree { + return "Value is NaN."; + } +} + +/** + * A {@link Cake} representing the `number` type. + * + * @example + * ```ts + * number.is(5); // true + * number.is("5"); // false + * ``` + * + * @example Although `typeof NaN === "number"`, this Cake does not accept `NaN`: + * ```ts + * number.is(NaN); // false + * number.as(NaN); // TypeError: Value is NaN. + * ``` + * + * @public + */ +const number = new NumberCake({}); + +export { NumberCake, number }; diff --git a/src/cake/TypeGuardCake.ts b/src/cake/TypeGuardCake.ts index c65ac09..b9ca9c6 100644 --- a/src/cake/TypeGuardCake.ts +++ b/src/cake/TypeGuardCake.ts @@ -3,7 +3,6 @@ import { is_boolean, is_bigint, is_never, - is_number, is_string, is_symbol, is_unknown, @@ -141,19 +140,6 @@ const bigint: Cake = typeGuard("bigint", is_bigint); */ const never: Cake = typeGuard("never", is_never); -/** - * A {@link Cake} representing the `number` type. - * - * @example - * ```ts - * number.is(5); // true - * number.is("5"); // false - * ``` - * - * @public - */ -const number: Cake = typeGuard("number", is_number); - /** * A {@link Cake} representing the `string` type. * @@ -206,7 +192,6 @@ export { boolean, bigint, never, - number, string, symbol, unknown, diff --git a/src/cake/index-internal.ts b/src/cake/index-internal.ts index 5011cd0..ae60837 100644 --- a/src/cake/index-internal.ts +++ b/src/cake/index-internal.ts @@ -9,6 +9,7 @@ export * from "./CakeStringifier"; export * from "./Checker"; export * from "./CheckOptions"; export * from "./LiteralCake"; +export * from "./NumberCake"; export * from "./ObjectCake"; export * from "./ReferenceCake"; export * from "./StringTree"; diff --git a/src/cake/index.ts b/src/cake/index.ts index 52a0f92..10ca497 100644 --- a/src/cake/index.ts +++ b/src/cake/index.ts @@ -24,6 +24,9 @@ export { LiteralCake, LiteralCakeArgs, LiteralNotEqualCakeError, + // NumberCake.ts + NumberCake, + number, // ObjectCake.ts ObjectCake, ObjectCakeArgs, @@ -56,7 +59,6 @@ export { boolean, bigint, never, - number, string, symbol, unknown, diff --git a/src/type-guards.ts b/src/type-guards.ts index 436d4e3..bab1129 100644 --- a/src/type-guards.ts +++ b/src/type-guards.ts @@ -15,10 +15,6 @@ function is_never(value: unknown): value is never { return false; } -function is_number(value: unknown): value is number { - return typeof value === "number"; -} - function is_object(value: unknown): value is object { switch (typeof value) { case "object": @@ -47,7 +43,6 @@ export { is_bigint, is_boolean, is_never, - is_number, is_object, is_string, is_symbol, diff --git a/tests/cake/Cake-withName.test.ts b/tests/cake/Cake-withName.test.ts index 1f72bb3..315cf85 100644 --- a/tests/cake/Cake-withName.test.ts +++ b/tests/cake/Cake-withName.test.ts @@ -3,6 +3,7 @@ import { bake, boolean, keysUnsound, + number, reference, TupleCake, TypeGuardCake, @@ -13,6 +14,7 @@ import { is_boolean } from "../../src/type-guards"; const cakes = { array: array({}), literal: bake(0), + number: number, object: bake({}), reference: reference(() => boolean), tuple: new TupleCake({ diff --git a/tests/cake/NumberCake.test.ts b/tests/cake/NumberCake.test.ts new file mode 100644 index 0000000..eb36160 --- /dev/null +++ b/tests/cake/NumberCake.test.ts @@ -0,0 +1,14 @@ +import { number } from "../../src"; +import { expectTypeError } from "../test-helpers"; + +describe("documentation examples", () => { + test("number", () => { + expect(number.is(5)).toStrictEqual(true); + expect(number.is("5")).toStrictEqual(false); + }); + + test("number NaN", () => { + expect(number.is(NaN)).toStrictEqual(false); + expectTypeError(() => number.as(NaN), "Value is NaN."); + }); +}); diff --git a/tests/cake/TypeGuardCake.test.ts b/tests/cake/TypeGuardCake.test.ts index dfb519c..fa82319 100644 --- a/tests/cake/TypeGuardCake.test.ts +++ b/tests/cake/TypeGuardCake.test.ts @@ -33,11 +33,6 @@ describe("documentation examples", () => { expect(never.is(undefined)).toStrictEqual(false); }); - test("number", () => { - expect(number.is(5)).toStrictEqual(true); - expect(number.is("5")).toStrictEqual(false); - }); - test("string", () => { expect(string.is("hello")).toStrictEqual(true); expect(string.is("")).toStrictEqual(true); From 776b482760c1fb3bf9f890695ee158578308e7a8 Mon Sep 17 00:00:00 2001 From: Justin Yao Du Date: Wed, 8 Feb 2023 21:51:03 -0800 Subject: [PATCH 2/3] Fix pull request number --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6336898..932f0cd 100644 --- a/README.md +++ b/README.md @@ -2022,7 +2022,7 @@ const c: Class = Date; #### Changed -- [number](#number) no longer accepts `NaN` ([#61](https://github.com/justinyaodu/caketype/pull/61)) +- [number](#number) no longer accepts `NaN` ([#64](https://github.com/justinyaodu/caketype/pull/64)) - Built-in named Cakes (e.g. [number](#number)) now have type `Cake` instead of `TypeGuardCake` ([#60](https://github.com/justinyaodu/caketype/pull/60)) - Replaced `TypeGuardFailedCakeError` with `WrongTypeCakeError`, which is more general and has a more concise error message ([#60](https://github.com/justinyaodu/caketype/pull/60)) From 40c8c15f0ebab62a1080fb12a8c2e1422c406521 Mon Sep 17 00:00:00 2001 From: Justin Yao Du Date: Wed, 8 Feb 2023 21:52:37 -0800 Subject: [PATCH 3/3] Update changelog --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 932f0cd..7b701d4 100644 --- a/README.md +++ b/README.md @@ -2023,7 +2023,7 @@ const c: Class = Date; #### Changed - [number](#number) no longer accepts `NaN` ([#64](https://github.com/justinyaodu/caketype/pull/64)) -- Built-in named Cakes (e.g. [number](#number)) now have type `Cake` instead of `TypeGuardCake` ([#60](https://github.com/justinyaodu/caketype/pull/60)) +- Built-in named Cakes (e.g. [boolean](#boolean)) now have type `Cake` instead of `TypeGuardCake` ([#60](https://github.com/justinyaodu/caketype/pull/60)) - Replaced `TypeGuardFailedCakeError` with `WrongTypeCakeError`, which is more general and has a more concise error message ([#60](https://github.com/justinyaodu/caketype/pull/60)) ---