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": {
|
||||
"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"
|
||||
},
|
||||
"files": [
|
||||
|
|
|
|||
116
source/index.ts
116
source/index.ts
|
|
@ -1,14 +1,29 @@
|
|||
import type {
|
||||
ArrayLike,
|
||||
Class,
|
||||
EvenInteger,
|
||||
Falsy,
|
||||
FiniteNumber,
|
||||
Integer,
|
||||
NaN as NaNType,
|
||||
NegativeInfinity,
|
||||
NegativeInteger,
|
||||
NegativeNumber,
|
||||
NodeStream,
|
||||
NonEmptyString,
|
||||
NonNegativeInteger,
|
||||
NonNegativeNumber,
|
||||
ObservableLike,
|
||||
OddInteger,
|
||||
Predicate,
|
||||
Primitive,
|
||||
PositiveInfinity,
|
||||
PositiveInteger,
|
||||
PositiveNumber,
|
||||
SafeInteger,
|
||||
TypedArray,
|
||||
UrlString,
|
||||
ValidLength,
|
||||
WeakRef,
|
||||
Whitespace,
|
||||
} from './types.ts';
|
||||
|
|
@ -22,6 +37,15 @@ type ExtractFromGlobalConstructors<Name extends string> =
|
|||
|
||||
type NodeBuffer = ExtractFromGlobalConstructors<'Buffer'>;
|
||||
|
||||
type NumericGuardResult<Input, Branded extends number> =
|
||||
(
|
||||
unknown extends Input
|
||||
? Branded
|
||||
: Input extends number
|
||||
? Branded & Input
|
||||
: number
|
||||
) & Input;
|
||||
|
||||
const typedArrayTypeNames = [
|
||||
'Int8Array',
|
||||
'Uint8Array',
|
||||
|
|
@ -99,6 +123,7 @@ function isPrimitiveTypeName(name: unknown): name is PrimitiveTypeName {
|
|||
export type TypeName = ObjectTypeName | PrimitiveTypeName;
|
||||
|
||||
const assertionTypeDescriptions = [
|
||||
'bound Function',
|
||||
'positive number',
|
||||
'negative number',
|
||||
'Class',
|
||||
|
|
@ -139,6 +164,7 @@ const assertionTypeDescriptions = [
|
|||
'non-negative number',
|
||||
'odd integer',
|
||||
'positive integer',
|
||||
'safe integer',
|
||||
'T',
|
||||
'in range',
|
||||
'predicate returns truthy for any value',
|
||||
|
|
@ -225,8 +251,7 @@ function detect(value: unknown): TypeName {
|
|||
return 'Promise';
|
||||
}
|
||||
|
||||
const objectTag = Object.prototype.toString.call(value).slice(8, -1);
|
||||
if (objectTag === 'String' || objectTag === 'Boolean' || objectTag === 'Number') {
|
||||
if (isBoxedPrimitiveObject(value)) {
|
||||
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);
|
||||
}
|
||||
|
||||
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(
|
||||
detect,
|
||||
{
|
||||
|
|
@ -560,11 +602,13 @@ export function isEnumCase<T = unknown>(value: unknown, targetEnum: T): value is
|
|||
}
|
||||
|
||||
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';
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -573,7 +617,8 @@ export function isFalsy(value: unknown): value is Falsy {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -622,7 +667,8 @@ export function isHtmlElement(value: unknown): value is HTMLElement {
|
|||
&& 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;
|
||||
}
|
||||
|
||||
|
|
@ -654,7 +700,8 @@ export function isInt8Array(value: unknown): value is 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);
|
||||
}
|
||||
|
||||
|
|
@ -666,7 +713,8 @@ export function isMap<Key = unknown, Value = unknown>(value: unknown): value is
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -674,11 +722,13 @@ export function isNativePromise<T = unknown>(value: unknown): value is Promise<T
|
|||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -714,11 +764,13 @@ export function isNonEmptyStringAndNotWhitespace(value: unknown): value is NonEm
|
|||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -763,7 +815,8 @@ export function isObservable(value: unknown): value is ObservableLike {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -808,7 +863,8 @@ export function isRegExp(value: unknown): value is 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);
|
||||
}
|
||||
|
||||
|
|
@ -900,7 +956,8 @@ export function isValidDate(value: unknown): value is Date {
|
|||
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;
|
||||
}
|
||||
|
||||
|
|
@ -956,6 +1013,8 @@ function typeErrorMessageMultipleValues(expectedType: AssertionTypeDescription |
|
|||
}
|
||||
|
||||
// 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 = {
|
||||
// Unknowns.
|
||||
undefined: (value: unknown, message?: string) => asserts value is undefined;
|
||||
|
|
@ -1188,7 +1247,7 @@ const methodTypeMap = {
|
|||
isBigUint64Array: 'BigUint64Array',
|
||||
isBlob: 'Blob',
|
||||
isBoolean: 'boolean',
|
||||
isBoundFunction: 'Function',
|
||||
isBoundFunction: 'bound Function',
|
||||
isBuffer: 'Buffer',
|
||||
isClass: 'Class',
|
||||
isDataView: 'DataView',
|
||||
|
|
@ -1247,7 +1306,7 @@ const methodTypeMap = {
|
|||
isPromise: 'Promise',
|
||||
isPropertyKey: 'PropertyKey',
|
||||
isRegExp: 'RegExp',
|
||||
isSafeInteger: 'integer',
|
||||
isSafeInteger: 'safe integer',
|
||||
isSet: 'Set',
|
||||
isSharedArrayBuffer: 'SharedArrayBuffer',
|
||||
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
|
||||
export function assertBoundFunction(value: unknown, message?: string): asserts value is Function {
|
||||
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 {
|
||||
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 {
|
||||
ArrayLike,
|
||||
Class,
|
||||
EvenInteger,
|
||||
FiniteNumber,
|
||||
Integer,
|
||||
NaN,
|
||||
NegativeInfinity,
|
||||
NegativeInteger,
|
||||
NegativeNumber,
|
||||
NodeStream,
|
||||
NonNegativeInteger,
|
||||
NonNegativeNumber,
|
||||
ObservableLike,
|
||||
OddInteger,
|
||||
PositiveInfinity,
|
||||
PositiveInteger,
|
||||
PositiveNumber,
|
||||
Predicate,
|
||||
Primitive,
|
||||
SafeInteger,
|
||||
TypedArray,
|
||||
UrlString,
|
||||
ValidLength,
|
||||
} from './types.ts';
|
||||
|
|
|
|||
113
source/types.ts
113
source/types.ts
|
|
@ -78,9 +78,122 @@ export type NonEmptyString = string & {0: string};
|
|||
|
||||
export type Whitespace = ' ';
|
||||
|
||||
type Brand<Key extends string> = Readonly<Record<Key, true>>;
|
||||
|
||||
/**
|
||||
A string that represents a valid URL.
|
||||
|
||||
This is a branded type to prevent incorrect TypeScript type narrowing.
|
||||
*/
|
||||
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,
|
||||
assertPropertyKey,
|
||||
type AssertionTypeDescription,
|
||||
type NaN as NaNType,
|
||||
type Predicate,
|
||||
type Primitive,
|
||||
type TypedArray,
|
||||
|
|
@ -156,7 +157,7 @@ const primitiveTypes = {
|
|||
safeInteger: {
|
||||
fixtures: [...reusableFixtures.integer, ...reusableFixtures.safeInteger],
|
||||
typename: 'number',
|
||||
typeDescription: 'integer',
|
||||
typeDescription: 'safe integer',
|
||||
},
|
||||
infinite: {
|
||||
fixtures: [...reusableFixtures.infinite],
|
||||
|
|
@ -208,6 +209,7 @@ const objectTypes = {
|
|||
object: {
|
||||
fixtures: [
|
||||
Object.create({x: 1}),
|
||||
{[Symbol.toStringTag]: 'String'},
|
||||
...reusableFixtures.plainObject,
|
||||
],
|
||||
typename: 'Object',
|
||||
|
|
@ -280,6 +282,7 @@ const objectTypes = {
|
|||
boundFunction: {
|
||||
fixtures: [...reusableFixtures.boundFunction, ...reusableFixtures.asyncFunction],
|
||||
typename: 'Function',
|
||||
typeDescription: 'bound Function',
|
||||
},
|
||||
map: {
|
||||
fixtures: [
|
||||
|
|
@ -526,6 +529,7 @@ test('is.positiveNumber', () => {
|
|||
assert.strictEqual(is.positiveNumber(-6), false);
|
||||
assert.strictEqual(is.positiveNumber(-1.4), false);
|
||||
assert.strictEqual(is.positiveNumber(Number.NEGATIVE_INFINITY), false);
|
||||
assert.strictEqual(is.positiveNumber(Number.NaN), false);
|
||||
|
||||
assert.throws(() => {
|
||||
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', () => {
|
||||
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(1.4), false);
|
||||
assert.strictEqual(is.negativeNumber(Number.POSITIVE_INFINITY), false);
|
||||
assert.strictEqual(is.negativeNumber(Number.NaN), false);
|
||||
|
||||
assert.throws(() => {
|
||||
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', () => {
|
||||
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', () => {
|
||||
assert.ok(is.truthy('unicorn'));
|
||||
assert.ok(is.truthy('🦄'));
|
||||
|
|
@ -2268,370 +2340,379 @@ test('assert', () => {
|
|||
test('custom assertion 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);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.arrayBuffer(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.arrayLike(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.asyncFunction(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.asyncGenerator(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.asyncGeneratorFunction(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.asyncIterable(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.bigInt64Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.bigUint64Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.bigint(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.blob(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.boolean(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.boundFunction(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.buffer(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.class(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.dataView(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.date(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.directInstanceOf(undefined, Error, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.emptyArray(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.emptyMap(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.emptyObject(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.emptySet(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.emptyString(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.emptyStringOrWhitespace(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
enum Enum {}
|
||||
isAssert.enumCase('invalid', Enum, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.error(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.evenInteger(33, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.falsy(true, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.finiteNumber(Number.POSITIVE_INFINITY, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.float32Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.float64Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.formData(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.function(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.generator(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.generatorFunction(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.htmlElement(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.inRange(5, [1, 2], message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.infinite(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.int16Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.int32Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.int8Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.integer(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.iterable(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.map(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nan(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nativePromise(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.negativeNumber(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nodeStream(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nonEmptyArray(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nonEmptyMap(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nonEmptyObject(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nonEmptySet(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nonEmptyString(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nonEmptyStringAndNotWhitespace(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nonNegativeNumber(-1, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.null(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.nullOrUndefined(false, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.number(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.numericString(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.object(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.observable(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.oddInteger(42, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.plainObject(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.positiveInteger(0, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.positiveNumber(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.primitive([], message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.promise(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.propertyKey(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.regExp(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.safeInteger(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.set(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.sharedArrayBuffer(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.string(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.symbol(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.truthy(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.tupleLike(undefined, [], message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.typedArray(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.uint16Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.uint32Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.uint8Array(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.uint8ClampedArray(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.undefined(false, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.urlInstance(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.urlSearchParams(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.urlString(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.validDate(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.validLength(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.weakMap(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.weakRef(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.weakSet(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
|
||||
assert.throws(() => {
|
||||
assertThrowsTypeErrorWithMessage(() => {
|
||||
isAssert.whitespaceString(undefined, message);
|
||||
}, {message});
|
||||
});
|
||||
});
|
||||
|
||||
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