Compare commits

...

12 commits
v7.2.0 ... main

Author SHA1 Message Date
Sindre Sorhus
7821031c66 Fix CI 2026-05-11 22:57:22 +09:00
Sindre Sorhus
a439305554 8.1.0 2026-05-11 20:22:19 +09:00
Sindre Sorhus
2d4956e634 Add negative assertion helper
Fixes #220
2026-05-11 20:17:16 +09:00
Sindre Sorhus
48df5c429c 8.0.0 2026-04-09 22:09:01 +07:00
Sindre Sorhus
13febb6b01 Fix some type guards 2026-04-09 14:56:49 +07:00
Sindre Sorhus
cb4ee0e92c Fix isEnumCase incorrectly accepting numeric enum key names 2026-04-09 14:17:12 +07:00
Sindre Sorhus
54fc09406a Add negativeInteger, nonNegativeInteger, arrayOf, and oneOf predicates 2026-04-08 19:13:51 +07:00
Sindre Sorhus
63be5c0c19 Add finiteNumber, nonNegativeNumber, and positiveInteger predicates 2026-04-08 16:26:44 +07:00
Sindre Sorhus
ac46b5400d Fix isInRange silently returning false when range contains NaN 2026-04-08 15:09:53 +07:00
Sindre Sorhus
47415dc46a Fix isNumericString incorrectly accepting strings with surrounding whitespace 2026-04-08 05:51:14 +07:00
Sindre Sorhus
3b40955b02 Fix handling of functions and arrays in isEmptyObject and isNonEmptyObject 2026-04-08 05:51:13 +07:00
Sindre Sorhus
faf700367e Require Node.js 22 2026-04-08 04:38:52 +07:00
12 changed files with 2459 additions and 1168 deletions

View file

@ -10,10 +10,11 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
node-version: node-version:
- 20 - 24
- 22
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v6
- uses: actions/setup-node@v4 - uses: actions/setup-node@v6
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- run: npm install - run: npm install

9
AGENTS.md Normal file
View 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
View file

@ -0,0 +1 @@
AGENTS.md

View file

@ -1,6 +1,6 @@
{ {
"name": "@sindresorhus/is", "name": "@sindresorhus/is",
"version": "7.2.0", "version": "8.1.0",
"description": "Type check values", "description": "Type check values",
"license": "MIT", "license": "MIT",
"repository": "sindresorhus/is", "repository": "sindresorhus/is",
@ -17,11 +17,11 @@
}, },
"sideEffects": false, "sideEffects": false,
"engines": { "engines": {
"node": ">=18" "node": ">=22"
}, },
"scripts": { "scripts": {
"build": "del distribution && tsc", "build": "del distribution && tsc",
"test": "tsc --noEmit && xo && ava", "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": [
@ -51,31 +51,26 @@
"typeguards", "typeguards",
"types" "types"
], ],
"devDependencies": { "xo": {
"@sindresorhus/tsconfig": "^6.0.0", "rules": {
"@types/jsdom": "^21.1.7", "@typescript-eslint/no-unsafe-enum-comparison": "off",
"@types/node": "^20.14.10", "@typescript-eslint/no-confusing-void-expression": "off",
"@types/zen-observable": "^0.8.7", "@typescript-eslint/no-unsafe-type-assertion": "off",
"ava": "^6.1.3", "@stylistic/operator-linebreak": "off"
"del-cli": "^5.1.0", }
"expect-type": "^0.19.0",
"jsdom": "^24.1.0",
"rxjs": "^7.8.1",
"tempy": "^3.1.0",
"tsimp": "2.0.11",
"typescript": "5.5.3",
"xo": "^0.58.0",
"zen-observable": "^0.10.0"
}, },
"ava": { "devDependencies": {
"environmentVariables": { "@sindresorhus/tsconfig": "^8.1.0",
"TSIMP_DIAG": "error" "@types/jsdom": "^28.0.1",
}, "@types/node": "^25.5.2",
"extensions": { "@types/zen-observable": "^0.8.7",
"ts": "module" "del-cli": "^7.0.0",
}, "expect-type": "^1.3.0",
"nodeArguments": [ "jsdom": "^29.0.2",
"--import=tsimp/import" "rxjs": "^7.8.2",
] "tempy": "^3.2.0",
"typescript": "6.0.2",
"xo": "^2.0.2",
"zen-observable": "^0.10.0"
} }
} }

View file

@ -130,6 +130,17 @@ is.array(value); // Validate `value` is an array.
is.array(value, is.number); // Validate `value` is an array and all of its items are numbers. is.array(value, is.number); // Validate `value` is an array and all of its items are numbers.
``` ```
##### .arrayOf(predicate)
Returns a type guard that checks if `value` is an array where every item matches the predicate. Useful for composing with other methods.
```js
const isStringArray = is.arrayOf(is.string);
isStringArray(['a', 'b']); //=> true
isStringArray(['a', 1]); //=> false
```
##### .function(value) ##### .function(value)
##### .buffer(value) ##### .buffer(value)
@ -463,6 +474,10 @@ function foo() {
foo(); foo();
``` ```
##### .finiteNumber(value)
Check if `value` is a number and is finite. Excludes `Infinity` and `-Infinity`.
##### .positiveNumber(value) ##### .positiveNumber(value)
Check if `value` is a number and is more than 0. Check if `value` is a number and is more than 0.
@ -471,6 +486,22 @@ Check if `value` is a number and is more than 0.
Check if `value` is a number and is less than 0. Check if `value` is a number and is less than 0.
##### .nonNegativeNumber(value)
Check if `value` is a number and is 0 or more.
##### .positiveInteger(value)
Check if `value` is an integer and is more than 0.
##### .negativeInteger(value)
Check if `value` is an integer and is less than 0.
##### .nonNegativeInteger(value)
Check if `value` is an integer and is 0 or more.
##### .inRange(value, range) ##### .inRange(value, range)
Check if `value` (number) is in the given `range`. The range is an array of two values, lower bound and upper bound, in no specific order. Check if `value` (number) is in the given `range`. The range is an array of two values, lower bound and upper bound, in no specific order.
@ -649,6 +680,17 @@ is.optional(123, is.string);
//=> false //=> false
``` ```
##### .oneOf(values)
Returns a type guard that checks if `value` is one of the given `values`. Best used with `as const` for precise type narrowing.
```ts
const isDirection = is.oneOf(['north', 'south', 'east', 'west'] as const);
isDirection('north'); //=> true
isDirection('up'); //=> false
```
##### .validDate(value) ##### .validDate(value)
Returns `true` if the value is a valid date. Returns `true` if the value is a valid date.
@ -744,6 +786,44 @@ handleMovieRatingApiResponse({rating: 0.87, title: 'The Matrix'});
handleMovieRatingApiResponse({rating: '🦄'}); handleMovieRatingApiResponse({rating: '🦄'});
``` ```
### Negative assertion
Asserts that `value` is not the specified type. Only exact, type-safe negative assertions are exposed.
Supported assertions:
- `assert.not.undefined(value)`
- `assert.not.null(value)`
- `assert.not.nullOrUndefined(value)`
- `assert.not.string(value)`
- `assert.not.boolean(value)`
- `assert.not.symbol(value)`
- `assert.not.bigint(value)`
- `assert.not.primitive(value)`
This intentionally excludes checks that cannot produce a safe TypeScript complement: `number` because `is.number` rejects `NaN`, refinements such as `integer` and `validDate`, and branded structural object checks such as `map` and `date`. Broad object checks such as `object` are also excluded to keep negative assertions limited to primitive and nullish types.
```ts
import {assert} from '@sindresorhus/is';
const value: string | undefined = getValue();
assert.not.undefined(value);
// Throws if `value` is `undefined`. Otherwise, `value` is now typed as `string`.
```
For `unknown` input, exact negative assertions narrow to the remaining representable type:
```ts
const value: unknown = getValue();
assert.not.nullOrUndefined(value);
// `value` is now typed as non-nullish.
assert.not.primitive(value);
// `value` is now typed as `object`.
```
### Optional assertion ### Optional assertion
Asserts that `value` is `undefined` or satisfies the provided `assertion`. Asserts that `value` is `undefined` or satisfies the provided `assertion`.

View file

@ -1,18 +1,33 @@
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.js'; } from './types.ts';
import {keysOf} from './utilities.js'; import {keysOf} from './utilities.ts';
// From type-fest. // From type-fest.
type ExtractFromGlobalConstructors<Name extends string> = type ExtractFromGlobalConstructors<Name extends string> =
@ -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',
@ -133,7 +158,13 @@ const assertionTypeDescriptions = [
'non-empty map', 'non-empty map',
'PropertyKey', 'PropertyKey',
'even integer', 'even integer',
'finite number',
'negative integer',
'non-negative integer',
'non-negative number',
'odd integer', 'odd integer',
'positive integer',
'safe integer',
'T', 'T',
'in range', 'in range',
'predicate returns truthy for any value', 'predicate returns truthy for any value',
@ -150,7 +181,7 @@ export type AssertionTypeDescription = typeof assertionTypeDescriptions[number];
const getObjectType = (value: unknown): ObjectTypeName | undefined => { const getObjectType = (value: unknown): ObjectTypeName | undefined => {
const objectTypeName = Object.prototype.toString.call(value).slice(8, -1); const objectTypeName = Object.prototype.toString.call(value).slice(8, -1);
if (/HTML\w+Element/.test(objectTypeName) && isHtmlElement(value)) { if (/HTML\w+Element/v.test(objectTypeName) && isHtmlElement(value)) {
return 'HTMLElement'; return 'HTMLElement';
} }
@ -166,6 +197,7 @@ function detect(value: unknown): TypeName {
return 'null'; return 'null';
} }
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check
switch (typeof value) { switch (typeof value) {
case 'undefined': { case 'undefined': {
return 'undefined'; return 'undefined';
@ -211,7 +243,7 @@ function detect(value: unknown): TypeName {
} }
const tagType = getObjectType(value); const tagType = getObjectType(value);
if (tagType && tagType !== 'Object') { if (tagType !== undefined && tagType !== 'Object') {
return tagType; return tagType;
} }
@ -219,7 +251,7 @@ function detect(value: unknown): TypeName {
return 'Promise'; return 'Promise';
} }
if (value instanceof String || value instanceof Boolean || value instanceof Number) { if (isBoxedPrimitiveObject(value)) {
throw new TypeError('Please don\'t use object wrappers for primitive types'); throw new TypeError('Please don\'t use object wrappers for primitive types');
} }
@ -230,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,
{ {
@ -238,6 +287,7 @@ const is = Object.assign(
array: isArray, array: isArray,
arrayBuffer: isArrayBuffer, arrayBuffer: isArrayBuffer,
arrayLike: isArrayLike, arrayLike: isArrayLike,
arrayOf: isArrayOf,
asyncFunction: isAsyncFunction, asyncFunction: isAsyncFunction,
asyncGenerator: isAsyncGenerator, asyncGenerator: isAsyncGenerator,
asyncGeneratorFunction: isAsyncGeneratorFunction, asyncGeneratorFunction: isAsyncGeneratorFunction,
@ -264,6 +314,7 @@ const is = Object.assign(
error: isError, error: isError,
evenInteger: isEvenInteger, evenInteger: isEvenInteger,
falsy: isFalsy, falsy: isFalsy,
finiteNumber: isFiniteNumber,
float32Array: isFloat32Array, float32Array: isFloat32Array,
float64Array: isFloat64Array, float64Array: isFloat64Array,
formData: isFormData, formData: isFormData,
@ -281,6 +332,7 @@ const is = Object.assign(
map: isMap, map: isMap,
nan: isNan, nan: isNan,
nativePromise: isNativePromise, nativePromise: isNativePromise,
negativeInteger: isNegativeInteger,
negativeNumber: isNegativeNumber, negativeNumber: isNegativeNumber,
nodeStream: isNodeStream, nodeStream: isNodeStream,
nonEmptyArray: isNonEmptyArray, nonEmptyArray: isNonEmptyArray,
@ -289,6 +341,8 @@ const is = Object.assign(
nonEmptySet: isNonEmptySet, nonEmptySet: isNonEmptySet,
nonEmptyString: isNonEmptyString, nonEmptyString: isNonEmptyString,
nonEmptyStringAndNotWhitespace: isNonEmptyStringAndNotWhitespace, nonEmptyStringAndNotWhitespace: isNonEmptyStringAndNotWhitespace,
nonNegativeInteger: isNonNegativeInteger,
nonNegativeNumber: isNonNegativeNumber,
null: isNull, null: isNull,
nullOrUndefined: isNullOrUndefined, nullOrUndefined: isNullOrUndefined,
number: isNumber, number: isNumber,
@ -296,7 +350,9 @@ const is = Object.assign(
object: isObject, object: isObject,
observable: isObservable, observable: isObservable,
oddInteger: isOddInteger, oddInteger: isOddInteger,
oneOf: isOneOf,
plainObject: isPlainObject, plainObject: isPlainObject,
positiveInteger: isPositiveInteger,
positiveNumber: isPositiveNumber, positiveNumber: isPositiveNumber,
primitive: isPrimitive, primitive: isPrimitive,
promise: isPromise, promise: isPromise,
@ -347,9 +403,13 @@ function validatePredicateArray(predicateArray: readonly Predicate[], allowEmpty
} }
for (const predicate of predicateArray) { for (const predicate of predicateArray) {
if (!isFunction(predicate)) { validatePredicate(predicate);
throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`); }
} }
function validatePredicate(predicate: Predicate) {
if (!isFunction(predicate)) {
throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`);
} }
} }
@ -430,6 +490,10 @@ export function isArrayLike<T = unknown>(value: unknown): value is ArrayLike<T>
return !isNullOrUndefined(value) && !isFunction(value) && isValidLength((value as ArrayLike<T>).length); return !isNullOrUndefined(value) && !isFunction(value) && isValidLength((value as ArrayLike<T>).length);
} }
export function isArrayOf<T>(predicate: (value: unknown) => value is T): (value: unknown) => value is T[] {
return (value: unknown): value is T[] => isArray(value) && value.every(element => predicate(element));
}
export function isAsyncFunction<T = unknown>(value: unknown): value is ((...arguments_: any[]) => Promise<T>) { export function isAsyncFunction<T = unknown>(value: unknown): value is ((...arguments_: any[]) => Promise<T>) {
return getObjectType(value) === 'AsyncFunction'; return getObjectType(value) === 'AsyncFunction';
} }
@ -466,7 +530,7 @@ export function isBoolean(value: unknown): value is boolean {
return value === true || value === false; return value === true || value === false;
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export function isBoundFunction(value: unknown): value is Function { export function isBoundFunction(value: unknown): value is Function {
return isFunction(value) && !Object.hasOwn(value, 'prototype'); return isFunction(value) && !Object.hasOwn(value, 'prototype');
} }
@ -475,12 +539,12 @@ export function isBoundFunction(value: unknown): value is Function {
Note: [Prefer using `Uint8Array` instead of `Buffer`.](https://sindresorhus.com/blog/goodbye-nodejs-buffer) Note: [Prefer using `Uint8Array` instead of `Buffer`.](https://sindresorhus.com/blog/goodbye-nodejs-buffer)
*/ */
export function isBuffer(value: unknown): value is NodeBuffer { export function isBuffer(value: unknown): value is NodeBuffer {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
return (value as any)?.constructor?.isBuffer?.(value) ?? false; return (value as any)?.constructor?.isBuffer?.(value) ?? false;
} }
export function isClass<T = unknown>(value: unknown): value is Class<T> { export function isClass<T = unknown>(value: unknown): value is Class<T> {
return isFunction(value) && /^class(\s+|{)/.test(value.toString()); return isFunction(value) && /^class(?:\s+|\{)/v.test(value.toString());
} }
export function isDataView(value: unknown): value is DataView { export function isDataView(value: unknown): value is DataView {
@ -508,7 +572,7 @@ export function isEmptyMap(value: unknown): value is Map<never, never> {
} }
export function isEmptyObject<Key extends keyof any = string>(value: unknown): value is Record<Key, never> { export function isEmptyObject<Key extends keyof any = string>(value: unknown): value is Record<Key, never> {
return isObject(value) && !isMap(value) && !isSet(value) && Object.keys(value).length === 0; return isObject(value) && !isFunction(value) && !isArray(value) && !isMap(value) && !isSet(value) && Object.keys(value).length === 0;
} }
export function isEmptySet(value: unknown): value is Set<never> { export function isEmptySet(value: unknown): value is Set<never> {
@ -524,16 +588,31 @@ export function isEmptyStringOrWhitespace(value: unknown): value is '' | Whitesp
} }
export function isEnumCase<T = unknown>(value: unknown, targetEnum: T): value is T[keyof T] { export function isEnumCase<T = unknown>(value: unknown, targetEnum: T): value is T[keyof T] {
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument // Numeric enums have reverse mappings (e.g. `Direction[0] = "Up"`), so their runtime object contains both `{ Up: 0 }` and `{ "0": "Up" }`. Filtering out entries that round-trip like a canonical number and point back to an own property leaves only actual enum member values.
return Object.values(targetEnum as any).includes(value as string); const enumObject = targetEnum as Record<PropertyKey, unknown>;
return Object.entries(enumObject).some(([key, enumValue]) => {
if (!isString(enumValue)) {
return enumValue === value;
}
const numericKey = Number(key);
if (Number.isNaN(numericKey) || String(numericKey) !== key) {
return enumValue === value;
}
return enumValue === value && !(Object.hasOwn(enumObject, enumValue) && enumObject[enumValue] === numericKey);
});
} }
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);
} }
@ -542,6 +621,11 @@ export function isFalsy(value: unknown): value is Falsy {
return !value; return !value;
} }
export function isFiniteNumber<Input>(value: Input): value is NumericGuardResult<Input, FiniteNumber>;
export function isFiniteNumber(value: unknown): boolean {
return Number.isFinite(value);
}
// TODO: Support detecting Float16Array when targeting Node.js 24. // TODO: Support detecting Float16Array when targeting Node.js 24.
export function isFloat32Array(value: unknown): value is Float32Array { export function isFloat32Array(value: unknown): value is Float32Array {
@ -556,7 +640,7 @@ export function isFormData(value: unknown): value is FormData {
return getObjectType(value) === 'FormData'; return getObjectType(value) === 'FormData';
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export function isFunction(value: unknown): value is Function { export function isFunction(value: unknown): value is Function {
return typeof value === 'function'; return typeof value === 'function';
} }
@ -569,11 +653,9 @@ export function isGeneratorFunction(value: unknown): value is GeneratorFunction
return getObjectType(value) === 'GeneratorFunction'; return getObjectType(value) === 'GeneratorFunction';
} }
// eslint-disable-next-line @typescript-eslint/naming-convention const NODE_TYPE_ELEMENT = 1; // eslint-disable-line @typescript-eslint/naming-convention
const NODE_TYPE_ELEMENT = 1;
// eslint-disable-next-line @typescript-eslint/naming-convention const DOM_PROPERTIES_TO_CHECK: Array<(keyof HTMLElement)> = [ // eslint-disable-line @typescript-eslint/naming-convention
const DOM_PROPERTIES_TO_CHECK: Array<(keyof HTMLElement)> = [
'innerHTML', 'innerHTML',
'ownerDocument', 'ownerDocument',
'style', 'style',
@ -589,7 +671,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;
} }
@ -599,6 +682,10 @@ export function isInRange(value: number, range: number | [number, number]): valu
} }
if (isArray(range) && range.length === 2) { if (isArray(range) && range.length === 2) {
if (Number.isNaN(range[0]) || Number.isNaN(range[1])) {
throw new TypeError(`Invalid range: ${JSON.stringify(range)}`);
}
return value >= Math.min(...range) && value <= Math.max(...range); return value >= Math.min(...range) && value <= Math.max(...range);
} }
@ -617,7 +704,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);
} }
@ -629,7 +717,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);
} }
@ -637,7 +726,13 @@ export function isNativePromise<T = unknown>(value: unknown): value is Promise<T
return getObjectType(value) === 'Promise'; return getObjectType(value) === 'Promise';
} }
export function isNegativeNumber(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<Input>(value: Input): value is NumericGuardResult<Input, NegativeNumber>;
export function isNegativeNumber(value: unknown): boolean {
return isNumber(value) && value < 0; return isNumber(value) && value < 0;
} }
@ -656,7 +751,7 @@ export function isNonEmptyMap<Key = unknown, Value = unknown>(value: unknown): v
// TODO: Use `not` operator here to remove `Map` and `Set` from type guard: // TODO: Use `not` operator here to remove `Map` and `Set` from type guard:
// - https://github.com/Microsoft/TypeScript/pull/29317 // - https://github.com/Microsoft/TypeScript/pull/29317
export function isNonEmptyObject<Key extends keyof any = string, Value = unknown>(value: unknown): value is Record<Key, Value> { export function isNonEmptyObject<Key extends keyof any = string, Value = unknown>(value: unknown): value is Record<Key, Value> {
return isObject(value) && !isMap(value) && !isSet(value) && Object.keys(value).length > 0; return isObject(value) && !isFunction(value) && !isArray(value) && !isMap(value) && !isSet(value) && Object.keys(value).length > 0;
} }
export function isNonEmptySet<T = unknown>(value: unknown): value is Set<T> { export function isNonEmptySet<T = unknown>(value: unknown): value is Set<T> {
@ -673,12 +768,22 @@ export function isNonEmptyStringAndNotWhitespace(value: unknown): value is NonEm
return isString(value) && !isEmptyStringOrWhitespace(value); return isString(value) && !isEmptyStringOrWhitespace(value);
} }
// eslint-disable-next-line @typescript-eslint/ban-types 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<Input>(value: Input): value is NumericGuardResult<Input, NonNegativeNumber>;
export function isNonNegativeNumber(value: unknown): boolean {
return isNumber(value) && value >= 0;
}
// eslint-disable-next-line @typescript-eslint/no-restricted-types
export function isNull(value: unknown): value is null { export function isNull(value: unknown): value is null {
return value === null; return value === null;
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function isNullOrUndefined(value: unknown): value is null | undefined { export function isNullOrUndefined(value: unknown): value is null | undefined {
return isNull(value) || isUndefined(value); return isNull(value) || isUndefined(value);
} }
@ -688,10 +793,10 @@ export function isNumber(value: unknown): value is number {
} }
export function isNumericString(value: unknown): value is `${number}` { export function isNumericString(value: unknown): value is `${number}` {
return isString(value) && !isEmptyStringOrWhitespace(value) && !Number.isNaN(Number(value)); return isString(value) && !isEmptyStringOrWhitespace(value) && value === value.trim() && !Number.isNaN(Number(value));
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function isObject(value: unknown): value is object { export function isObject(value: unknown): value is object {
return !isNull(value) && (typeof value === 'object' || isFunction(value)); return !isNull(value) && (typeof value === 'object' || isFunction(value));
} }
@ -701,12 +806,12 @@ export function isObservable(value: unknown): value is ObservableLike {
return false; return false;
} }
// eslint-disable-next-line no-use-extend-native/no-use-extend-native, @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (Symbol.observable !== undefined && value === (value as any)[Symbol.observable]?.()) { if (Symbol.observable !== undefined && value === (value as any)[Symbol.observable]?.()) {
return true; return true;
} }
// eslint-disable-next-line @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
if (value === (value as any)['@@observable']?.()) { if (value === (value as any)['@@observable']?.()) {
return true; return true;
} }
@ -714,10 +819,15 @@ 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);
} }
export function isOneOf<T extends readonly unknown[]>(values: T): (value: unknown) => value is T[number] {
return (value: unknown): value is T[number] => values.includes(value);
}
export function isPlainObject<Value = unknown>(value: unknown): value is Record<PropertyKey, Value> { export function isPlainObject<Value = unknown>(value: unknown): value is Record<PropertyKey, Value> {
// From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js // From: https://github.com/sindresorhus/is-plain-obj/blob/main/index.js
if (typeof value !== 'object' || value === null) { if (typeof value !== 'object' || value === null) {
@ -730,7 +840,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 isPositiveNumber(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<Input>(value: Input): value is NumericGuardResult<Input, PositiveNumber>;
export function isPositiveNumber(value: unknown): boolean {
return isNumber(value) && value > 0; return isNumber(value) && value > 0;
} }
@ -742,7 +858,7 @@ export function isPromise<T = unknown>(value: unknown): value is Promise<T> {
return isNativePromise(value) || hasPromiseApi(value); return isNativePromise(value) || hasPromiseApi(value);
} }
// `PropertyKey` is any value that can be used as an object key (string, number, or symbol) // `PropertyKey` is any value that can be used as an object key (string, number, or symbol). Note: NaN is technically `typeof 'number'` and thus fits TypeScript's `PropertyKey`, but we intentionally exclude it here because using NaN as a property key is almost always a mistake.
export function isPropertyKey(value: unknown): value is PropertyKey { export function isPropertyKey(value: unknown): value is PropertyKey {
return isAny([isString, isNumber, isSymbol], value); return isAny([isString, isNumber, isSymbol], value);
} }
@ -751,7 +867,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);
} }
@ -777,8 +894,8 @@ export function isTruthy<T>(value: T | Falsy): value is T {
return Boolean(value); return Boolean(value);
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
type ResolveTypesOfTypeGuardsTuple<TypeGuardsOfT, ResultOfT extends unknown[] = [] > = type ResolveTypesOfTypeGuardsTuple<TypeGuardsOfT, ResultOfT extends unknown[] = []> =
TypeGuardsOfT extends [TypeGuard<infer U>, ...infer TOthers] TypeGuardsOfT extends [TypeGuard<infer U>, ...infer TOthers]
? ResolveTypesOfTypeGuardsTuple<TOthers, [...ResultOfT, U]> ? ResolveTypesOfTypeGuardsTuple<TOthers, [...ResultOfT, U]>
: TypeGuardsOfT extends undefined[] : TypeGuardsOfT extends undefined[]
@ -843,35 +960,34 @@ 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;
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function isWeakMap<Key extends object = object, Value = unknown>(value: unknown): value is WeakMap<Key, Value> { export function isWeakMap<Key extends object = object, Value = unknown>(value: unknown): value is WeakMap<Key, Value> {
return getObjectType(value) === 'WeakMap'; return getObjectType(value) === 'WeakMap';
} }
// eslint-disable-next-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function isWeakRef(value: unknown): value is WeakRef<object> { export function isWeakRef(value: unknown): value is WeakRef<object> {
return getObjectType(value) === 'WeakRef'; return getObjectType(value) === 'WeakRef';
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function isWeakSet(value: unknown): value is WeakSet<object> { export function isWeakSet(value: unknown): value is WeakSet<object> {
return getObjectType(value) === 'WeakSet'; return getObjectType(value) === 'WeakSet';
} }
export function isWhitespaceString(value: unknown): value is Whitespace { export function isWhitespaceString(value: unknown): value is Whitespace {
return isString(value) && /^\s+$/.test(value); return isString(value) && /^\s+$/v.test(value);
} }
type ArrayMethod = (function_: (value: unknown, index: number, array: unknown[]) => boolean, thisArgument?: unknown) => boolean; type ArrayMethod = (function_: (value: unknown, index: number, array: unknown[]) => boolean, thisArgument?: unknown) => boolean;
function predicateOnArray(method: ArrayMethod, predicate: Predicate, values: unknown[]) { function predicateOnArray(method: ArrayMethod, predicate: Predicate, values: unknown[]) {
if (!isFunction(predicate)) { validatePredicate(predicate);
throw new TypeError(`Invalid predicate: ${JSON.stringify(predicate)}`);
}
if (values.length === 0) { if (values.length === 0) {
throw new TypeError('Invalid number of values'); throw new TypeError('Invalid number of values');
@ -884,6 +1000,17 @@ function typeErrorMessage(description: AssertionTypeDescription, value: unknown)
return `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`; return `Expected value which is \`${description}\`, received value of type \`${is(value)}\`.`;
} }
function typeErrorMessageNot(description: AssertionTypeDescription, value: unknown): string {
return `Expected value which is not \`${description}\`, received value of type \`${is(value)}\`.`;
}
type NotAssertionResult<Value, Forbidden, UnknownResult> = Exclude<Value, Forbidden> & ([unknown] extends [Value] ? UnknownResult : unknown);
type NotAssertion<Forbidden, UnknownResult = unknown> = <Value>(value: Value, message?: string) => asserts value is NotAssertionResult<Value, Forbidden, UnknownResult>;
// eslint-disable-next-line @typescript-eslint/no-restricted-types
type UnknownNotPrimitive<Forbidden extends Primitive> = Exclude<Primitive, Forbidden> | object;
function unique<T>(values: T[]): T[] { function unique<T>(values: T[]): T[] {
// eslint-disable-next-line unicorn/prefer-spread // eslint-disable-next-line unicorn/prefer-spread
return Array.from(new Set(values)); return Array.from(new Set(values));
@ -899,17 +1026,24 @@ 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;
string: (value: unknown, message?: string) => asserts value is string; string: (value: unknown, message?: string) => asserts value is string;
number: (value: unknown, message?: string) => asserts value is number; number: (value: unknown, message?: string) => asserts value is number;
finiteNumber: (value: unknown, message?: string) => asserts value is number;
positiveNumber: (value: unknown, message?: string) => asserts value is number; positiveNumber: (value: unknown, message?: string) => asserts value is number;
negativeInteger: (value: unknown, message?: string) => asserts value is number;
negativeNumber: (value: unknown, message?: string) => asserts value is number; negativeNumber: (value: unknown, message?: string) => asserts value is number;
nonNegativeInteger: (value: unknown, message?: string) => asserts value is number;
nonNegativeNumber: (value: unknown, message?: string) => asserts value is number;
positiveInteger: (value: unknown, message?: string) => asserts value is number;
bigint: (value: unknown, message?: string) => asserts value is bigint; bigint: (value: unknown, message?: string) => asserts value is bigint;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
function: (value: unknown, message?: string) => asserts value is Function; function: (value: unknown, message?: string) => asserts value is Function;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
null: (value: unknown, message?: string) => asserts value is null; null: (value: unknown, message?: string) => asserts value is null;
class: <T = unknown>(value: unknown, message?: string) => asserts value is Class<T>; class: <T = unknown>(value: unknown, message?: string) => asserts value is Class<T>;
boolean: (value: unknown, message?: string) => asserts value is boolean; boolean: (value: unknown, message?: string) => asserts value is boolean;
@ -918,7 +1052,7 @@ type Assert = {
array: <T = unknown>(value: unknown, assertion?: (element: unknown) => asserts element is T, message?: string) => asserts value is T[]; array: <T = unknown>(value: unknown, assertion?: (element: unknown) => asserts element is T, message?: string) => asserts value is T[];
buffer: (value: unknown, message?: string) => asserts value is NodeBuffer; buffer: (value: unknown, message?: string) => asserts value is NodeBuffer;
blob: (value: unknown, message?: string) => asserts value is Blob; blob: (value: unknown, message?: string) => asserts value is Blob;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
nullOrUndefined: (value: unknown, message?: string) => asserts value is null | undefined; nullOrUndefined: (value: unknown, message?: string) => asserts value is null | undefined;
object: <Key extends keyof any = string, Value = unknown>(value: unknown, message?: string) => asserts value is Record<Key, Value>; object: <Key extends keyof any = string, Value = unknown>(value: unknown, message?: string) => asserts value is Record<Key, Value>;
iterable: <T = unknown>(value: unknown, message?: string) => asserts value is Iterable<T>; iterable: <T = unknown>(value: unknown, message?: string) => asserts value is Iterable<T>;
@ -929,20 +1063,20 @@ type Assert = {
promise: <T = unknown>(value: unknown, message?: string) => asserts value is Promise<T>; promise: <T = unknown>(value: unknown, message?: string) => asserts value is Promise<T>;
generatorFunction: (value: unknown, message?: string) => asserts value is GeneratorFunction; generatorFunction: (value: unknown, message?: string) => asserts value is GeneratorFunction;
asyncGeneratorFunction: (value: unknown, message?: string) => asserts value is AsyncGeneratorFunction; asyncGeneratorFunction: (value: unknown, message?: string) => asserts value is AsyncGeneratorFunction;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
asyncFunction: (value: unknown, message?: string) => asserts value is Function; asyncFunction: (value: unknown, message?: string) => asserts value is Function;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
boundFunction: (value: unknown, message?: string) => asserts value is Function; boundFunction: (value: unknown, message?: string) => asserts value is Function;
regExp: (value: unknown, message?: string) => asserts value is RegExp; regExp: (value: unknown, message?: string) => asserts value is RegExp;
date: (value: unknown, message?: string) => asserts value is Date; date: (value: unknown, message?: string) => asserts value is Date;
error: (value: unknown, message?: string) => asserts value is Error; error: (value: unknown, message?: string) => asserts value is Error;
map: <Key = unknown, Value = unknown>(value: unknown, message?: string) => asserts value is Map<Key, Value>; map: <Key = unknown, Value = unknown>(value: unknown, message?: string) => asserts value is Map<Key, Value>;
set: <T = unknown>(value: unknown, message?: string) => asserts value is Set<T>; set: <T = unknown>(value: unknown, message?: string) => asserts value is Set<T>;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
weakMap: <Key extends object = object, Value = unknown>(value: unknown, message?: string) => asserts value is WeakMap<Key, Value>; weakMap: <Key extends object = object, Value = unknown>(value: unknown, message?: string) => asserts value is WeakMap<Key, Value>;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
weakSet: <T extends object = object>(value: unknown, message?: string) => asserts value is WeakSet<T>; weakSet: <T extends object = object>(value: unknown, message?: string) => asserts value is WeakSet<T>;
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
weakRef: <T extends object = object>(value: unknown, message?: string) => asserts value is WeakRef<T>; weakRef: <T extends object = object>(value: unknown, message?: string) => asserts value is WeakRef<T>;
int8Array: (value: unknown, message?: string) => asserts value is Int8Array; int8Array: (value: unknown, message?: string) => asserts value is Int8Array;
uint8Array: (value: unknown, message?: string) => asserts value is Uint8Array; uint8Array: (value: unknown, message?: string) => asserts value is Uint8Array;
@ -1002,6 +1136,8 @@ type Assert = {
directInstanceOf: <T>(instance: unknown, class_: Class<T>, message?: string) => asserts instance is T; directInstanceOf: <T>(instance: unknown, class_: Class<T>, message?: string) => asserts instance is T;
inRange: (value: number, range: number | [number, number], message?: string) => asserts value is number; inRange: (value: number, range: number | [number, number], message?: string) => asserts value is number;
not: NotAssert;
// Variadic functions. // Variadic functions.
any: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never; any: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never;
all: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never; all: (predicate: Predicate | readonly Predicate[], ...values: unknown[]) => void | never;
@ -1014,9 +1150,58 @@ type Assert = {
optional: <T>(value: unknown, assertion: (value: unknown, message?: string) => asserts value is T, message?: string) => asserts value is T | undefined; optional: <T>(value: unknown, assertion: (value: unknown, message?: string) => asserts value is T, message?: string) => asserts value is T | undefined;
}; };
type NotAssert = {
undefined: NotAssertion<undefined, UnknownNotPrimitive<undefined>>;
// eslint-disable-next-line @typescript-eslint/no-restricted-types
null: NotAssertion<null, UnknownNotPrimitive<null>>;
// eslint-disable-next-line @typescript-eslint/no-restricted-types
nullOrUndefined: NotAssertion<null | undefined, UnknownNotPrimitive<null | undefined>>;
string: NotAssertion<string, UnknownNotPrimitive<string>>;
boolean: NotAssertion<boolean, UnknownNotPrimitive<boolean>>;
symbol: NotAssertion<symbol, UnknownNotPrimitive<symbol>>;
bigint: NotAssertion<bigint, UnknownNotPrimitive<bigint>>;
// eslint-disable-next-line @typescript-eslint/no-restricted-types
primitive: NotAssertion<Primitive, object>;
};
// Negative assertions are limited to types where the assertion rejects every TypeScript value assignable to the forbidden type. Structural object types such as `Map`, `Set`, `Date`, and `Array` are excluded because TypeScript accepts shape-compatible mocks while the runtime checks use object brands, so `Exclude` would narrow values that can pass the negative assertion.
function createAssertNot<Forbidden, UnknownResult = unknown>(predicate: Predicate, description: AssertionTypeDescription): NotAssertion<Forbidden, UnknownResult> {
return <Value>(value: Value, message?: string): asserts value is NotAssertionResult<Value, Forbidden, UnknownResult> => {
if (predicate(value)) {
throw new TypeError(message ?? typeErrorMessageNot(description, value));
}
};
}
export const assertNotUndefined: NotAssertion<undefined, UnknownNotPrimitive<undefined>> = createAssertNot<undefined, UnknownNotPrimitive<undefined>>(isUndefined, 'undefined');
// eslint-disable-next-line @typescript-eslint/no-restricted-types
export const assertNotNull: NotAssertion<null, UnknownNotPrimitive<null>> = createAssertNot<null, UnknownNotPrimitive<null>>(isNull, 'null');
// eslint-disable-next-line @typescript-eslint/no-restricted-types
export const assertNotNullOrUndefined: NotAssertion<null | undefined, UnknownNotPrimitive<null | undefined>>
// eslint-disable-next-line @typescript-eslint/no-restricted-types
= createAssertNot<null | undefined, UnknownNotPrimitive<null | undefined>>(isNullOrUndefined, 'null or undefined');
export const assertNotString: NotAssertion<string, UnknownNotPrimitive<string>> = createAssertNot<string, UnknownNotPrimitive<string>>(isString, 'string');
export const assertNotBoolean: NotAssertion<boolean, UnknownNotPrimitive<boolean>> = createAssertNot<boolean, UnknownNotPrimitive<boolean>>(isBoolean, 'boolean');
export const assertNotSymbol: NotAssertion<symbol, UnknownNotPrimitive<symbol>> = createAssertNot<symbol, UnknownNotPrimitive<symbol>>(isSymbol, 'symbol');
export const assertNotBigint: NotAssertion<bigint, UnknownNotPrimitive<bigint>> = createAssertNot<bigint, UnknownNotPrimitive<bigint>>(isBigint, 'bigint');
export const assertNotPrimitive: NotAssertion<Primitive, object> = createAssertNot<Primitive, object>(isPrimitive, 'primitive'); // eslint-disable-line @typescript-eslint/no-restricted-types
// We intentionally do not support `assert.not(is.undefined, value)`. TypeScript cannot derive safe complement types from arbitrary predicates, and many predicates here are refinements (for example, `is.number` rejects `NaN`). Explicit methods keep runtime checks and type narrowing aligned.
const notAssertions: NotAssert = {
bigint: assertNotBigint,
boolean: assertNotBoolean,
null: assertNotNull,
nullOrUndefined: assertNotNullOrUndefined,
primitive: assertNotPrimitive,
string: assertNotString,
symbol: assertNotSymbol,
undefined: assertNotUndefined,
};
export const assert: Assert = { export const assert: Assert = {
all: assertAll, all: assertAll,
any: assertAny, any: assertAny,
not: notAssertions,
optional: assertOptional, optional: assertOptional,
array: assertArray, array: assertArray,
arrayBuffer: assertArrayBuffer, arrayBuffer: assertArrayBuffer,
@ -1046,6 +1231,7 @@ export const assert: Assert = {
error: assertError, error: assertError,
evenInteger: assertEvenInteger, evenInteger: assertEvenInteger,
falsy: assertFalsy, falsy: assertFalsy,
finiteNumber: assertFiniteNumber,
float32Array: assertFloat32Array, float32Array: assertFloat32Array,
float64Array: assertFloat64Array, float64Array: assertFloat64Array,
formData: assertFormData, formData: assertFormData,
@ -1063,6 +1249,7 @@ export const assert: Assert = {
map: assertMap, map: assertMap,
nan: assertNan, nan: assertNan,
nativePromise: assertNativePromise, nativePromise: assertNativePromise,
negativeInteger: assertNegativeInteger,
negativeNumber: assertNegativeNumber, negativeNumber: assertNegativeNumber,
nodeStream: assertNodeStream, nodeStream: assertNodeStream,
nonEmptyArray: assertNonEmptyArray, nonEmptyArray: assertNonEmptyArray,
@ -1071,6 +1258,8 @@ export const assert: Assert = {
nonEmptySet: assertNonEmptySet, nonEmptySet: assertNonEmptySet,
nonEmptyString: assertNonEmptyString, nonEmptyString: assertNonEmptyString,
nonEmptyStringAndNotWhitespace: assertNonEmptyStringAndNotWhitespace, nonEmptyStringAndNotWhitespace: assertNonEmptyStringAndNotWhitespace,
nonNegativeInteger: assertNonNegativeInteger,
nonNegativeNumber: assertNonNegativeNumber,
null: assertNull, null: assertNull,
nullOrUndefined: assertNullOrUndefined, nullOrUndefined: assertNullOrUndefined,
number: assertNumber, number: assertNumber,
@ -1079,6 +1268,7 @@ export const assert: Assert = {
observable: assertObservable, observable: assertObservable,
oddInteger: assertOddInteger, oddInteger: assertOddInteger,
plainObject: assertPlainObject, plainObject: assertPlainObject,
positiveInteger: assertPositiveInteger,
positiveNumber: assertPositiveNumber, positiveNumber: assertPositiveNumber,
primitive: assertPrimitive, primitive: assertPrimitive,
promise: assertPromise, promise: assertPromise,
@ -1121,7 +1311,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',
@ -1137,6 +1327,7 @@ const methodTypeMap = {
isError: 'Error', isError: 'Error',
isEvenInteger: 'even integer', isEvenInteger: 'even integer',
isFalsy: 'falsy', isFalsy: 'falsy',
isFiniteNumber: 'finite number',
isFloat32Array: 'Float32Array', isFloat32Array: 'Float32Array',
isFloat64Array: 'Float64Array', isFloat64Array: 'Float64Array',
isFormData: 'FormData', isFormData: 'FormData',
@ -1154,6 +1345,7 @@ const methodTypeMap = {
isMap: 'Map', isMap: 'Map',
isNan: 'NaN', isNan: 'NaN',
isNativePromise: 'native Promise', isNativePromise: 'native Promise',
isNegativeInteger: 'negative integer',
isNegativeNumber: 'negative number', isNegativeNumber: 'negative number',
isNodeStream: 'Node.js Stream', isNodeStream: 'Node.js Stream',
isNonEmptyArray: 'non-empty array', isNonEmptyArray: 'non-empty array',
@ -1162,6 +1354,8 @@ const methodTypeMap = {
isNonEmptySet: 'non-empty set', isNonEmptySet: 'non-empty set',
isNonEmptyString: 'non-empty string', isNonEmptyString: 'non-empty string',
isNonEmptyStringAndNotWhitespace: 'non-empty string and not whitespace', isNonEmptyStringAndNotWhitespace: 'non-empty string and not whitespace',
isNonNegativeInteger: 'non-negative integer',
isNonNegativeNumber: 'non-negative number',
isNull: 'null', isNull: 'null',
isNullOrUndefined: 'null or undefined', isNullOrUndefined: 'null or undefined',
isNumber: 'number', isNumber: 'number',
@ -1170,12 +1364,13 @@ const methodTypeMap = {
isObservable: 'Observable', isObservable: 'Observable',
isOddInteger: 'odd integer', isOddInteger: 'odd integer',
isPlainObject: 'plain object', isPlainObject: 'plain object',
isPositiveInteger: 'positive integer',
isPositiveNumber: 'positive number', isPositiveNumber: 'positive number',
isPrimitive: 'primitive', isPrimitive: 'primitive',
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',
@ -1261,7 +1456,7 @@ export function assertArrayLike<T = unknown>(value: unknown, message?: string):
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export function assertAsyncFunction(value: unknown, message?: string): asserts value is Function { export function assertAsyncFunction(value: unknown, message?: string): asserts value is Function {
if (!isAsyncFunction(value)) { if (!isAsyncFunction(value)) {
throw new TypeError(message ?? typeErrorMessage('AsyncFunction', value)); throw new TypeError(message ?? typeErrorMessage('AsyncFunction', value));
@ -1316,10 +1511,10 @@ export function assertBoolean(value: unknown, message?: string): asserts value i
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types // 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));
} }
} }
@ -1416,6 +1611,12 @@ export function assertFalsy(value: unknown, message?: string): asserts value is
} }
} }
export function assertFiniteNumber(value: unknown, message?: string): asserts value is number {
if (!isFiniteNumber(value)) {
throw new TypeError(message ?? typeErrorMessage('finite number', value));
}
}
export function assertFloat32Array(value: unknown, message?: string): asserts value is Float32Array { export function assertFloat32Array(value: unknown, message?: string): asserts value is Float32Array {
if (!isFloat32Array(value)) { if (!isFloat32Array(value)) {
throw new TypeError(message ?? typeErrorMessage('Float32Array', value)); throw new TypeError(message ?? typeErrorMessage('Float32Array', value));
@ -1434,7 +1635,7 @@ export function assertFormData(value: unknown, message?: string): asserts value
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
export function assertFunction(value: unknown, message?: string): asserts value is Function { export function assertFunction(value: unknown, message?: string): asserts value is Function {
if (!isFunction(value)) { if (!isFunction(value)) {
throw new TypeError(message ?? typeErrorMessage('Function', value)); throw new TypeError(message ?? typeErrorMessage('Function', value));
@ -1519,6 +1720,12 @@ export function assertNativePromise<T = unknown>(value: unknown, message?: strin
} }
} }
export function assertNegativeInteger(value: unknown, message?: string): asserts value is number {
if (!isNegativeInteger(value)) {
throw new TypeError(message ?? typeErrorMessage('negative integer', value));
}
}
export function assertNegativeNumber(value: unknown, message?: string): asserts value is number { export function assertNegativeNumber(value: unknown, message?: string): asserts value is number {
if (!isNegativeNumber(value)) { if (!isNegativeNumber(value)) {
throw new TypeError(message ?? typeErrorMessage('negative number', value)); throw new TypeError(message ?? typeErrorMessage('negative number', value));
@ -1567,14 +1774,26 @@ export function assertNonEmptyStringAndNotWhitespace(value: unknown, message?: s
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types export function assertNonNegativeInteger(value: unknown, message?: string): asserts value is number {
if (!isNonNegativeInteger(value)) {
throw new TypeError(message ?? typeErrorMessage('non-negative integer', value));
}
}
export function assertNonNegativeNumber(value: unknown, message?: string): asserts value is number {
if (!isNonNegativeNumber(value)) {
throw new TypeError(message ?? typeErrorMessage('non-negative number', value));
}
}
// eslint-disable-next-line @typescript-eslint/no-restricted-types
export function assertNull(value: unknown, message?: string): asserts value is null { export function assertNull(value: unknown, message?: string): asserts value is null {
if (!isNull(value)) { if (!isNull(value)) {
throw new TypeError(message ?? typeErrorMessage('null', value)); throw new TypeError(message ?? typeErrorMessage('null', value));
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function assertNullOrUndefined(value: unknown, message?: string): asserts value is null | undefined { export function assertNullOrUndefined(value: unknown, message?: string): asserts value is null | undefined {
if (!isNullOrUndefined(value)) { if (!isNullOrUndefined(value)) {
throw new TypeError(message ?? typeErrorMessage('null or undefined', value)); throw new TypeError(message ?? typeErrorMessage('null or undefined', value));
@ -1593,7 +1812,7 @@ export function assertNumericString(value: unknown, message?: string): asserts v
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function assertObject(value: unknown, message?: string): asserts value is object { export function assertObject(value: unknown, message?: string): asserts value is object {
if (!isObject(value)) { if (!isObject(value)) {
throw new TypeError(message ?? typeErrorMessage('Object', value)); throw new TypeError(message ?? typeErrorMessage('Object', value));
@ -1618,6 +1837,12 @@ export function assertPlainObject<Value = unknown>(value: unknown, message?: str
} }
} }
export function assertPositiveInteger(value: unknown, message?: string): asserts value is number {
if (!isPositiveInteger(value)) {
throw new TypeError(message ?? typeErrorMessage('positive integer', value));
}
}
export function assertPositiveNumber(value: unknown, message?: string): asserts value is number { export function assertPositiveNumber(value: unknown, message?: string): asserts value is number {
if (!isPositiveNumber(value)) { if (!isPositiveNumber(value)) {
throw new TypeError(message ?? typeErrorMessage('positive number', value)); throw new TypeError(message ?? typeErrorMessage('positive number', value));
@ -1636,7 +1861,7 @@ export function assertPromise<T = unknown>(value: unknown, message?: string): as
} }
} }
export function assertPropertyKey(value: unknown, message?: string): asserts value is number { export function assertPropertyKey(value: unknown, message?: string): asserts value is PropertyKey {
if (!isPropertyKey(value)) { if (!isPropertyKey(value)) {
throw new TypeError(message ?? typeErrorMessage('PropertyKey', value)); throw new TypeError(message ?? typeErrorMessage('PropertyKey', value));
} }
@ -1650,7 +1875,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));
} }
} }
@ -1757,21 +1982,21 @@ export function assertValidLength(value: unknown, message?: string): asserts val
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function assertWeakMap<Key extends object = object, Value = unknown>(value: unknown, message?: string): asserts value is WeakMap<Key, Value> { export function assertWeakMap<Key extends object = object, Value = unknown>(value: unknown, message?: string): asserts value is WeakMap<Key, Value> {
if (!isWeakMap(value)) { if (!isWeakMap(value)) {
throw new TypeError(message ?? typeErrorMessage('WeakMap', value)); throw new TypeError(message ?? typeErrorMessage('WeakMap', value));
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function assertWeakRef<T extends object = object>(value: unknown, message?: string): asserts value is WeakRef<T> { export function assertWeakRef<T extends object = object>(value: unknown, message?: string): asserts value is WeakRef<T> {
if (!isWeakRef(value)) { if (!isWeakRef(value)) {
throw new TypeError(message ?? typeErrorMessage('WeakRef', value)); throw new TypeError(message ?? typeErrorMessage('WeakRef', value));
} }
} }
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export function assertWeakSet<T extends object = object>(value: unknown, message?: string): asserts value is WeakSet<T> { export function assertWeakSet<T extends object = object>(value: unknown, message?: string): asserts value is WeakSet<T> {
if (!isWeakSet(value)) { if (!isWeakSet(value)) {
throw new TypeError(message ?? typeErrorMessage('WeakSet', value)); throw new TypeError(message ?? typeErrorMessage('WeakSet', value));
@ -1789,10 +2014,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,
} from './types.js'; ValidLength,
} from './types.ts';

View file

@ -4,7 +4,8 @@
Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive). Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
*/ */
export type Primitive = export type Primitive =
| null // eslint-disable-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
| null
| undefined | undefined
| string | string
| number | number
@ -53,10 +54,11 @@ export type ObservableLike = {
[Symbol.observable](): ObservableLike; [Symbol.observable](): ObservableLike;
}; };
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/no-restricted-types
export type Falsy = false | 0 | 0n | '' | null | undefined; export type Falsy = false | 0 | 0n | '' | null | undefined;
export type WeakRef<T extends object> = { // eslint-disable-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations // eslint-disable-next-line @typescript-eslint/no-restricted-types
export type WeakRef<T extends object> = {
readonly [Symbol.toStringTag]: 'WeakRef'; readonly [Symbol.toStringTag]: 'WeakRef';
deref(): T | undefined; deref(): T | undefined;
}; };
@ -76,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'>;

View file

@ -1,3 +1,3 @@
export function keysOf<T extends Record<PropertyKey, unknown>>(value: T): Array<keyof T> { export function keysOf<T extends Record<PropertyKey, unknown>>(value: T): Array<keyof T> {
return Object.keys(value) as Array<keyof T>; return Object.keys(value) as Array<keyof T>; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
} }

File diff suppressed because it is too large Load diff

12
test/tsconfig.json Normal file
View file

@ -0,0 +1,12 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"rootDir": "..",
"noUnusedLocals": false,
"noUnusedParameters": false
},
"include": [
"../source",
"type-tests.ts"
]
}

405
test/type-tests.ts Normal file
View file

@ -0,0 +1,405 @@
import {expectTypeOf} from 'expect-type';
import is, {
assert as isAssert,
assertNotNullOrUndefined,
assertNotPrimitive,
assertNotString,
assertNotUndefined,
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 Primitive,
type SafeInteger,
type ValidLength,
} from '../source/index.ts';
// eslint-disable-next-line @typescript-eslint/no-restricted-types
type UnknownNotPrimitive<Forbidden extends Primitive> = Exclude<Primitive, Forbidden> | object;
// 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;
};
const assertNotUndefinedCheck = (value: string | undefined) => {
isAssert.not.undefined(value);
expectTypeOf(value).toEqualTypeOf<string>();
};
const assertNotUndefinedUnknownCheck = (value: unknown) => {
isAssert.not.undefined(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<undefined>>();
};
const assertNotUndefinedGenericCheck = <T>(value: T) => {
isAssert.not.undefined(value);
const _: Exclude<T, undefined> = value;
};
const nullValue = null;
type Null = typeof nullValue;
const assertNotNullUnknownCheck = (value: unknown) => {
isAssert.not.null(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<Null>>();
};
const assertNotNullOrUndefinedCheck = (value: string | Null | undefined) => {
isAssert.not.nullOrUndefined(value);
expectTypeOf(value).toEqualTypeOf<string>();
};
const assertNotNullOrUndefinedUnknownCheck = (value: unknown) => {
isAssert.not.nullOrUndefined(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<Null | undefined>>();
};
const assertNotStringCheck = (value: string | number) => {
isAssert.not.string(value);
expectTypeOf(value).toEqualTypeOf<number>();
};
const assertNotStringUnknownCheck = (value: unknown) => {
isAssert.not.string(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<string>>();
};
const assertNotStringGenericCheck = <T>(value: T) => {
isAssert.not.string(value);
const _: Exclude<T, string> = value;
};
const assertNotBooleanUnknownCheck = (value: unknown) => {
isAssert.not.boolean(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<boolean>>();
};
const assertNotSymbolUnknownCheck = (value: unknown) => {
isAssert.not.symbol(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<symbol>>();
};
const assertNotBigintUnknownCheck = (value: unknown) => {
isAssert.not.bigint(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<bigint>>();
};
const assertNotPrimitiveUnknownCheck = (value: unknown) => {
isAssert.not.primitive(value);
// eslint-disable-next-line @typescript-eslint/no-restricted-types
expectTypeOf(value).toEqualTypeOf<object>();
};
const assertNotPrimitiveGenericCheck = <T>(value: T) => {
isAssert.not.primitive(value);
const _: Exclude<T, Primitive> = value;
};
const assertNotNamedUndefinedExportCheck = (value: 0 | false | '' | Null | undefined | 'ok') => {
assertNotUndefined(value);
expectTypeOf(value).toEqualTypeOf<0 | false | '' | Null | 'ok'>();
};
const assertNotNamedNullOrUndefinedUnknownExportCheck = (value: unknown) => {
assertNotNullOrUndefined(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<Null | undefined>>();
};
const assertNotNamedStringExportCheck = (value: string | number) => {
assertNotString(value);
expectTypeOf(value).toEqualTypeOf<number>();
};
const assertNotNamedStringUnknownExportCheck = (value: unknown) => {
assertNotString(value);
expectTypeOf(value).toEqualTypeOf<UnknownNotPrimitive<string>>();
};
const assertNotNamedPrimitiveUnknownExportCheck = (value: unknown) => {
assertNotPrimitive(value);
// eslint-disable-next-line @typescript-eslint/no-restricted-types
expectTypeOf(value).toEqualTypeOf<object>();
};
const assertNotCallableDoesNotExistCheck = (value: string | undefined) => {
// @ts-expect-error -- Generic negative assertions cannot safely infer complement types from arbitrary predicates.
isAssert.not(is.undefined, value);
const _: string | undefined = value;
};
const assertNotNumberDoesNotExistCheck = (value: string | number) => {
// @ts-expect-error -- `is.number` rejects `NaN`, so a narrowing negative assertion would be unsound.
isAssert.not.number(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
const _: string | number = value;
};
const assertNotIntegerDoesNotExistCheck = (value: string | number) => {
// @ts-expect-error -- Numeric refinements are intentionally excluded from `assert.not`.
isAssert.not.integer(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
const _: string | number = value;
};
const assertNotObjectDoesNotExistCheck = (value: Record<string, unknown> | string) => {
// @ts-expect-error -- TypeScript's `{}` type includes primitives, so `not.object` cannot safely narrow every object-like input.
isAssert.not.object(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
const _: Record<string, unknown> | string = value;
};
const assertNotBlobDoesNotExistCheck = (value: Blob | File | string) => {
// @ts-expect-error -- `File` extends `Blob` in TypeScript but does not match the exact runtime `Blob` check.
isAssert.not.blob(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
const _: Blob | File | string = value;
};
const assertNotMapDoesNotExistCheck = (value: Map<string, number> | string) => {
// @ts-expect-error -- Structural object types such as `Map` can be assignable in TypeScript without matching the runtime brand check.
isAssert.not.map(value); // eslint-disable-line @typescript-eslint/no-unsafe-call, unicorn/no-array-callback-reference
const _: Map<string, number> | string = value;
};
const assertNotSetDoesNotExistCheck = (value: Set<string> | string) => {
// @ts-expect-error -- Structural object types such as `Set` can be assignable in TypeScript without matching the runtime brand check.
isAssert.not.set(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
const _: Set<string> | string = value;
};
const assertNotDateDoesNotExistCheck = (value: Date | string) => {
// @ts-expect-error -- Structural object types such as `Date` can be assignable in TypeScript without matching the runtime brand check.
isAssert.not.date(value); // eslint-disable-line @typescript-eslint/no-unsafe-call
const _: Date | string = value;
};
// 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);
assertNotUndefinedCheck('🦄');
assertNotUndefinedUnknownCheck('🦄');
assertNotUndefinedGenericCheck<string | undefined>('🦄');
assertNotNullUnknownCheck('🦄');
assertNotNullOrUndefinedCheck('🦄');
assertNotNullOrUndefinedUnknownCheck('🦄');
assertNotStringCheck(1);
assertNotStringUnknownCheck(1);
assertNotStringGenericCheck<string | number>(1);
assertNotBooleanUnknownCheck(1);
assertNotSymbolUnknownCheck(1);
assertNotBigintUnknownCheck(1);
assertNotPrimitiveUnknownCheck({});
assertNotPrimitiveGenericCheck<string | {unicorn: true}>({unicorn: true});
assertNotNamedUndefinedExportCheck(0);
assertNotNamedNullOrUndefinedUnknownExportCheck('🦄');
assertNotNamedStringExportCheck(1);
assertNotNamedStringUnknownExportCheck(1);
assertNotNamedPrimitiveUnknownExportCheck({});
assertNotCallableDoesNotExistCheck('🦄');
assertNotNumberDoesNotExistCheck(Number.NaN);
assertNotIntegerDoesNotExistCheck(1.5);
assertNotObjectDoesNotExistCheck('🦄');
assertNotBlobDoesNotExistCheck('🦄');
assertNotMapDoesNotExistCheck('🦄');
assertNotSetDoesNotExistCheck('🦄');
assertNotDateDoesNotExistCheck('🦄');

View file

@ -1,5 +1,11 @@
{ {
"extends": "@sindresorhus/tsconfig", "extends": "@sindresorhus/tsconfig",
"compilerOptions": {
"types": ["node"],
"rootDir": "source",
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true
},
"include": [ "include": [
"source" "source"
], ],