Add finiteNumber, nonNegativeNumber, and positiveInteger predicates

This commit is contained in:
Sindre Sorhus 2026-04-08 16:12:51 +07:00
parent ac46b5400d
commit 63be5c0c19
3 changed files with 150 additions and 0 deletions

View file

@ -463,6 +463,10 @@ function foo() {
foo();
```
##### .finiteNumber(value)
Check if `value` is a number and is finite. Excludes `Infinity` and `-Infinity`.
##### .positiveNumber(value)
Check if `value` is a number and is more than 0.
@ -471,6 +475,14 @@ Check if `value` is a number and is more than 0.
Check if `value` is a number and is less than 0.
##### .nonNegativeNumber(value)
Check if `value` is a number and is 0 or more.
##### .positiveInteger(value)
Check if `value` is an integer and is more than 0.
##### .inRange(value, range)
Check if `value` (number) is in the given `range`. The range is an array of two values, lower bound and upper bound, in no specific order.

View file

@ -266,6 +266,7 @@ const is = Object.assign(
error: isError,
evenInteger: isEvenInteger,
falsy: isFalsy,
finiteNumber: isFiniteNumber,
float32Array: isFloat32Array,
float64Array: isFloat64Array,
formData: isFormData,
@ -291,6 +292,7 @@ const is = Object.assign(
nonEmptySet: isNonEmptySet,
nonEmptyString: isNonEmptyString,
nonEmptyStringAndNotWhitespace: isNonEmptyStringAndNotWhitespace,
nonNegativeNumber: isNonNegativeNumber,
null: isNull,
nullOrUndefined: isNullOrUndefined,
number: isNumber,
@ -299,6 +301,7 @@ const is = Object.assign(
observable: isObservable,
oddInteger: isOddInteger,
plainObject: isPlainObject,
positiveInteger: isPositiveInteger,
positiveNumber: isPositiveNumber,
primitive: isPrimitive,
promise: isPromise,
@ -544,6 +547,10 @@ export function isFalsy(value: unknown): value is Falsy {
return !value;
}
export function isFiniteNumber(value: unknown): value is number {
return Number.isFinite(value);
}
// TODO: Support detecting Float16Array when targeting Node.js 24.
export function isFloat32Array(value: unknown): value is Float32Array {
@ -677,6 +684,10 @@ export function isNonEmptyStringAndNotWhitespace(value: unknown): value is NonEm
return isString(value) && !isEmptyStringOrWhitespace(value);
}
export function isNonNegativeNumber(value: unknown): value is number {
return isNumber(value) && value >= 0;
}
// eslint-disable-next-line @typescript-eslint/no-restricted-types
export function isNull(value: unknown): value is null {
return value === null;
@ -734,6 +745,10 @@ export function isPlainObject<Value = unknown>(value: unknown): value is Record<
return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
}
export function isPositiveInteger(value: unknown): value is number {
return isInteger(value) && value > 0;
}
export function isPositiveNumber(value: unknown): value is number {
return isNumber(value) && value > 0;
}
@ -908,8 +923,11 @@ type Assert = {
undefined: (value: unknown, message?: string) => asserts value is undefined;
string: (value: unknown, message?: string) => asserts value is string;
number: (value: unknown, message?: string) => asserts value is number;
finiteNumber: (value: unknown, message?: string) => asserts value is number;
positiveNumber: (value: unknown, message?: string) => asserts value is number;
negativeNumber: (value: unknown, message?: string) => asserts value is number;
nonNegativeNumber: (value: unknown, message?: string) => asserts value is number;
positiveInteger: (value: unknown, message?: string) => asserts value is number;
bigint: (value: unknown, message?: string) => asserts value is bigint;
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
function: (value: unknown, message?: string) => asserts value is Function;
@ -1050,6 +1068,7 @@ export const assert: Assert = {
error: assertError,
evenInteger: assertEvenInteger,
falsy: assertFalsy,
finiteNumber: assertFiniteNumber,
float32Array: assertFloat32Array,
float64Array: assertFloat64Array,
formData: assertFormData,
@ -1075,6 +1094,7 @@ export const assert: Assert = {
nonEmptySet: assertNonEmptySet,
nonEmptyString: assertNonEmptyString,
nonEmptyStringAndNotWhitespace: assertNonEmptyStringAndNotWhitespace,
nonNegativeNumber: assertNonNegativeNumber,
null: assertNull,
nullOrUndefined: assertNullOrUndefined,
number: assertNumber,
@ -1083,6 +1103,7 @@ export const assert: Assert = {
observable: assertObservable,
oddInteger: assertOddInteger,
plainObject: assertPlainObject,
positiveInteger: assertPositiveInteger,
positiveNumber: assertPositiveNumber,
primitive: assertPrimitive,
promise: assertPromise,
@ -1141,6 +1162,7 @@ const methodTypeMap = {
isError: 'Error',
isEvenInteger: 'even integer',
isFalsy: 'falsy',
isFiniteNumber: 'finite number',
isFloat32Array: 'Float32Array',
isFloat64Array: 'Float64Array',
isFormData: 'FormData',
@ -1166,6 +1188,7 @@ const methodTypeMap = {
isNonEmptySet: 'non-empty set',
isNonEmptyString: 'non-empty string',
isNonEmptyStringAndNotWhitespace: 'non-empty string and not whitespace',
isNonNegativeNumber: 'non-negative number',
isNull: 'null',
isNullOrUndefined: 'null or undefined',
isNumber: 'number',
@ -1174,6 +1197,7 @@ const methodTypeMap = {
isObservable: 'Observable',
isOddInteger: 'odd integer',
isPlainObject: 'plain object',
isPositiveInteger: 'positive integer',
isPositiveNumber: 'positive number',
isPrimitive: 'primitive',
isPromise: 'Promise',
@ -1420,6 +1444,12 @@ export function assertFalsy(value: unknown, message?: string): asserts value is
}
}
export function assertFiniteNumber(value: unknown, message?: string): asserts value is number {
if (!isFiniteNumber(value)) {
throw new TypeError(message ?? typeErrorMessage('finite number', value));
}
}
export function assertFloat32Array(value: unknown, message?: string): asserts value is Float32Array {
if (!isFloat32Array(value)) {
throw new TypeError(message ?? typeErrorMessage('Float32Array', value));
@ -1571,6 +1601,12 @@ export function assertNonEmptyStringAndNotWhitespace(value: unknown, message?: s
}
}
export function assertNonNegativeNumber(value: unknown, message?: string): asserts value is number {
if (!isNonNegativeNumber(value)) {
throw new TypeError(message ?? typeErrorMessage('non-negative number', value));
}
}
// eslint-disable-next-line @typescript-eslint/no-restricted-types
export function assertNull(value: unknown, message?: string): asserts value is null {
if (!isNull(value)) {
@ -1622,6 +1658,12 @@ export function assertPlainObject<Value = unknown>(value: unknown, message?: str
}
}
export function assertPositiveInteger(value: unknown, message?: string): asserts value is number {
if (!isPositiveInteger(value)) {
throw new TypeError(message ?? typeErrorMessage('positive integer', value));
}
}
export function assertPositiveNumber(value: unknown, message?: string): asserts value is number {
if (!isPositiveNumber(value)) {
throw new TypeError(message ?? typeErrorMessage('positive number', value));

View file

@ -544,6 +544,34 @@ test('is.positiveNumber', () => {
});
});
test('is.finiteNumber', () => {
assert.ok(is.finiteNumber(6));
assert.ok(is.finiteNumber(-6));
assert.ok(is.finiteNumber(0));
assert.ok(is.finiteNumber(1.4));
assert.doesNotThrow(() => {
isAssert.finiteNumber(6);
});
assert.doesNotThrow(() => {
isAssert.finiteNumber(0);
});
assert.strictEqual(is.finiteNumber(Number.POSITIVE_INFINITY), false);
assert.strictEqual(is.finiteNumber(Number.NEGATIVE_INFINITY), false);
assert.strictEqual(is.finiteNumber(Number.NaN), false);
assert.throws(() => {
isAssert.finiteNumber(Number.POSITIVE_INFINITY);
});
assert.throws(() => {
isAssert.finiteNumber(Number.NEGATIVE_INFINITY);
});
assert.throws(() => {
isAssert.finiteNumber(Number.NaN);
});
});
test('is.negativeNumber', () => {
assert.ok(is.negativeNumber(-6));
assert.ok(is.negativeNumber(-1.4));
@ -582,6 +610,62 @@ test('is.negativeNumber', () => {
});
});
test('is.nonNegativeNumber', () => {
assert.ok(is.nonNegativeNumber(0));
assert.ok(is.nonNegativeNumber(6));
assert.ok(is.nonNegativeNumber(1.4));
assert.ok(is.nonNegativeNumber(Number.POSITIVE_INFINITY));
assert.doesNotThrow(() => {
isAssert.nonNegativeNumber(0);
});
assert.doesNotThrow(() => {
isAssert.nonNegativeNumber(6);
});
assert.ok(is.nonNegativeNumber(-0)); // -0 >= 0 is true in JavaScript
assert.strictEqual(is.nonNegativeNumber(-6), false);
assert.strictEqual(is.nonNegativeNumber(-1.4), false);
assert.strictEqual(is.nonNegativeNumber(Number.NEGATIVE_INFINITY), false);
assert.strictEqual(is.nonNegativeNumber(Number.NaN), false);
assert.throws(() => {
isAssert.nonNegativeNumber(-6);
});
assert.throws(() => {
isAssert.nonNegativeNumber(Number.NEGATIVE_INFINITY);
});
});
test('is.positiveInteger', () => {
assert.ok(is.positiveInteger(1));
assert.ok(is.positiveInteger(6));
assert.ok(is.positiveInteger(100));
assert.doesNotThrow(() => {
isAssert.positiveInteger(1);
});
assert.doesNotThrow(() => {
isAssert.positiveInteger(6);
});
assert.strictEqual(is.positiveInteger(0), false);
assert.strictEqual(is.positiveInteger(-1), false);
assert.strictEqual(is.positiveInteger(1.5), false);
assert.strictEqual(is.positiveInteger(Number.POSITIVE_INFINITY), false);
assert.strictEqual(is.positiveInteger(Number.NaN), false);
assert.throws(() => {
isAssert.positiveInteger(0);
});
assert.throws(() => {
isAssert.positiveInteger(-1);
});
assert.throws(() => {
isAssert.positiveInteger(1.5);
});
});
test('is.numericString supplemental', () => {
assert.strictEqual(is.numericString(''), false);
assert.strictEqual(is.numericString(' '), false);
@ -2159,6 +2243,10 @@ test('custom assertion message', () => {
isAssert.falsy(true, message);
}, {message});
assert.throws(() => {
isAssert.finiteNumber(Number.POSITIVE_INFINITY, message);
}, {message});
assert.throws(() => {
isAssert.float32Array(undefined, message);
}, {message});
@ -2259,6 +2347,10 @@ test('custom assertion message', () => {
isAssert.nonEmptyStringAndNotWhitespace(undefined, message);
}, {message});
assert.throws(() => {
isAssert.nonNegativeNumber(-1, message);
}, {message});
assert.throws(() => {
isAssert.null(undefined, message);
}, {message});
@ -2291,6 +2383,10 @@ test('custom assertion message', () => {
isAssert.plainObject(undefined, message);
}, {message});
assert.throws(() => {
isAssert.positiveInteger(0, message);
}, {message});
assert.throws(() => {
isAssert.positiveNumber(undefined, message);
}, {message});