From 9d26c020eeff50d4b2a14d6b86ec51204a6f8157 Mon Sep 17 00:00:00 2001 From: hyperbola Date: Sun, 16 Jul 2023 22:19:31 +0800 Subject: [PATCH] Fix type guards for `assert.{truthy,falsy,nan}` (#187) --- source/index.ts | 14 +++---- test/test.ts | 101 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/source/index.ts b/source/index.ts index 4612504..5862ad9 100644 --- a/source/index.ts +++ b/source/index.ts @@ -294,7 +294,7 @@ is.urlString = (value: unknown): value is string => { is.truthy = (value: T | Falsy): value is T => Boolean(value); // eslint-disable-line unicorn/prefer-native-coercion-functions // Example: `is.falsy = (value: unknown): value is (not true | 0 | '' | undefined | null) => Boolean(value);` -is.falsy = (value: T | Falsy): value is Falsy => !value; +is.falsy = (value: unknown): value is Falsy => !value; is.nan = (value: unknown) => Number.isNaN(value as number); @@ -570,9 +570,9 @@ type Assert = { enumCase: (value: unknown, targetEnum: T) => asserts value is T[keyof T]; urlInstance: (value: unknown) => asserts value is URL; urlString: (value: unknown) => asserts value is string; - truthy: (value: unknown) => asserts value is unknown; - falsy: (value: unknown) => asserts value is unknown; - nan: (value: unknown) => asserts value is unknown; + truthy: (value: T | Falsy) => asserts value is T; + falsy: (value: unknown) => asserts value is Falsy; + nan: (value: unknown) => asserts value is number; primitive: (value: unknown) => asserts value is Primitive; integer: (value: unknown) => asserts value is number; safeInteger: (value: unknown) => asserts value is number; @@ -678,9 +678,9 @@ export const assert: Assert = { enumCase: (value: unknown, targetEnum: T): asserts value is T[keyof T] => assertType(is.enumCase(value, targetEnum), 'EnumCase', value), urlInstance: (value: unknown): asserts value is URL => assertType(is.urlInstance(value), 'URL', value), urlString: (value: unknown): asserts value is string => assertType(is.urlString(value), AssertionTypeDescription.urlString, value), - truthy: (value: unknown): asserts value is unknown => assertType(is.truthy(value), AssertionTypeDescription.truthy, value), - falsy: (value: unknown): asserts value is unknown => assertType(is.falsy(value), AssertionTypeDescription.falsy, value), - nan: (value: unknown): asserts value is unknown => assertType(is.nan(value), AssertionTypeDescription.nan, value), + truthy: (value: T | Falsy): asserts value is T => assertType(is.truthy(value), AssertionTypeDescription.truthy, value), + falsy: (value: unknown): asserts value is Falsy => assertType(is.falsy(value), AssertionTypeDescription.falsy, value), + nan: (value: unknown): asserts value is number => assertType(is.nan(value), AssertionTypeDescription.nan, value), primitive: (value: unknown): asserts value is Primitive => assertType(is.primitive(value), AssertionTypeDescription.primitive, value), integer: (value: unknown): asserts value is number => assertType(is.integer(value), AssertionTypeDescription.integer, value), safeInteger: (value: unknown): asserts value is number => assertType(is.safeInteger(value), AssertionTypeDescription.safeInteger, value), diff --git a/test/test.ts b/test/test.ts index d94fe39..e4734c1 100644 --- a/test/test.ts +++ b/test/test.ts @@ -1076,6 +1076,54 @@ test('is.truthy', t => { t.notThrows(() => { assert.truthy(BigInt(1)); }); + + // Checks that `assert.truthy` narrow downs boolean type to `true`. + { + const booleans = [true, false]; + const function_ = (value: true) => value; + assert.truthy(booleans[0]); + function_(booleans[0]); + } + + // Checks that `assert.truthy` excludes zero value from number type. + { + const bits: Array<0 | 1> = [1, 0, -0]; + const function_ = (value: 1) => value; + assert.truthy(bits[0]); + function_(bits[0]); + } + + // Checks that `assert.truthy` excludes zero value from bigint type. + { + const bits: Array<0n | 1n> = [1n, 0n, -0n]; + const function_ = (value: 1n) => value; + assert.truthy(bits[0]); + function_(bits[0]); + } + + // Checks that `assert.truthy` excludes empty string from string type. + { + const strings: Array<'nonEmpty' | ''> = ['nonEmpty', '']; + const function_ = (value: 'nonEmpty') => value; + assert.truthy(strings[0]); + function_(strings[0]); + } + + // Checks that `assert.truthy` excludes undefined from mixed type. + { + const maybeUndefineds = ['🦄', undefined]; + const function_ = (value: string) => value; + assert.truthy(maybeUndefineds[0]); + function_(maybeUndefineds[0]); + } + + // Checks that `assert.truthy` excludes null from mixed type. + { + const maybeNulls = ['🦄', null]; + const function_ = (value: string) => value; + assert.truthy(maybeNulls[0]); + function_(maybeNulls[0]); + } }); test('is.falsy', t => { @@ -1119,6 +1167,59 @@ test('is.falsy', t => { t.notThrows(() => { assert.falsy(BigInt(0)); }); + + // Checks that `assert.falsy` narrow downs boolean type to `false`. + { + const booleans = [false, true]; + const function_ = (value: false) => value; + assert.falsy(booleans[0]); + function_(booleans[0]); + } + + // Checks that `assert.falsy` narrow downs number type to `0`. + { + const bits = [0, -0, 1]; + const function_ = (value: 0) => value; + assert.falsy(bits[0]); + function_(bits[0]); + assert.falsy(bits[1]); + function_(bits[1]); + } + + // Checks that `assert.falsy` narrow downs bigint type to `0n`. + { + const bits = [0n, -0n, 1n]; + const function_ = (value: 0n) => value; + assert.falsy(bits[0]); + function_(bits[0]); + assert.falsy(bits[1]); + function_(bits[1]); + } + + // Checks that `assert.falsy` narrow downs string type to empty string. + { + const strings = ['', 'nonEmpty']; + const function_ = (value: '') => value; + assert.falsy(strings[0]); + function_(strings[0]); + } + + // Checks that `assert.falsy` can narrow down mixed type to undefined. + { + const maybeUndefineds = [undefined, Symbol('🦄')]; + const function_ = (value: undefined) => value; + assert.falsy(maybeUndefineds[0]); + function_(maybeUndefineds[0]); + } + + // Checks that `assert.falsy` can narrow down mixed type to null. + { + const maybeNulls = [null, Symbol('🦄')]; + // eslint-disable-next-line @typescript-eslint/ban-types + const function_ = (value: null) => value; + assert.falsy(maybeNulls[0]); + function_(maybeNulls[0]); + } }); test('is.nan', t => {