From d2e65aa5b89f1fb0b8c429fda4cb55468dddb9c6 Mon Sep 17 00:00:00 2001 From: Younho Choo Date: Mon, 17 Jan 2022 20:16:58 +0900 Subject: [PATCH] Improve `is.integer` & `is.safeInteger` TypeScript type --- source/index.ts | 26 +++++++++++++------------- source/types.ts | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/source/index.ts b/source/index.ts index 561cfcf..cda6ac3 100644 --- a/source/index.ts +++ b/source/index.ts @@ -2,7 +2,7 @@ /// /// -import {Class, TypedArray, ObservableLike, Primitive} from './types'; +import {Class, TypedArray, ObservableLike, Primitive, Integer} from './types'; const typedArrayTypeNames = [ 'Int8Array', @@ -258,8 +258,8 @@ is.nan = (value: unknown) => Number.isNaN(value as number); is.primitive = (value: unknown): value is Primitive => is.null_(value) || isPrimitiveTypeName(typeof value); -is.integer = (value: unknown): value is number => Number.isInteger(value as number); -is.safeInteger = (value: unknown): value is number => Number.isSafeInteger(value as number); +is.integer = (value: T): value is Integer => Number.isInteger(value); +is.safeInteger = (value: T): value is Integer => Number.isSafeInteger(value); is.plainObject = (value: unknown): value is Record => { // From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js @@ -279,7 +279,7 @@ export interface ArrayLike { readonly length: number; } -const isValidLength = (value: unknown): value is number => is.safeInteger(value) && value >= 0; +const isValidLength = (value: T): value is Integer => is.safeInteger(value) && value >= 0; is.arrayLike = (value: unknown): value is ArrayLike => !is.nullOrUndefined(value) && !is.function_(value) && isValidLength((value as ArrayLike).length); is.inRange = (value: number, range: number | number[]): value is number => { @@ -336,7 +336,7 @@ is.nodeStream = (value: unknown): value is NodeStream => is.object(value) && is. is.infinite = (value: unknown): value is number => value === Infinity || value === -Infinity; -const isAbsoluteMod2 = (remainder: number) => (value: number): value is number => is.integer(value) && Math.abs(value % 2) === remainder; +const isAbsoluteMod2 = (remainder: number) => (value: T): value is Integer => is.integer(value) && Math.abs(value % 2) === remainder; is.evenInteger = isAbsoluteMod2(0); is.oddInteger = isAbsoluteMod2(1); @@ -509,8 +509,8 @@ interface Assert { falsy: (value: unknown) => asserts value is unknown; nan: (value: unknown) => asserts value is unknown; primitive: (value: unknown) => asserts value is Primitive; - integer: (value: unknown) => asserts value is number; - safeInteger: (value: unknown) => asserts value is number; + integer: (value: T) => asserts value is Integer; + safeInteger: (value: T) => asserts value is Integer; plainObject: (value: unknown) => asserts value is Record; typedArray: (value: unknown) => asserts value is TypedArray; arrayLike: (value: unknown) => asserts value is ArrayLike; @@ -534,8 +534,8 @@ interface Assert { urlSearchParams: (value: unknown) => asserts value is URLSearchParams; // Numbers. - evenInteger: (value: number) => asserts value is number; - oddInteger: (value: number) => asserts value is number; + evenInteger: (value: T) => asserts value is Integer; + oddInteger: (value: T) => asserts value is Integer; // Two arguments. directInstanceOf: (instance: unknown, class_: Class) => asserts instance is T; @@ -610,8 +610,8 @@ export const assert: Assert = { 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), 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), + integer: (value: T): asserts value is Integer => assertType(is.integer(value), AssertionTypeDescription.integer, value), + safeInteger: (value: T): asserts value is Integer => assertType(is.safeInteger(value), AssertionTypeDescription.safeInteger, value), plainObject: (value: unknown): asserts value is Record => assertType(is.plainObject(value), AssertionTypeDescription.plainObject, value), typedArray: (value: unknown): asserts value is TypedArray => assertType(is.typedArray(value), AssertionTypeDescription.typedArray, value), arrayLike: (value: unknown): asserts value is ArrayLike => assertType(is.arrayLike(value), AssertionTypeDescription.arrayLike, value), @@ -635,8 +635,8 @@ export const assert: Assert = { urlSearchParams: (value: unknown): asserts value is URLSearchParams => assertType(is.urlSearchParams(value), 'URLSearchParams', value), // Numbers. - evenInteger: (value: number): asserts value is number => assertType(is.evenInteger(value), AssertionTypeDescription.evenInteger, value), - oddInteger: (value: number): asserts value is number => assertType(is.oddInteger(value), AssertionTypeDescription.oddInteger, value), + evenInteger: (value: T): asserts value is Integer => assertType(is.evenInteger(value), AssertionTypeDescription.evenInteger, value), + oddInteger: (value: T): asserts value is Integer => assertType(is.oddInteger(value), AssertionTypeDescription.oddInteger, value), // Two arguments. directInstanceOf: (instance: unknown, class_: Class): asserts instance is T => assertType(is.directInstanceOf(instance, class_), AssertionTypeDescription.directInstanceOf, instance), diff --git a/source/types.ts b/source/types.ts index 8bdd0f5..2b361b0 100644 --- a/source/types.ts +++ b/source/types.ts @@ -47,3 +47,24 @@ export interface ObservableLike { subscribe(observer: (value: unknown) => void): void; [Symbol.observable](): ObservableLike; } + +/** +A `number` that is an integer. +You can't pass a `bigint` as they are already guaranteed to be integers. + +Use-case: Validating and documenting parameters. + +@example +``` +import {Integer} from 'type-fest'; +declare function setYear(length: Integer): void; +``` + +@see NegativeInteger +@see NonNegativeInteger + +@category Numeric +*/ +// `${bigint}` is a type that matches a valid bigint literal without the `n` (ex. 1, 0b1, 0o1, 0x1) +// Because T is a number and not a string we can effectively use this to filter out any numbers containing decimal points +export type Integer = `${Type}` extends `${bigint}` ? Type : never;