forked from orbit-oss/is
Fix some type guards
This commit is contained in:
parent
cb4ee0e92c
commit
13febb6b01
8 changed files with 715 additions and 205 deletions
9
AGENTS.md
Normal file
9
AGENTS.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Notes
|
||||||
|
|
||||||
|
## Branded types for type guards
|
||||||
|
|
||||||
|
TypeScript type guards narrow in both branches. If `is.integer(n)` returns `value is number` and the input is `number`, the false branch computes `Exclude<number, number>` = `never`. This makes common patterns like `if (!is.integer(n)) throw; use(n)` fail because `n` becomes `never` after the guard.
|
||||||
|
|
||||||
|
To avoid this, type guard predicates use branded types (e.g., `number & {readonly __brand: 'Integer'}`, `string & {readonly __brand: 'UrlString'}`). A branded subtype ensures the false branch stays the original type (e.g., `Exclude<number, Integer>` = `number`).
|
||||||
|
|
||||||
|
Assert functions (`asserts value is T`) don't need branded types since they throw on failure and have no false branch. They use plain types like `asserts value is number`.
|
||||||
1
CLAUDE.md
Symbolic link
1
CLAUDE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
AGENTS.md
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "del distribution && tsc",
|
"build": "del distribution && tsc",
|
||||||
"test": "tsc --noEmit && xo && node --experimental-transform-types --test test/test.ts",
|
"test": "tsc --noEmit && tsc --project test/tsconfig.json --noEmit && xo && node --experimental-transform-types --test test/test.ts",
|
||||||
"prepare": "npm run build"
|
"prepare": "npm run build"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
|
||||||
116
source/index.ts
116
source/index.ts
|
|
@ -1,14 +1,29 @@
|
||||||
import type {
|
import type {
|
||||||
ArrayLike,
|
ArrayLike,
|
||||||
Class,
|
Class,
|
||||||
|
EvenInteger,
|
||||||
Falsy,
|
Falsy,
|
||||||
|
FiniteNumber,
|
||||||
|
Integer,
|
||||||
|
NaN as NaNType,
|
||||||
|
NegativeInfinity,
|
||||||
|
NegativeInteger,
|
||||||
|
NegativeNumber,
|
||||||
NodeStream,
|
NodeStream,
|
||||||
NonEmptyString,
|
NonEmptyString,
|
||||||
|
NonNegativeInteger,
|
||||||
|
NonNegativeNumber,
|
||||||
ObservableLike,
|
ObservableLike,
|
||||||
|
OddInteger,
|
||||||
Predicate,
|
Predicate,
|
||||||
Primitive,
|
Primitive,
|
||||||
|
PositiveInfinity,
|
||||||
|
PositiveInteger,
|
||||||
|
PositiveNumber,
|
||||||
|
SafeInteger,
|
||||||
TypedArray,
|
TypedArray,
|
||||||
UrlString,
|
UrlString,
|
||||||
|
ValidLength,
|
||||||
WeakRef,
|
WeakRef,
|
||||||
Whitespace,
|
Whitespace,
|
||||||
} from './types.ts';
|
} from './types.ts';
|
||||||
|
|
@ -22,6 +37,15 @@ type ExtractFromGlobalConstructors<Name extends string> =
|
||||||
|
|
||||||
type NodeBuffer = ExtractFromGlobalConstructors<'Buffer'>;
|
type NodeBuffer = ExtractFromGlobalConstructors<'Buffer'>;
|
||||||
|
|
||||||
|
type NumericGuardResult<Input, Branded extends number> =
|
||||||
|
(
|
||||||
|
unknown extends Input
|
||||||
|
? Branded
|
||||||
|
: Input extends number
|
||||||
|
? Branded & Input
|
||||||
|
: number
|
||||||
|
) & Input;
|
||||||
|
|
||||||
const typedArrayTypeNames = [
|
const typedArrayTypeNames = [
|
||||||
'Int8Array',
|
'Int8Array',
|
||||||
'Uint8Array',
|
'Uint8Array',
|
||||||
|
|
@ -99,6 +123,7 @@ function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName {
|
||||||
export type TypeName = ObjectTypeName | PrimitiveTypeName;
|
export type TypeName = ObjectTypeName | PrimitiveTypeName;
|
||||||
|
|
||||||
const assertionTypeDescriptions = [
|
const assertionTypeDescriptions = [
|
||||||
|
'bound Function',
|
||||||
'positive number',
|
'positive number',
|
||||||
'negative number',
|
'negative number',
|
||||||
'Class',
|
'Class',
|
||||||
|
|
@ -139,6 +164,7 @@ const assertionTypeDescriptions = [
|
||||||
'non-negative number',
|
'non-negative number',
|
||||||
'odd integer',
|
'odd integer',
|
||||||
'positive integer',
|
'positive integer',
|
||||||
|
'safe integer',
|
||||||
'T',
|
'T',
|
||||||
'in range',
|
'in range',
|
||||||
'predicate returns truthy for any value',
|
'predicate returns truthy for any value',
|
||||||
|
|
@ -225,8 +251,7 @@ function detect(value: unknown): TypeName {
|
||||||
return 'Promise';
|
return 'Promise';
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectTag = Object.prototype.toString.call(value).slice(8, -1);
|
if (isBoxedPrimitiveObject(value)) {
|
||||||
if (objectTag === 'String' || objectTag === 'Boolean' || objectTag === 'Number') {
|
|
||||||
throw new TypeError('Please don\'t use object wrappers for primitive types');
|
throw new TypeError('Please don\'t use object wrappers for primitive types');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -237,6 +262,23 @@ function hasPromiseApi<T = unknown>(value: unknown): value is Promise<T> {
|
||||||
return isFunction((value as Promise<T>)?.then) && isFunction((value as Promise<T>)?.catch);
|
return isFunction((value as Promise<T>)?.then) && isFunction((value as Promise<T>)?.catch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hasBoxedPrimitiveBrand(value: unknown, valueOf: () => unknown): boolean {
|
||||||
|
try {
|
||||||
|
// `Object.prototype.toString` can be spoofed via `Symbol.toStringTag`, but the
|
||||||
|
// boxed primitive `valueOf` methods still enforce the real internal brand.
|
||||||
|
Reflect.apply(valueOf, value, []);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBoxedPrimitiveObject(value: unknown): boolean {
|
||||||
|
return hasBoxedPrimitiveBrand(value, String.prototype.valueOf)
|
||||||
|
|| hasBoxedPrimitiveBrand(value, Boolean.prototype.valueOf)
|
||||||
|
|| hasBoxedPrimitiveBrand(value, Number.prototype.valueOf);
|
||||||
|
}
|
||||||
|
|
||||||
const is = Object.assign(
|
const is = Object.assign(
|
||||||
detect,
|
detect,
|
||||||
{
|
{
|
||||||
|
|
@ -560,11 +602,13 @@ export function isEnumCase<T = unknown>(value: unknown, targetEnum: T): value is
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isError(value: unknown): value is Error {
|
export function isError(value: unknown): value is Error {
|
||||||
// TODO: Use `Error.isError` when targeting Node.js 24.`
|
// TODO: Use `Error.isError` when targeting Node.js 24.
|
||||||
return getObjectType(value) === 'Error';
|
return getObjectType(value) === 'Error';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isEvenInteger(value: unknown): value is number {
|
// For numeric guards, preserve branded narrowing for `unknown`, keep the false branch usable for plain `number`, and still narrow mixed unions to `number`.
|
||||||
|
export function isEvenInteger<Input>(value: Input): value is NumericGuardResult<Input, EvenInteger>;
|
||||||
|
export function isEvenInteger(value: unknown): boolean {
|
||||||
return isAbsoluteModule2(0)(value);
|
return isAbsoluteModule2(0)(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -573,7 +617,8 @@ export function isFalsy(value: unknown): value is Falsy {
|
||||||
return !value;
|
return !value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFiniteNumber(value: unknown): value is number {
|
export function isFiniteNumber<Input>(value: Input): value is NumericGuardResult<Input, FiniteNumber>;
|
||||||
|
export function isFiniteNumber(value: unknown): boolean {
|
||||||
return Number.isFinite(value);
|
return Number.isFinite(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -622,7 +667,8 @@ export function isHtmlElement(value: unknown): value is HTMLElement {
|
||||||
&& DOM_PROPERTIES_TO_CHECK.every(property => property in value);
|
&& DOM_PROPERTIES_TO_CHECK.every(property => property in value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isInfinite(value: unknown): value is number {
|
export function isInfinite<Input>(value: Input): value is NumericGuardResult<Input, PositiveInfinity | NegativeInfinity>;
|
||||||
|
export function isInfinite(value: unknown): boolean {
|
||||||
return value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY;
|
return value === Number.POSITIVE_INFINITY || value === Number.NEGATIVE_INFINITY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -654,7 +700,8 @@ export function isInt8Array(value: unknown): value is Int8Array {
|
||||||
return getObjectType(value) === 'Int8Array';
|
return getObjectType(value) === 'Int8Array';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isInteger(value: unknown): value is number {
|
export function isInteger<Input>(value: Input): value is NumericGuardResult<Input, Integer>;
|
||||||
|
export function isInteger(value: unknown): boolean {
|
||||||
return Number.isInteger(value);
|
return Number.isInteger(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -666,7 +713,8 @@ export function isMap<Key = unknown, Value = unknown>(value: unknown): value is
|
||||||
return getObjectType(value) === 'Map';
|
return getObjectType(value) === 'Map';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNan(value: unknown) {
|
export function isNan<Input>(value: Input): value is NumericGuardResult<Input, NaNType>;
|
||||||
|
export function isNan(value: unknown): boolean {
|
||||||
return Number.isNaN(value);
|
return Number.isNaN(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -674,11 +722,13 @@ export function isNativePromise<T = unknown>(value: unknown): value is Promise<T
|
||||||
return getObjectType(value) === 'Promise';
|
return getObjectType(value) === 'Promise';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNegativeInteger(value: unknown): value is number {
|
export function isNegativeInteger<Input>(value: Input): value is NumericGuardResult<Input, NegativeInteger>;
|
||||||
|
export function isNegativeInteger(value: unknown): boolean {
|
||||||
return isInteger(value) && value < 0;
|
return isInteger(value) && value < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNegativeNumber(value: unknown): value is number {
|
export function isNegativeNumber<Input>(value: Input): value is NumericGuardResult<Input, NegativeNumber>;
|
||||||
|
export function isNegativeNumber(value: unknown): boolean {
|
||||||
return isNumber(value) && value < 0;
|
return isNumber(value) && value < 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -714,11 +764,13 @@ export function isNonEmptyStringAndNotWhitespace(value: unknown): value is NonEm
|
||||||
return isString(value) && !isEmptyStringOrWhitespace(value);
|
return isString(value) && !isEmptyStringOrWhitespace(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNonNegativeInteger(value: unknown): value is number {
|
export function isNonNegativeInteger<Input>(value: Input): value is NumericGuardResult<Input, NonNegativeInteger>;
|
||||||
|
export function isNonNegativeInteger(value: unknown): boolean {
|
||||||
return isInteger(value) && value >= 0;
|
return isInteger(value) && value >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isNonNegativeNumber(value: unknown): value is number {
|
export function isNonNegativeNumber<Input>(value: Input): value is NumericGuardResult<Input, NonNegativeNumber>;
|
||||||
|
export function isNonNegativeNumber(value: unknown): boolean {
|
||||||
return isNumber(value) && value >= 0;
|
return isNumber(value) && value >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -763,7 +815,8 @@ export function isObservable(value: unknown): value is ObservableLike {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isOddInteger(value: unknown): value is number {
|
export function isOddInteger<Input>(value: Input): value is NumericGuardResult<Input, OddInteger>;
|
||||||
|
export function isOddInteger(value: unknown): boolean {
|
||||||
return isAbsoluteModule2(1)(value);
|
return isAbsoluteModule2(1)(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -783,11 +836,13 @@ 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);
|
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 {
|
export function isPositiveInteger<Input>(value: Input): value is NumericGuardResult<Input, PositiveInteger>;
|
||||||
|
export function isPositiveInteger(value: unknown): boolean {
|
||||||
return isInteger(value) && value > 0;
|
return isInteger(value) && value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isPositiveNumber(value: unknown): value is number {
|
export function isPositiveNumber<Input>(value: Input): value is NumericGuardResult<Input, PositiveNumber>;
|
||||||
|
export function isPositiveNumber(value: unknown): boolean {
|
||||||
return isNumber(value) && value > 0;
|
return isNumber(value) && value > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -808,7 +863,8 @@ export function isRegExp(value: unknown): value is RegExp {
|
||||||
return getObjectType(value) === 'RegExp';
|
return getObjectType(value) === 'RegExp';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isSafeInteger(value: unknown): value is number {
|
export function isSafeInteger<Input>(value: Input): value is NumericGuardResult<Input, SafeInteger>;
|
||||||
|
export function isSafeInteger(value: unknown): boolean {
|
||||||
return Number.isSafeInteger(value);
|
return Number.isSafeInteger(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -900,7 +956,8 @@ export function isValidDate(value: unknown): value is Date {
|
||||||
return isDate(value) && !isNan(Number(value));
|
return isDate(value) && !isNan(Number(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValidLength(value: unknown): value is number {
|
export function isValidLength<Input>(value: Input): value is NumericGuardResult<Input, ValidLength>;
|
||||||
|
export function isValidLength(value: unknown): boolean {
|
||||||
return isSafeInteger(value) && value >= 0;
|
return isSafeInteger(value) && value >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -956,6 +1013,8 @@ function typeErrorMessageMultipleValues(expectedType: AssertionTypeDescription |
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type assertions have to be declared with an explicit type.
|
// Type assertions have to be declared with an explicit type.
|
||||||
|
// Keep assertion outputs unbranded even when the corresponding `is.*` guard uses a branded subtype.
|
||||||
|
// The brands exist to preserve useful false-branch narrowing for type guards on `number` inputs, which does not apply to `asserts`.
|
||||||
type Assert = {
|
type Assert = {
|
||||||
// Unknowns.
|
// Unknowns.
|
||||||
undefined: (value: unknown, message?: string) => asserts value is undefined;
|
undefined: (value: unknown, message?: string) => asserts value is undefined;
|
||||||
|
|
@ -1188,7 +1247,7 @@ const methodTypeMap = {
|
||||||
isBigUint64Array: 'BigUint64Array',
|
isBigUint64Array: 'BigUint64Array',
|
||||||
isBlob: 'Blob',
|
isBlob: 'Blob',
|
||||||
isBoolean: 'boolean',
|
isBoolean: 'boolean',
|
||||||
isBoundFunction: 'Function',
|
isBoundFunction: 'bound Function',
|
||||||
isBuffer: 'Buffer',
|
isBuffer: 'Buffer',
|
||||||
isClass: 'Class',
|
isClass: 'Class',
|
||||||
isDataView: 'DataView',
|
isDataView: 'DataView',
|
||||||
|
|
@ -1247,7 +1306,7 @@ const methodTypeMap = {
|
||||||
isPromise: 'Promise',
|
isPromise: 'Promise',
|
||||||
isPropertyKey: 'PropertyKey',
|
isPropertyKey: 'PropertyKey',
|
||||||
isRegExp: 'RegExp',
|
isRegExp: 'RegExp',
|
||||||
isSafeInteger: 'integer',
|
isSafeInteger: 'safe integer',
|
||||||
isSet: 'Set',
|
isSet: 'Set',
|
||||||
isSharedArrayBuffer: 'SharedArrayBuffer',
|
isSharedArrayBuffer: 'SharedArrayBuffer',
|
||||||
isString: 'string',
|
isString: 'string',
|
||||||
|
|
@ -1391,7 +1450,7 @@ export function assertBoolean(value: unknown, message?: string): asserts value i
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
|
||||||
export function assertBoundFunction(value: unknown, message?: string): asserts value is Function {
|
export function assertBoundFunction(value: unknown, message?: string): asserts value is Function {
|
||||||
if (!isBoundFunction(value)) {
|
if (!isBoundFunction(value)) {
|
||||||
throw new TypeError(message ?? typeErrorMessage('Function', value));
|
throw new TypeError(message ?? typeErrorMessage('bound Function', value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1752,7 +1811,7 @@ export function assertRegExp(value: unknown, message?: string): asserts value is
|
||||||
|
|
||||||
export function assertSafeInteger(value: unknown, message?: string): asserts value is number {
|
export function assertSafeInteger(value: unknown, message?: string): asserts value is number {
|
||||||
if (!isSafeInteger(value)) {
|
if (!isSafeInteger(value)) {
|
||||||
throw new TypeError(message ?? typeErrorMessage('integer', value));
|
throw new TypeError(message ?? typeErrorMessage('safe integer', value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1891,10 +1950,25 @@ export default is;
|
||||||
export type {
|
export type {
|
||||||
ArrayLike,
|
ArrayLike,
|
||||||
Class,
|
Class,
|
||||||
|
EvenInteger,
|
||||||
|
FiniteNumber,
|
||||||
|
Integer,
|
||||||
|
NaN,
|
||||||
|
NegativeInfinity,
|
||||||
|
NegativeInteger,
|
||||||
|
NegativeNumber,
|
||||||
NodeStream,
|
NodeStream,
|
||||||
|
NonNegativeInteger,
|
||||||
|
NonNegativeNumber,
|
||||||
ObservableLike,
|
ObservableLike,
|
||||||
|
OddInteger,
|
||||||
|
PositiveInfinity,
|
||||||
|
PositiveInteger,
|
||||||
|
PositiveNumber,
|
||||||
Predicate,
|
Predicate,
|
||||||
Primitive,
|
Primitive,
|
||||||
|
SafeInteger,
|
||||||
TypedArray,
|
TypedArray,
|
||||||
UrlString,
|
UrlString,
|
||||||
|
ValidLength,
|
||||||
} from './types.ts';
|
} from './types.ts';
|
||||||
|
|
|
||||||
113
source/types.ts
113
source/types.ts
|
|
@ -78,9 +78,122 @@ export type NonEmptyString = string & {0: string};
|
||||||
|
|
||||||
export type Whitespace = ' ';
|
export type Whitespace = ' ';
|
||||||
|
|
||||||
|
type Brand<Key extends string> = Readonly<Record<Key, true>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A string that represents a valid URL.
|
A string that represents a valid URL.
|
||||||
|
|
||||||
This is a branded type to prevent incorrect TypeScript type narrowing.
|
This is a branded type to prevent incorrect TypeScript type narrowing.
|
||||||
*/
|
*/
|
||||||
export type UrlString = string & {readonly __brand: 'UrlString'};
|
export type UrlString = string & {readonly __brand: 'UrlString'};
|
||||||
|
|
||||||
|
// Keep numeric guards branded and simple. This intentionally favors correct false-branch narrowing for `number` inputs over perfect success-branch narrowing for numeric literal unions.
|
||||||
|
|
||||||
|
/**
|
||||||
|
The IEEE 754 "Not-a-Number" value, typed as a subtype of `number`.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type NaN = number & Brand<'__nanBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A finite number (excludes `NaN`, `Infinity`, and `-Infinity`).
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type FiniteNumber = number & Brand<'__finiteNumberBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A number greater than or equal to zero.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type NonNegativeNumber = number & Brand<'__nonNegativeNumberBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An integer value (no fractional part).
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type Integer = FiniteNumber & Brand<'__integerBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A number greater than zero.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type PositiveNumber = NonNegativeNumber & Brand<'__positiveNumberBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A number less than zero.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type NegativeNumber = number & Brand<'__negativeNumberBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An integer less than zero.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type NegativeInteger = Integer & NegativeNumber & Brand<'__negativeIntegerBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An integer greater than or equal to zero.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type NonNegativeInteger = Integer & NonNegativeNumber & Brand<'__nonNegativeIntegerBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An integer greater than zero.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type PositiveInteger = NonNegativeInteger & PositiveNumber & Brand<'__positiveIntegerBrand'>;
|
||||||
|
|
||||||
|
// Note: type-fest uses the `1e999` overflow trick to represent these types (since TypeScript has
|
||||||
|
// no built-in Infinity type), but we use branded types here for consistency and to avoid
|
||||||
|
// relying on numeric overflow behavior.
|
||||||
|
|
||||||
|
/**
|
||||||
|
A positive infinite number (`Infinity`).
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type PositiveInfinity = PositiveNumber & Brand<'__positiveInfinityBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A negative infinite number (`-Infinity`).
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type NegativeInfinity = NegativeNumber & Brand<'__negativeInfinityBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A safe integer (within the range of `Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`).
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type SafeInteger = Integer & Brand<'__safeIntegerBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An even integer.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type EvenInteger = Integer & Brand<'__evenIntegerBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
An odd integer.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type OddInteger = Integer & Brand<'__oddIntegerBrand'>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
A non-negative safe integer, suitable as an array or string length.
|
||||||
|
|
||||||
|
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||||
|
*/
|
||||||
|
export type ValidLength = SafeInteger & NonNegativeInteger & Brand<'__validLengthBrand'>;
|
||||||
|
|
|
||||||
447
test/test.ts
447
test/test.ts
|
|
@ -15,6 +15,7 @@ import is, {
|
||||||
assert as isAssert,
|
assert as isAssert,
|
||||||
assertPropertyKey,
|
assertPropertyKey,
|
||||||
type AssertionTypeDescription,
|
type AssertionTypeDescription,
|
||||||
|
type NaN as NaNType,
|
||||||
type Predicate,
|
type Predicate,
|
||||||
type Primitive,
|
type Primitive,
|
||||||
type TypedArray,
|
type TypedArray,
|
||||||
|
|
@ -156,7 +157,7 @@ const primitiveTypes = {
|
||||||
safeInteger: {
|
safeInteger: {
|
||||||
fixtures: [...reusableFixtures.integer, ...reusableFixtures.safeInteger],
|
fixtures: [...reusableFixtures.integer, ...reusableFixtures.safeInteger],
|
||||||
typename: 'number',
|
typename: 'number',
|
||||||
typeDescription: 'integer',
|
typeDescription: 'safe integer',
|
||||||
},
|
},
|
||||||
infinite: {
|
infinite: {
|
||||||
fixtures: [...reusableFixtures.infinite],
|
fixtures: [...reusableFixtures.infinite],
|
||||||
|
|
@ -208,6 +209,7 @@ const objectTypes = {
|
||||||
object: {
|
object: {
|
||||||
fixtures: [
|
fixtures: [
|
||||||
Object.create({x: 1}),
|
Object.create({x: 1}),
|
||||||
|
{[Symbol.toStringTag]: 'String'},
|
||||||
...reusableFixtures.plainObject,
|
...reusableFixtures.plainObject,
|
||||||
],
|
],
|
||||||
typename: 'Object',
|
typename: 'Object',
|
||||||
|
|
@ -280,6 +282,7 @@ const objectTypes = {
|
||||||
boundFunction: {
|
boundFunction: {
|
||||||
fixtures: [...reusableFixtures.boundFunction, ...reusableFixtures.asyncFunction],
|
fixtures: [...reusableFixtures.boundFunction, ...reusableFixtures.asyncFunction],
|
||||||
typename: 'Function',
|
typename: 'Function',
|
||||||
|
typeDescription: 'bound Function',
|
||||||
},
|
},
|
||||||
map: {
|
map: {
|
||||||
fixtures: [
|
fixtures: [
|
||||||
|
|
@ -526,6 +529,7 @@ test('is.positiveNumber', () => {
|
||||||
assert.strictEqual(is.positiveNumber(-6), false);
|
assert.strictEqual(is.positiveNumber(-6), false);
|
||||||
assert.strictEqual(is.positiveNumber(-1.4), false);
|
assert.strictEqual(is.positiveNumber(-1.4), false);
|
||||||
assert.strictEqual(is.positiveNumber(Number.NEGATIVE_INFINITY), false);
|
assert.strictEqual(is.positiveNumber(Number.NEGATIVE_INFINITY), false);
|
||||||
|
assert.strictEqual(is.positiveNumber(Number.NaN), false);
|
||||||
|
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
isAssert.positiveNumber(0);
|
isAssert.positiveNumber(0);
|
||||||
|
|
@ -544,6 +548,33 @@ test('is.positiveNumber', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('is.nan', () => {
|
||||||
|
assert.ok(is.nan(Number.NaN));
|
||||||
|
assert.ok(is.nan(NaN)); // eslint-disable-line unicorn/prefer-number-properties
|
||||||
|
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
isAssert.nan(Number.NaN);
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(is.nan(0), false);
|
||||||
|
assert.strictEqual(is.nan(-0), false);
|
||||||
|
assert.strictEqual(is.nan(1), false);
|
||||||
|
assert.strictEqual(is.nan(Number.POSITIVE_INFINITY), false);
|
||||||
|
assert.strictEqual(is.nan(Number.NEGATIVE_INFINITY), false);
|
||||||
|
assert.strictEqual(is.nan('NaN'), false);
|
||||||
|
assert.strictEqual(is.nan(undefined), false);
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
isAssert.nan(0);
|
||||||
|
});
|
||||||
|
assert.throws(() => {
|
||||||
|
isAssert.nan(1);
|
||||||
|
});
|
||||||
|
assert.throws(() => {
|
||||||
|
isAssert.nan('NaN');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('is.finiteNumber', () => {
|
test('is.finiteNumber', () => {
|
||||||
assert.ok(is.finiteNumber(6));
|
assert.ok(is.finiteNumber(6));
|
||||||
assert.ok(is.finiteNumber(-6));
|
assert.ok(is.finiteNumber(-6));
|
||||||
|
|
@ -592,6 +623,7 @@ test('is.negativeNumber', () => {
|
||||||
assert.strictEqual(is.negativeNumber(6), false);
|
assert.strictEqual(is.negativeNumber(6), false);
|
||||||
assert.strictEqual(is.negativeNumber(1.4), false);
|
assert.strictEqual(is.negativeNumber(1.4), false);
|
||||||
assert.strictEqual(is.negativeNumber(Number.POSITIVE_INFINITY), false);
|
assert.strictEqual(is.negativeNumber(Number.POSITIVE_INFINITY), false);
|
||||||
|
assert.strictEqual(is.negativeNumber(Number.NaN), false);
|
||||||
|
|
||||||
assert.throws(() => {
|
assert.throws(() => {
|
||||||
isAssert.negativeNumber(0);
|
isAssert.negativeNumber(0);
|
||||||
|
|
@ -729,6 +761,32 @@ test('is.nonNegativeInteger', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('is.infinite', () => {
|
||||||
|
assert.ok(is.infinite(Number.POSITIVE_INFINITY));
|
||||||
|
assert.ok(is.infinite(Number.NEGATIVE_INFINITY));
|
||||||
|
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
isAssert.infinite(Number.POSITIVE_INFINITY);
|
||||||
|
});
|
||||||
|
assert.doesNotThrow(() => {
|
||||||
|
isAssert.infinite(Number.NEGATIVE_INFINITY);
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.strictEqual(is.infinite(0), false);
|
||||||
|
assert.strictEqual(is.infinite(1), false);
|
||||||
|
assert.strictEqual(is.infinite(-1), false);
|
||||||
|
assert.strictEqual(is.infinite(Number.NaN), false);
|
||||||
|
assert.strictEqual(is.infinite(Number.MAX_VALUE), false);
|
||||||
|
assert.strictEqual(is.infinite('Infinity'), false);
|
||||||
|
|
||||||
|
assert.throws(() => {
|
||||||
|
isAssert.infinite(0);
|
||||||
|
});
|
||||||
|
assert.throws(() => {
|
||||||
|
isAssert.infinite(Number.NaN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('is.numericString supplemental', () => {
|
test('is.numericString supplemental', () => {
|
||||||
assert.strictEqual(is.numericString(''), false);
|
assert.strictEqual(is.numericString(''), false);
|
||||||
assert.strictEqual(is.numericString(' '), false);
|
assert.strictEqual(is.numericString(' '), false);
|
||||||
|
|
@ -998,6 +1056,20 @@ test('is.urlString', () => {
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Type test for is.nan branded-type narrowing
|
||||||
|
(() => {
|
||||||
|
const value: unknown = Number.NaN;
|
||||||
|
|
||||||
|
if (is.nan(value)) {
|
||||||
|
// ✅ In true branch: value is narrowed to the branded NaN type
|
||||||
|
expectTypeOf(value).toEqualTypeOf<NaNType>();
|
||||||
|
expectTypeOf(value).toMatchTypeOf<number>();
|
||||||
|
} else {
|
||||||
|
// ✅ In false branch: value remains unknown (not incorrectly narrowed)
|
||||||
|
expectTypeOf(value).toEqualTypeOf<unknown>();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
test('is.truthy', () => {
|
test('is.truthy', () => {
|
||||||
assert.ok(is.truthy('unicorn'));
|
assert.ok(is.truthy('unicorn'));
|
||||||
assert.ok(is.truthy('🦄'));
|
assert.ok(is.truthy('🦄'));
|
||||||
|
|
@ -2268,370 +2340,379 @@ test('assert', () => {
|
||||||
test('custom assertion message', () => {
|
test('custom assertion message', () => {
|
||||||
const message = 'Custom error message';
|
const message = 'Custom error message';
|
||||||
|
|
||||||
assert.throws(() => {
|
const assertThrowsTypeErrorWithMessage = (assertion: () => void) => {
|
||||||
|
// `node:assert` does not verify the error class when matching only on `{message}`.
|
||||||
|
assert.throws(assertion, error => {
|
||||||
|
assert.ok(error instanceof TypeError);
|
||||||
|
assert.strictEqual(error.message, message);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.array(undefined, undefined, message);
|
isAssert.array(undefined, undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.arrayBuffer(undefined, message);
|
isAssert.arrayBuffer(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.arrayLike(undefined, message);
|
isAssert.arrayLike(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.asyncFunction(undefined, message);
|
isAssert.asyncFunction(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.asyncGenerator(undefined, message);
|
isAssert.asyncGenerator(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.asyncGeneratorFunction(undefined, message);
|
isAssert.asyncGeneratorFunction(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.asyncIterable(undefined, message);
|
isAssert.asyncIterable(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.bigInt64Array(undefined, message);
|
isAssert.bigInt64Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.bigUint64Array(undefined, message);
|
isAssert.bigUint64Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.bigint(undefined, message);
|
isAssert.bigint(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.blob(undefined, message);
|
isAssert.blob(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.boolean(undefined, message);
|
isAssert.boolean(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.boundFunction(undefined, message);
|
isAssert.boundFunction(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.buffer(undefined, message);
|
isAssert.buffer(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.class(undefined, message);
|
isAssert.class(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.dataView(undefined, message);
|
isAssert.dataView(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.date(undefined, message);
|
isAssert.date(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.directInstanceOf(undefined, Error, message);
|
isAssert.directInstanceOf(undefined, Error, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.emptyArray(undefined, message);
|
isAssert.emptyArray(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.emptyMap(undefined, message);
|
isAssert.emptyMap(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.emptyObject(undefined, message);
|
isAssert.emptyObject(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.emptySet(undefined, message);
|
isAssert.emptySet(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.emptyString(undefined, message);
|
isAssert.emptyString(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.emptyStringOrWhitespace(undefined, message);
|
isAssert.emptyStringOrWhitespace(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
enum Enum {}
|
enum Enum {}
|
||||||
isAssert.enumCase('invalid', Enum, message);
|
isAssert.enumCase('invalid', Enum, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.error(undefined, message);
|
isAssert.error(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.evenInteger(33, message);
|
isAssert.evenInteger(33, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.falsy(true, message);
|
isAssert.falsy(true, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.finiteNumber(Number.POSITIVE_INFINITY, message);
|
isAssert.finiteNumber(Number.POSITIVE_INFINITY, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.float32Array(undefined, message);
|
isAssert.float32Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.float64Array(undefined, message);
|
isAssert.float64Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.formData(undefined, message);
|
isAssert.formData(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.function(undefined, message);
|
isAssert.function(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.generator(undefined, message);
|
isAssert.generator(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.generatorFunction(undefined, message);
|
isAssert.generatorFunction(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.htmlElement(undefined, message);
|
isAssert.htmlElement(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.inRange(5, [1, 2], message);
|
isAssert.inRange(5, [1, 2], message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.infinite(undefined, message);
|
isAssert.infinite(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.int16Array(undefined, message);
|
isAssert.int16Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.int32Array(undefined, message);
|
isAssert.int32Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.int8Array(undefined, message);
|
isAssert.int8Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.integer(undefined, message);
|
isAssert.integer(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.iterable(undefined, message);
|
isAssert.iterable(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.map(undefined, message);
|
isAssert.map(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nan(undefined, message);
|
isAssert.nan(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nativePromise(undefined, message);
|
isAssert.nativePromise(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.negativeNumber(undefined, message);
|
isAssert.negativeNumber(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nodeStream(undefined, message);
|
isAssert.nodeStream(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nonEmptyArray(undefined, message);
|
isAssert.nonEmptyArray(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nonEmptyMap(undefined, message);
|
isAssert.nonEmptyMap(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nonEmptyObject(undefined, message);
|
isAssert.nonEmptyObject(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nonEmptySet(undefined, message);
|
isAssert.nonEmptySet(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nonEmptyString(undefined, message);
|
isAssert.nonEmptyString(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nonEmptyStringAndNotWhitespace(undefined, message);
|
isAssert.nonEmptyStringAndNotWhitespace(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nonNegativeNumber(-1, message);
|
isAssert.nonNegativeNumber(-1, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.null(undefined, message);
|
isAssert.null(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.nullOrUndefined(false, message);
|
isAssert.nullOrUndefined(false, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.number(undefined, message);
|
isAssert.number(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.numericString(undefined, message);
|
isAssert.numericString(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.object(undefined, message);
|
isAssert.object(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.observable(undefined, message);
|
isAssert.observable(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.oddInteger(42, message);
|
isAssert.oddInteger(42, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.plainObject(undefined, message);
|
isAssert.plainObject(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.positiveInteger(0, message);
|
isAssert.positiveInteger(0, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.positiveNumber(undefined, message);
|
isAssert.positiveNumber(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.primitive([], message);
|
isAssert.primitive([], message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.promise(undefined, message);
|
isAssert.promise(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.propertyKey(undefined, message);
|
isAssert.propertyKey(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.regExp(undefined, message);
|
isAssert.regExp(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.safeInteger(undefined, message);
|
isAssert.safeInteger(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.set(undefined, message);
|
isAssert.set(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.sharedArrayBuffer(undefined, message);
|
isAssert.sharedArrayBuffer(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.string(undefined, message);
|
isAssert.string(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.symbol(undefined, message);
|
isAssert.symbol(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.truthy(undefined, message);
|
isAssert.truthy(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.tupleLike(undefined, [], message);
|
isAssert.tupleLike(undefined, [], message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.typedArray(undefined, message);
|
isAssert.typedArray(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.uint16Array(undefined, message);
|
isAssert.uint16Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.uint32Array(undefined, message);
|
isAssert.uint32Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.uint8Array(undefined, message);
|
isAssert.uint8Array(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.uint8ClampedArray(undefined, message);
|
isAssert.uint8ClampedArray(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.undefined(false, message);
|
isAssert.undefined(false, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.urlInstance(undefined, message);
|
isAssert.urlInstance(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.urlSearchParams(undefined, message);
|
isAssert.urlSearchParams(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.urlString(undefined, message);
|
isAssert.urlString(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.validDate(undefined, message);
|
isAssert.validDate(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.validLength(undefined, message);
|
isAssert.validLength(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.weakMap(undefined, message);
|
isAssert.weakMap(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.weakRef(undefined, message);
|
isAssert.weakRef(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.weakSet(undefined, message);
|
isAssert.weakSet(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
|
|
||||||
assert.throws(() => {
|
assertThrowsTypeErrorWithMessage(() => {
|
||||||
isAssert.whitespaceString(undefined, message);
|
isAssert.whitespaceString(undefined, message);
|
||||||
}, {message});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('is.optional', () => {
|
test('is.optional', () => {
|
||||||
|
|
|
||||||
12
test/tsconfig.json
Normal file
12
test/tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "..",
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"../source",
|
||||||
|
"type-tests.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
220
test/type-tests.ts
Normal file
220
test/type-tests.ts
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
import is, {
|
||||||
|
type EvenInteger,
|
||||||
|
type FiniteNumber,
|
||||||
|
type Integer,
|
||||||
|
type NaN as NaNType,
|
||||||
|
type NegativeInfinity,
|
||||||
|
type NegativeInteger,
|
||||||
|
type NegativeNumber,
|
||||||
|
type NonNegativeInteger,
|
||||||
|
type NonNegativeNumber,
|
||||||
|
type OddInteger,
|
||||||
|
type PositiveInfinity,
|
||||||
|
type PositiveInteger,
|
||||||
|
type PositiveNumber,
|
||||||
|
type SafeInteger,
|
||||||
|
type ValidLength,
|
||||||
|
} from '../source/index.ts';
|
||||||
|
|
||||||
|
// For each predicate, verify two things:
|
||||||
|
// 1. True branch narrows to the branded type.
|
||||||
|
// 2. False branch on a `number` input stays `number` (not `never`).
|
||||||
|
// Without the branded types, `Exclude<number, number>` = `never` would break
|
||||||
|
// the common validation-guard pattern: if (!is.X(n)) throw; use(n).
|
||||||
|
|
||||||
|
const nanCheck = (value: number) => {
|
||||||
|
if (is.nan(value)) {
|
||||||
|
const _: NaNType = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const finiteNumberCheck = (value: number) => {
|
||||||
|
if (is.finiteNumber(value)) {
|
||||||
|
const _: FiniteNumber = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nonNegativeNumberCheck = (value: number) => {
|
||||||
|
if (is.nonNegativeNumber(value)) {
|
||||||
|
const _: NonNegativeNumber = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const positiveIntegerCheck = (value: number) => {
|
||||||
|
if (is.positiveInteger(value)) {
|
||||||
|
const _: PositiveInteger = value;
|
||||||
|
const __: Integer = value;
|
||||||
|
const ___: NonNegativeInteger = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const negativeIntegerCheck = (value: number) => {
|
||||||
|
if (is.negativeInteger(value)) {
|
||||||
|
const _: NegativeInteger = value;
|
||||||
|
const __: Integer = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const nonNegativeIntegerCheck = (value: number) => {
|
||||||
|
if (is.nonNegativeInteger(value)) {
|
||||||
|
const _: NonNegativeInteger = value;
|
||||||
|
const __: Integer = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const infiniteCheck = (value: number) => {
|
||||||
|
if (is.infinite(value)) {
|
||||||
|
const _: PositiveInfinity | NegativeInfinity = value;
|
||||||
|
const __: PositiveNumber | NegativeNumber = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const integerCheck = (value: number) => {
|
||||||
|
if (is.integer(value)) {
|
||||||
|
const _: Integer = value;
|
||||||
|
const __: FiniteNumber = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const safeIntegerCheck = (value: number) => {
|
||||||
|
if (is.safeInteger(value)) {
|
||||||
|
const _: SafeInteger = value;
|
||||||
|
const __: Integer = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const evenIntegerCheck = (value: number) => {
|
||||||
|
if (is.evenInteger(value)) {
|
||||||
|
const _: EvenInteger = value;
|
||||||
|
const __: Integer = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const oddIntegerCheck = (value: number) => {
|
||||||
|
if (is.oddInteger(value)) {
|
||||||
|
const _: OddInteger = value;
|
||||||
|
const __: Integer = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const positiveNumberCheck = (value: number) => {
|
||||||
|
if (is.positiveNumber(value)) {
|
||||||
|
const _: PositiveNumber = value;
|
||||||
|
const __: NonNegativeNumber = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const negativeNumberCheck = (value: number) => {
|
||||||
|
if (is.negativeNumber(value)) {
|
||||||
|
const _: NegativeNumber = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validLengthCheck = (value: number) => {
|
||||||
|
if (is.validLength(value)) {
|
||||||
|
const _: ValidLength = value;
|
||||||
|
const __: SafeInteger = value;
|
||||||
|
const ___: NonNegativeInteger = value;
|
||||||
|
} else {
|
||||||
|
const _: number = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const integerUnknownCheck = (value: unknown) => {
|
||||||
|
if (is.integer(value)) {
|
||||||
|
const _: Integer = value;
|
||||||
|
const __: FiniteNumber = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const positiveIntegerUnknownCheck = (value: unknown) => {
|
||||||
|
if (is.positiveInteger(value)) {
|
||||||
|
const _: PositiveInteger = value;
|
||||||
|
const __: NonNegativeInteger = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const integerMixedUnionCheck = (value: string | number) => {
|
||||||
|
if (is.integer(value)) {
|
||||||
|
const _: number = value;
|
||||||
|
} else {
|
||||||
|
const _: string = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const positiveNumberMixedUnionCheck = (value: string | number) => {
|
||||||
|
if (is.positiveNumber(value)) {
|
||||||
|
const _: number = value;
|
||||||
|
} else {
|
||||||
|
const _: string = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const chainedNumericGuardCheck = (value: number) => {
|
||||||
|
if (is.positiveNumber(value) && is.integer(value)) {
|
||||||
|
const _: PositiveNumber = value;
|
||||||
|
const __: Integer = value;
|
||||||
|
const ___: FiniteNumber = value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const distinctNumericBrandsStayDistinct = (
|
||||||
|
positiveInteger: PositiveInteger,
|
||||||
|
negativeInteger: NegativeInteger,
|
||||||
|
validLength: ValidLength,
|
||||||
|
) => {
|
||||||
|
// @ts-expect-error -- Distinct numeric refinements must not collapse into each other.
|
||||||
|
const _: NegativeInteger = positiveInteger;
|
||||||
|
// @ts-expect-error -- ValidLength is non-negative and must not become a signed integer refinement.
|
||||||
|
const __: NegativeInteger = validLength;
|
||||||
|
|
||||||
|
return negativeInteger;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Suppress unused variable warnings
|
||||||
|
nanCheck(42);
|
||||||
|
finiteNumberCheck(42);
|
||||||
|
nonNegativeNumberCheck(42);
|
||||||
|
positiveIntegerCheck(42);
|
||||||
|
negativeIntegerCheck(-1);
|
||||||
|
nonNegativeIntegerCheck(0);
|
||||||
|
infiniteCheck(Number.POSITIVE_INFINITY);
|
||||||
|
integerCheck(1);
|
||||||
|
safeIntegerCheck(1);
|
||||||
|
evenIntegerCheck(2);
|
||||||
|
oddIntegerCheck(1);
|
||||||
|
positiveNumberCheck(1);
|
||||||
|
negativeNumberCheck(-1);
|
||||||
|
validLengthCheck(0);
|
||||||
|
integerUnknownCheck(1);
|
||||||
|
positiveIntegerUnknownCheck(1);
|
||||||
|
integerMixedUnionCheck(1);
|
||||||
|
positiveNumberMixedUnionCheck(1);
|
||||||
|
chainedNumericGuardCheck(1);
|
||||||
|
distinctNumericBrandsStayDistinct(42 as PositiveInteger, -1 as NegativeInteger, 0 as ValidLength);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue