Compare commits
No commits in common. "main" and "v7.0.2" have entirely different histories.
12 changed files with 1622 additions and 2944 deletions
7
.github/workflows/main.yml
vendored
7
.github/workflows/main.yml
vendored
|
|
@ -10,11 +10,10 @@ jobs:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node-version:
|
node-version:
|
||||||
- 24
|
- 20
|
||||||
- 22
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
node-version: ${{ matrix.node-version }}
|
||||||
- run: npm install
|
- run: npm install
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
# 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 +0,0 @@
|
||||||
AGENTS.md
|
|
||||||
47
package.json
47
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@sindresorhus/is",
|
"name": "@sindresorhus/is",
|
||||||
"version": "8.1.0",
|
"version": "7.0.2",
|
||||||
"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": ">=22"
|
"node": ">=18"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "del distribution && tsc",
|
"build": "del distribution && tsc",
|
||||||
"test": "tsc --noEmit && tsc --project test/tsconfig.json --noEmit && xo && node --experimental-transform-types --test test/test.ts",
|
"test": "tsc --noEmit && xo && ava",
|
||||||
"prepare": "npm run build"
|
"prepare": "npm run build"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
|
|
@ -51,26 +51,31 @@
|
||||||
"typeguards",
|
"typeguards",
|
||||||
"types"
|
"types"
|
||||||
],
|
],
|
||||||
"xo": {
|
|
||||||
"rules": {
|
|
||||||
"@typescript-eslint/no-unsafe-enum-comparison": "off",
|
|
||||||
"@typescript-eslint/no-confusing-void-expression": "off",
|
|
||||||
"@typescript-eslint/no-unsafe-type-assertion": "off",
|
|
||||||
"@stylistic/operator-linebreak": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@sindresorhus/tsconfig": "^8.1.0",
|
"@sindresorhus/tsconfig": "^6.0.0",
|
||||||
"@types/jsdom": "^28.0.1",
|
"@types/jsdom": "^21.1.7",
|
||||||
"@types/node": "^25.5.2",
|
"@types/node": "^20.14.10",
|
||||||
"@types/zen-observable": "^0.8.7",
|
"@types/zen-observable": "^0.8.7",
|
||||||
"del-cli": "^7.0.0",
|
"ava": "^6.1.3",
|
||||||
"expect-type": "^1.3.0",
|
"del-cli": "^5.1.0",
|
||||||
"jsdom": "^29.0.2",
|
"expect-type": "^0.19.0",
|
||||||
"rxjs": "^7.8.2",
|
"jsdom": "^24.1.0",
|
||||||
"tempy": "^3.2.0",
|
"rxjs": "^7.8.1",
|
||||||
"typescript": "6.0.2",
|
"tempy": "^3.1.0",
|
||||||
"xo": "^2.0.2",
|
"tsimp": "2.0.11",
|
||||||
|
"typescript": "5.5.3",
|
||||||
|
"xo": "^0.58.0",
|
||||||
"zen-observable": "^0.10.0"
|
"zen-observable": "^0.10.0"
|
||||||
|
},
|
||||||
|
"ava": {
|
||||||
|
"environmentVariables": {
|
||||||
|
"TSIMP_DIAG": "error"
|
||||||
|
},
|
||||||
|
"extensions": {
|
||||||
|
"ts": "module"
|
||||||
|
},
|
||||||
|
"nodeArguments": [
|
||||||
|
"--import=tsimp/import"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
159
readme.md
159
readme.md
|
|
@ -130,17 +130,6 @@ 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)
|
||||||
|
|
@ -474,10 +463,6 @@ 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.
|
||||||
|
|
@ -486,22 +471,6 @@ 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.
|
||||||
|
|
@ -606,31 +575,6 @@ is.any([is.boolean, is.number], 'unicorns', [], new Map());
|
||||||
//=> false
|
//=> false
|
||||||
```
|
```
|
||||||
|
|
||||||
##### .any(predicate[])
|
|
||||||
|
|
||||||
Using an array of `predicate[]` without values, returns a combined type guard that checks if a value matches **any** of the predicates:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const isStringOrNumber = is.any([is.string, is.number]);
|
|
||||||
|
|
||||||
isStringOrNumber('hello');
|
|
||||||
//=> true
|
|
||||||
|
|
||||||
isStringOrNumber(123);
|
|
||||||
//=> true
|
|
||||||
|
|
||||||
isStringOrNumber(true);
|
|
||||||
//=> false
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful for composing with other methods like `is.optional`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
is.optional(value, is.any([is.string, is.number]));
|
|
||||||
```
|
|
||||||
|
|
||||||
An empty predicate array currently returns a predicate that always returns `false`. This will throw in the next major release.
|
|
||||||
|
|
||||||
##### .all(predicate, ...values)
|
##### .all(predicate, ...values)
|
||||||
|
|
||||||
Returns `true` if **all** of the input `values` returns true in the `predicate`:
|
Returns `true` if **all** of the input `values` returns true in the `predicate`:
|
||||||
|
|
@ -643,54 +587,6 @@ is.all(is.string, '🦄', [], 'unicorns');
|
||||||
//=> false
|
//=> false
|
||||||
```
|
```
|
||||||
|
|
||||||
##### .all(predicate[])
|
|
||||||
|
|
||||||
Using an array of `predicate[]` without values, returns a combined type guard that checks if a value matches **all** of the predicates:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const isArrayAndNonEmpty = is.all([is.array, is.nonEmptyArray]);
|
|
||||||
|
|
||||||
isArrayAndNonEmpty(['hello']);
|
|
||||||
//=> true
|
|
||||||
|
|
||||||
isArrayAndNonEmpty([]);
|
|
||||||
//=> false
|
|
||||||
```
|
|
||||||
|
|
||||||
This is useful for composing with other methods like `is.optional`:
|
|
||||||
|
|
||||||
```js
|
|
||||||
is.optional(value, is.all([is.object, is.plainObject]));
|
|
||||||
```
|
|
||||||
|
|
||||||
An empty predicate array currently returns a predicate that always returns `true`. This will throw in the next major release.
|
|
||||||
|
|
||||||
##### .optional(value, predicate)
|
|
||||||
|
|
||||||
Returns `true` if `value` is `undefined` or satisfies the given `predicate`.
|
|
||||||
|
|
||||||
```js
|
|
||||||
is.optional(undefined, is.string);
|
|
||||||
//=> true
|
|
||||||
|
|
||||||
is.optional('🦄', is.string);
|
|
||||||
//=> true
|
|
||||||
|
|
||||||
is.optional(123, is.string);
|
|
||||||
//=> 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.
|
||||||
|
|
@ -786,61 +682,6 @@ 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
|
|
||||||
|
|
||||||
Asserts that `value` is `undefined` or satisfies the provided `assertion`.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
import {assert} from '@sindresorhus/is';
|
|
||||||
|
|
||||||
assert.optional(undefined, assert.string);
|
|
||||||
// Passes without throwing
|
|
||||||
|
|
||||||
assert.optional('🦄', assert.string);
|
|
||||||
// Passes without throwing
|
|
||||||
|
|
||||||
assert.optional(123, assert.string);
|
|
||||||
// Throws: Expected value which is `string`, received value of type `number`
|
|
||||||
```
|
|
||||||
|
|
||||||
## Generic type parameters
|
## Generic type parameters
|
||||||
|
|
||||||
The type guards and type assertions are aware of [generic type parameters](https://www.typescriptlang.org/docs/handbook/generics.html), such as `Promise<T>` and `Map<Key, Value>`. The default is `unknown` for most cases, since `is` cannot check them at runtime. If the generic type is known at compile-time, either implicitly (inferred) or explicitly (provided), `is` propagates the type so it can be used later.
|
The type guards and type assertions are aware of [generic type parameters](https://www.typescriptlang.org/docs/handbook/generics.html), such as `Promise<T>` and `Map<Key, Value>`. The default is `unknown` for most cases, since `is` cannot check them at runtime. If the generic type is known at compile-time, either implicitly (inferred) or explicitly (provided), `is` propagates the type so it can be used later.
|
||||||
|
|
|
||||||
507
source/index.ts
507
source/index.ts
File diff suppressed because it is too large
Load diff
128
source/types.ts
128
source/types.ts
|
|
@ -4,8 +4,7 @@
|
||||||
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 =
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
| null // eslint-disable-line @typescript-eslint/ban-types
|
||||||
| null
|
|
||||||
| undefined
|
| undefined
|
||||||
| string
|
| string
|
||||||
| number
|
| number
|
||||||
|
|
@ -54,11 +53,10 @@ export type ObservableLike = {
|
||||||
[Symbol.observable](): ObservableLike;
|
[Symbol.observable](): ObservableLike;
|
||||||
};
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
export type Falsy = false | 0 | 0n | '' | null | undefined;
|
export type Falsy = false | 0 | 0n | '' | null | undefined;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
export type WeakRef<T extends object> = { // eslint-disable-line @typescript-eslint/ban-types, unicorn/prevent-abbreviations
|
||||||
export type WeakRef<T extends object> = {
|
|
||||||
readonly [Symbol.toStringTag]: 'WeakRef';
|
readonly [Symbol.toStringTag]: 'WeakRef';
|
||||||
deref(): T | undefined;
|
deref(): T | undefined;
|
||||||
};
|
};
|
||||||
|
|
@ -77,123 +75,3 @@ export type Predicate = (value: unknown) => boolean;
|
||||||
export type NonEmptyString = string & {0: string};
|
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.
|
|
||||||
|
|
||||||
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'>;
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
export function keysOf<T extends Record<PropertyKey, unknown>>(value: T): Array<keyof T> {
|
|
||||||
return Object.keys(value) as Array<keyof T>; // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
|
||||||
}
|
|
||||||
3282
test/test.ts
3282
test/test.ts
File diff suppressed because it is too large
Load diff
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"rootDir": "..",
|
|
||||||
"noUnusedLocals": false,
|
|
||||||
"noUnusedParameters": false
|
|
||||||
},
|
|
||||||
"include": [
|
|
||||||
"../source",
|
|
||||||
"type-tests.ts"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,405 +0,0 @@
|
||||||
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('🦄');
|
|
||||||
|
|
@ -1,11 +1,5 @@
|
||||||
{
|
{
|
||||||
"extends": "@sindresorhus/tsconfig",
|
"extends": "@sindresorhus/tsconfig",
|
||||||
"compilerOptions": {
|
|
||||||
"types": ["node"],
|
|
||||||
"rootDir": "source",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"rewriteRelativeImportExtensions": true
|
|
||||||
},
|
|
||||||
"include": [
|
"include": [
|
||||||
"source"
|
"source"
|
||||||
],
|
],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue