Compare commits
254 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7821031c66 | ||
|
|
a439305554 | ||
|
|
2d4956e634 | ||
|
|
48df5c429c | ||
|
|
13febb6b01 | ||
|
|
cb4ee0e92c | ||
|
|
54fc09406a | ||
|
|
63be5c0c19 | ||
|
|
ac46b5400d | ||
|
|
47415dc46a | ||
|
|
3b40955b02 | ||
|
|
faf700367e | ||
|
|
eff8e6b318 | ||
|
|
9bdcd9b57f | ||
|
|
fbcc68e139 | ||
|
|
e7c84fcb79 | ||
|
|
d22ab62991 | ||
|
|
1f2440ae0d | ||
|
|
c68ad76062 | ||
|
|
ef35cc350a | ||
|
|
882a91c54f | ||
|
|
e8e8124ba7 | ||
|
|
47a5099325 | ||
|
|
e0976457e0 | ||
|
|
5565c5e3ba | ||
|
|
ab85d9bca9 | ||
|
|
0ff273fee8 | ||
|
|
25a376875d | ||
|
|
8cbcaee674 | ||
|
|
92699e1049 | ||
|
|
47f49741ea | ||
|
|
0df21e4151 | ||
|
|
f7148e19dc | ||
|
|
a1987f8bad | ||
|
|
f2e5834421 | ||
|
|
664b9077e1 | ||
|
|
07ea404e86 | ||
|
|
e9418fe1b9 | ||
|
|
0e687a23a8 | ||
|
|
1acbd9e202 | ||
|
|
0d4cf6fcc8 | ||
|
|
f10e2caf3d | ||
|
|
e7e2213e91 | ||
|
|
9d6c91ee58 | ||
|
|
877ed1cc6a | ||
|
|
dadca59f6a | ||
|
|
68e0c95857 | ||
|
|
bcec30d735 | ||
|
|
ee79af32b6 | ||
|
|
85c89925b6 | ||
|
|
e03c249d6c | ||
|
|
5044c91273 | ||
|
|
bd5dfda993 | ||
|
|
44beb083a3 | ||
|
|
3868f47783 | ||
|
|
94dc715577 | ||
|
|
20ad8231c2 | ||
|
|
ad0c3b1429 | ||
|
|
9d26c020ee | ||
|
|
278e0e9696 | ||
|
|
1284da085f | ||
|
|
3177d11801 | ||
|
|
9265e9072d | ||
|
|
7d468191f4 | ||
|
|
d1574d358d | ||
|
|
61a437eba3 | ||
|
|
65404fbd8e | ||
|
|
888e145c5a | ||
|
|
e559b37b72 | ||
|
|
f3693674f6 | ||
|
|
911f44dc36 | ||
|
|
33cd815503 | ||
|
|
592d9093cd | ||
|
|
d3ff1fdfce | ||
|
|
45cae31f4d | ||
|
|
dc99f7cd4a | ||
|
|
e503a9ec49 | ||
|
|
06d217f70c | ||
|
|
ac7c567c2b | ||
|
|
778c5da5b3 | ||
|
|
d6fc1ce0fe | ||
|
|
c408f5a268 | ||
|
|
65ea91297e | ||
|
|
5b7ea154e6 | ||
|
|
6cbefb9af7 | ||
|
|
dc2dc9a438 | ||
|
|
23cf074a73 | ||
|
|
04ccf21dba | ||
|
|
c3d12667fd | ||
|
|
63d75d68ee | ||
|
|
73daee6648 | ||
|
|
b1efe7f5cf | ||
|
|
a5b4017d5e | ||
|
|
f5cc764e22 | ||
|
|
ad661ebcee | ||
|
|
a82aeeaa5e | ||
|
|
13b2343dfc | ||
|
|
a8de3d6f34 | ||
|
|
b007935b4b | ||
|
|
d2f98e472d | ||
|
|
6f2b24d822 | ||
|
|
238e8c80c7 | ||
|
|
b748ab72b6 | ||
|
|
5ed7e9bb40 | ||
|
|
4f8b01f2dc | ||
|
|
da6bb531af | ||
|
|
d528545e02 | ||
|
|
94749dbb2e | ||
|
|
bf6bba7af8 | ||
|
|
d8ced89efe | ||
|
|
5feadcb0b8 | ||
|
|
4c29fb35cb | ||
|
|
09d31733d3 | ||
|
|
3f93bf200d | ||
|
|
e31db97eab | ||
|
|
4b35ad5bec | ||
|
|
47fa419e4f | ||
|
|
71ca1e5573 | ||
|
|
a96abee1a3 | ||
|
|
1ffe2fb6a7 | ||
|
|
9d404cad2e | ||
|
|
fae0096eba | ||
|
|
402fbb5a7e | ||
|
|
05cdaccf92 | ||
|
|
1ff389cabb | ||
|
|
d1929ad47c | ||
|
|
f97029fd73 | ||
|
|
863e26ad6a | ||
|
|
5d0ccec21c | ||
|
|
446a7a081e | ||
|
|
d28c86ee27 | ||
|
|
11003b925e | ||
|
|
0c3f110386 | ||
|
|
c842cc260f | ||
|
|
aeb3f74d65 | ||
|
|
c25b606c3b | ||
|
|
af6b03d67f | ||
|
|
0cc82c583e | ||
|
|
8ff75abfff | ||
|
|
e7277de849 | ||
|
|
3f2caa4835 | ||
|
|
4a63743feb | ||
|
|
7c16f20d16 | ||
|
|
0f9b8479e9 | ||
|
|
0cbd9df6ce | ||
|
|
e358e44dd5 | ||
|
|
4f4820ef2f | ||
|
|
f04dffa575 | ||
|
|
ffc6ce4586 | ||
|
|
7bf407fbf1 | ||
|
|
878d111ae7 | ||
|
|
6106086a35 | ||
|
|
b064473589 | ||
|
|
04cd282265 | ||
|
|
9bc8307770 | ||
|
|
2dac8e96f4 | ||
|
|
dd2a91dce5 | ||
|
|
373605e40d | ||
|
|
120f74ab63 | ||
|
|
ea4204f0b4 | ||
|
|
28913cae88 | ||
|
|
79144f9542 | ||
|
|
c66885b781 | ||
|
|
0fff8265e6 | ||
|
|
3ec41686f7 | ||
|
|
2f5e03bed2 | ||
|
|
9683cd7fd9 | ||
|
|
3c847be5a0 | ||
|
|
2502442404 | ||
|
|
641d856b36 | ||
|
|
ab586df0f9 | ||
|
|
0e1cce5d45 | ||
|
|
4d0120adb7 | ||
|
|
6cb1d1e910 | ||
|
|
42fd8d3574 | ||
|
|
844b43c9df | ||
|
|
566f363632 | ||
|
|
2ee148f5a1 | ||
|
|
9df6f4ebe9 | ||
|
|
b8a9fb457c | ||
|
|
2232eeea79 | ||
|
|
69bbf2d4be | ||
|
|
d4869045c2 | ||
|
|
7317226c80 | ||
|
|
9ac56f1be7 | ||
|
|
c983ffa4cd | ||
|
|
45c976071a | ||
|
|
6e07df5896 | ||
|
|
65c94f1a02 | ||
|
|
442f7b709f | ||
|
|
ad6f372c47 | ||
|
|
36b46f1889 | ||
|
|
b2bb3e7d37 | ||
|
|
3bdaf21475 | ||
|
|
c8736c2972 | ||
|
|
c84c2cbeca | ||
|
|
d11b7eaaa1 | ||
|
|
ff268de3d0 | ||
|
|
fe754b5528 | ||
|
|
0988832ef7 | ||
|
|
db25d554dc | ||
|
|
37f6cc3fe3 | ||
|
|
1df2ff09ce | ||
|
|
55c00956f9 | ||
|
|
38df0cfb24 | ||
|
|
7ae4b44ca2 | ||
|
|
8894dbec17 | ||
|
|
5670ed7a97 | ||
|
|
64ced4d33c | ||
|
|
ef85a5173d | ||
|
|
fd32b11b6d | ||
|
|
40fc2fd790 | ||
|
|
828a5b3088 | ||
|
|
28702421bf | ||
|
|
70b08940be | ||
|
|
4ce2ee9d39 | ||
|
|
50e3fe88c7 | ||
|
|
a04a04c131 | ||
|
|
ae3adc9818 | ||
|
|
319982a09c | ||
|
|
89fc424975 | ||
|
|
e8ac7dc4aa | ||
|
|
89867fbc16 | ||
|
|
59a638b216 | ||
|
|
d075b547fc | ||
|
|
9770f66899 | ||
|
|
8d8fd2b7e0 | ||
|
|
83adc096ef | ||
|
|
0bafa04e77 | ||
|
|
bd428997a2 | ||
|
|
a4eebe4b4a | ||
|
|
dfcdfc3a83 | ||
|
|
cdd4829edf | ||
|
|
615932d6c2 | ||
|
|
00974a2fe9 | ||
|
|
dc3b6ff86b | ||
|
|
04cb80dfb1 | ||
|
|
651f434eab | ||
|
|
75ac3cd574 | ||
|
|
6268253ec6 | ||
|
|
46e886d10b | ||
|
|
ee8f5d16f8 | ||
|
|
24c964a7c7 | ||
|
|
11b98171c8 | ||
|
|
47982af117 | ||
|
|
27d15f40bd | ||
|
|
226e4d90da | ||
|
|
dbe5cc8d82 | ||
|
|
000f66bbdc | ||
|
|
e97e68cd01 | ||
|
|
f918d8a7d4 | ||
|
|
26ca195302 | ||
|
|
3cbef48b6c | ||
|
|
119c41d39b |
19 changed files with 6388 additions and 514 deletions
3
.gitattributes
vendored
3
.gitattributes
vendored
|
|
@ -1,2 +1 @@
|
|||
* text=auto
|
||||
*.js text eol=lf
|
||||
* text=auto eol=lf
|
||||
|
|
|
|||
3
.github/security.md
vendored
Normal file
3
.github/security.md
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Security Policy
|
||||
|
||||
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
||||
21
.github/workflows/main.yml
vendored
Normal file
21
.github/workflows/main.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
name: CI
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
jobs:
|
||||
test:
|
||||
name: Node.js ${{ matrix.node-version }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version:
|
||||
- 24
|
||||
- 22
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install
|
||||
- run: npm test
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +1,4 @@
|
|||
node_modules
|
||||
yarn.lock
|
||||
/distribution
|
||||
.tsimp
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- '8'
|
||||
- '6'
|
||||
- '4'
|
||||
9
AGENTS.md
Normal file
9
AGENTS.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Notes
|
||||
|
||||
## Branded types for type guards
|
||||
|
||||
TypeScript type guards narrow in both branches. If `is.integer(n)` returns `value is number` and the input is `number`, the false branch computes `Exclude<number, number>` = `never`. This makes common patterns like `if (!is.integer(n)) throw; use(n)` fail because `n` becomes `never` after the guard.
|
||||
|
||||
To avoid this, type guard predicates use branded types (e.g., `number & {readonly __brand: 'Integer'}`, `string & {readonly __brand: 'UrlString'}`). A branded subtype ensures the false branch stays the original type (e.g., `Exclude<number, Integer>` = `number`).
|
||||
|
||||
Assert functions (`asserts value is T`) don't need branded types since they throw on failure and have no false branch. They use plain types like `asserts value is number`.
|
||||
1
CLAUDE.md
Symbolic link
1
CLAUDE.md
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
AGENTS.md
|
||||
151
index.js
151
index.js
|
|
@ -1,151 +0,0 @@
|
|||
'use strict';
|
||||
const toString = Object.prototype.toString;
|
||||
const getObjectType = x => toString.call(x).slice(8, -1);
|
||||
|
||||
const is = value => {
|
||||
if (value == null) { // eslint-disable-line no-eq-null, eqeqeq
|
||||
return 'null';
|
||||
}
|
||||
|
||||
if (value === true || value === false) {
|
||||
return 'boolean';
|
||||
}
|
||||
|
||||
const type = typeof value;
|
||||
|
||||
if (type === 'undefined') {
|
||||
return 'undefined';
|
||||
}
|
||||
|
||||
if (type === 'string') {
|
||||
return 'string';
|
||||
}
|
||||
|
||||
if (type === 'number') {
|
||||
return 'number';
|
||||
}
|
||||
|
||||
if (type === 'symbol') {
|
||||
return 'symbol';
|
||||
}
|
||||
|
||||
if (type === 'function') {
|
||||
return 'Function';
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return 'Array';
|
||||
}
|
||||
|
||||
if (Buffer.isBuffer(value)) {
|
||||
return 'Buffer';
|
||||
}
|
||||
|
||||
const tagType = getObjectType(value);
|
||||
if (tagType) {
|
||||
return tagType;
|
||||
}
|
||||
|
||||
if (value instanceof String || value instanceof Boolean || value instanceof Number) {
|
||||
throw new TypeError('Please don\'t use object wrappers for primitive types');
|
||||
}
|
||||
|
||||
return 'Object';
|
||||
};
|
||||
|
||||
is.undefined = x => typeof x === 'undefined';
|
||||
is.null = x => x === null;
|
||||
is.string = x => typeof x === 'string';
|
||||
is.number = x => typeof x === 'number';
|
||||
is.boolean = x => typeof x === 'boolean';
|
||||
is.symbol = x => typeof x === 'symbol';
|
||||
|
||||
is.array = Array.isArray;
|
||||
is.function = x => typeof x === 'function';
|
||||
is.buffer = Buffer.isBuffer;
|
||||
|
||||
is.object = x => {
|
||||
const type = typeof x;
|
||||
return x !== null && (type === 'object' || type === 'function');
|
||||
};
|
||||
|
||||
is.nativePromise = x => getObjectType(x) === 'Promise';
|
||||
|
||||
is.promise = x => {
|
||||
return is.nativePromise(x) ||
|
||||
(
|
||||
x !== null &&
|
||||
typeof x === 'object' &&
|
||||
typeof x.then === 'function' &&
|
||||
typeof x.catch === 'function'
|
||||
);
|
||||
};
|
||||
|
||||
is.regExp = x => getObjectType(x) === 'RegExp';
|
||||
is.date = x => getObjectType(x) === 'Date';
|
||||
is.error = x => getObjectType(x) === 'Error';
|
||||
is.map = x => getObjectType(x) === 'Map';
|
||||
is.set = x => getObjectType(x) === 'Set';
|
||||
is.weakMap = x => getObjectType(x) === 'WeakMap';
|
||||
is.weakSet = x => getObjectType(x) === 'WeakSet';
|
||||
|
||||
is.int8Array = x => getObjectType(x) === 'Int8Array';
|
||||
is.uint8Array = x => getObjectType(x) === 'Uint8Array';
|
||||
is.uint8ClampedArray = x => getObjectType(x) === 'Uint8ClampedArray';
|
||||
is.int16Array = x => getObjectType(x) === 'Int16Array';
|
||||
is.uint16Array = x => getObjectType(x) === 'Uint16Array';
|
||||
is.int32Array = x => getObjectType(x) === 'Int32Array';
|
||||
is.uint32Array = x => getObjectType(x) === 'Uint32Array';
|
||||
is.float32Array = x => getObjectType(x) === 'Float32Array';
|
||||
is.float64Array = x => getObjectType(x) === 'Float64Array';
|
||||
|
||||
is.arrayBuffer = x => getObjectType(x) === 'ArrayBuffer';
|
||||
|
||||
is.sharedArrayBuffer = x => {
|
||||
try {
|
||||
return getObjectType(x) === 'SharedArrayBuffer';
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
is.nan = Number.isNaN;
|
||||
is.nullOrUndefined = x => x === null || typeof x === 'undefined';
|
||||
|
||||
is.primitive = x => {
|
||||
const type = typeof x;
|
||||
return x === null ||
|
||||
type === 'undefined' ||
|
||||
type === 'string' ||
|
||||
type === 'number' ||
|
||||
type === 'boolean' ||
|
||||
type === 'symbol';
|
||||
};
|
||||
|
||||
is.integer = Number.isInteger;
|
||||
|
||||
is.plainObject = x => {
|
||||
// From: https://github.com/sindresorhus/is-plain-obj/blob/master/index.js
|
||||
let prototype;
|
||||
// eslint-disable-next-line no-return-assign
|
||||
return getObjectType(x) === 'Object' &&
|
||||
(prototype = Object.getPrototypeOf(x), prototype === null ||
|
||||
prototype === Object.getPrototypeOf({}));
|
||||
};
|
||||
|
||||
is.iterable = x => !is.null(x) && !is.undefined(x) && typeof x[Symbol.iterator] === 'function';
|
||||
|
||||
const typedArrayTypes = new Set([
|
||||
'Int8Array',
|
||||
'Uint8Array',
|
||||
'Uint8ClampedArray',
|
||||
'Int16Array',
|
||||
'Uint16Array',
|
||||
'Int32Array',
|
||||
'Uint32Array',
|
||||
'Float32Array',
|
||||
'Float64Array'
|
||||
]);
|
||||
is.typedArray = x => typedArrayTypes.has(getObjectType(x));
|
||||
|
||||
module.exports = is;
|
||||
2
license
2
license
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
|
||||
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
|
|
|||
121
package.json
121
package.json
|
|
@ -1,49 +1,76 @@
|
|||
{
|
||||
"name": "@sindresorhus/is",
|
||||
"version": "0.1.0",
|
||||
"description": "Type check values: `is.string('🦄') //=> true`",
|
||||
"license": "MIT",
|
||||
"repository": "sindresorhus/is",
|
||||
"author": {
|
||||
"name": "Sindre Sorhus",
|
||||
"email": "sindresorhus@gmail.com",
|
||||
"url": "sindresorhus.com"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "xo && ava"
|
||||
},
|
||||
"files": [
|
||||
"index.js"
|
||||
],
|
||||
"keywords": [
|
||||
"type",
|
||||
"types",
|
||||
"is",
|
||||
"check",
|
||||
"checking",
|
||||
"validate",
|
||||
"validation",
|
||||
"utility",
|
||||
"util",
|
||||
"typeof",
|
||||
"instanceof",
|
||||
"object",
|
||||
"assert",
|
||||
"assertion",
|
||||
"test",
|
||||
"kind",
|
||||
"primitive",
|
||||
"verify",
|
||||
"compare"
|
||||
],
|
||||
"devDependencies": {
|
||||
"ava": "*",
|
||||
"xo": "*"
|
||||
}
|
||||
"name": "@sindresorhus/is",
|
||||
"version": "8.1.0",
|
||||
"description": "Type check values",
|
||||
"license": "MIT",
|
||||
"repository": "sindresorhus/is",
|
||||
"funding": "https://github.com/sindresorhus/is?sponsor=1",
|
||||
"author": {
|
||||
"name": "Sindre Sorhus",
|
||||
"email": "sindresorhus@gmail.com",
|
||||
"url": "https://sindresorhus.com"
|
||||
},
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"types": "./distribution/index.d.ts",
|
||||
"default": "./distribution/index.js"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"engines": {
|
||||
"node": ">=22"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "del distribution && tsc",
|
||||
"test": "tsc --noEmit && tsc --project test/tsconfig.json --noEmit && xo && node --experimental-transform-types --test test/test.ts",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"files": [
|
||||
"distribution"
|
||||
],
|
||||
"keywords": [
|
||||
"type",
|
||||
"types",
|
||||
"is",
|
||||
"check",
|
||||
"checking",
|
||||
"validate",
|
||||
"validation",
|
||||
"utility",
|
||||
"util",
|
||||
"typeof",
|
||||
"instanceof",
|
||||
"object",
|
||||
"assert",
|
||||
"assertion",
|
||||
"test",
|
||||
"kind",
|
||||
"primitive",
|
||||
"verify",
|
||||
"compare",
|
||||
"typescript",
|
||||
"typeguards",
|
||||
"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": {
|
||||
"@sindresorhus/tsconfig": "^8.1.0",
|
||||
"@types/jsdom": "^28.0.1",
|
||||
"@types/node": "^25.5.2",
|
||||
"@types/zen-observable": "^0.8.7",
|
||||
"del-cli": "^7.0.0",
|
||||
"expect-type": "^1.3.0",
|
||||
"jsdom": "^29.0.2",
|
||||
"rxjs": "^7.8.2",
|
||||
"tempy": "^3.2.0",
|
||||
"typescript": "6.0.2",
|
||||
"xo": "^2.0.2",
|
||||
"zen-observable": "^0.10.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
798
readme.md
798
readme.md
|
|
@ -1,21 +1,30 @@
|
|||
# is [](https://travis-ci.org/sindresorhus/is)
|
||||
# is
|
||||
|
||||
> Type check values: `is.string('🦄') //=> true`
|
||||
> Type check values
|
||||
|
||||
For example, `is.string('🦄') //=> true`
|
||||
|
||||
<img src="header.gif" width="182" align="right">
|
||||
|
||||
## Highlights
|
||||
|
||||
- Written in TypeScript
|
||||
- [Extensive use of type guards](#type-guards)
|
||||
- [Supports type assertions](#type-assertions)
|
||||
- [Aware of generic type parameters](#generic-type-parameters) (use with caution)
|
||||
- Actively maintained
|
||||
- 
|
||||
|
||||
## Install
|
||||
|
||||
```sh
|
||||
npm install @sindresorhus/is
|
||||
```
|
||||
$ npm install @sindresorhus/is
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```js
|
||||
const is = require('@sindresorhus/is');
|
||||
import is from '@sindresorhus/is';
|
||||
|
||||
is('🦄');
|
||||
//=> 'string'
|
||||
|
|
@ -27,6 +36,44 @@ is.number(6);
|
|||
//=> true
|
||||
```
|
||||
|
||||
[Assertions](#type-assertions) perform the same type checks, but throw an error if the type does not match.
|
||||
|
||||
```js
|
||||
import {assert} from '@sindresorhus/is';
|
||||
|
||||
assert.string(2);
|
||||
//=> Error: Expected value which is `string`, received value of type `number`.
|
||||
```
|
||||
|
||||
Assertions (except `assertAll` and `assertAny`) also support an optional custom error message.
|
||||
|
||||
```js
|
||||
import {assert} from '@sindresorhus/is';
|
||||
|
||||
assert.nonEmptyString(process.env.API_URL, 'The API_URL environment variable is required.');
|
||||
//=> Error: The API_URL environment variable is required.
|
||||
```
|
||||
|
||||
And with TypeScript:
|
||||
|
||||
```ts
|
||||
import {assert} from '@sindresorhus/is';
|
||||
|
||||
assert.string(foo);
|
||||
// `foo` is now typed as a `string`.
|
||||
```
|
||||
|
||||
### Named exports
|
||||
|
||||
Named exports allow tooling to perform tree-shaking, potentially reducing bundle size by including only code from the methods that are used.
|
||||
|
||||
Every method listed below is available as a named export. Each method is prefixed by either `is` or `assert` depending on usage.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
import {assertNull, isUndefined} from '@sindresorhus/is';
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
|
|
@ -46,30 +93,72 @@ Example:
|
|||
- `'Function'`
|
||||
- `'Object'`
|
||||
|
||||
Note: It will throw if you try to feed it object-wrapped primitives, as that's a bad practice. For example `new String('foo')`.
|
||||
This method is also exported as `detect`. You can import it like this:
|
||||
|
||||
```js
|
||||
import {detect} from '@sindresorhus/is';
|
||||
```
|
||||
|
||||
Note: It will throw an error if you try to feed it object-wrapped primitives, as that's a bad practice. For example `new String('foo')`.
|
||||
|
||||
### is.{method}
|
||||
|
||||
All the below methods accept a value and returns a boolean for whether the value is of the desired type.
|
||||
All the below methods accept a value and return a boolean for whether the value is of the desired type.
|
||||
|
||||
#### Primitives
|
||||
|
||||
##### .undefined(value)
|
||||
##### .null(value)
|
||||
|
||||
##### .string(value)
|
||||
##### .number(value)
|
||||
|
||||
Note: `is.number(NaN)` returns `false`. This intentionally deviates from `typeof` behavior to increase user-friendliness of `is` type checks.
|
||||
|
||||
##### .boolean(value)
|
||||
##### .symbol(value)
|
||||
##### .bigint(value)
|
||||
|
||||
#### Built-in types
|
||||
|
||||
##### .array(value)
|
||||
##### .array(value, assertion?)
|
||||
|
||||
Returns true if `value` is an array and all of its items match the assertion (if provided).
|
||||
|
||||
```js
|
||||
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.
|
||||
```
|
||||
|
||||
##### .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)
|
||||
|
||||
##### .buffer(value)
|
||||
|
||||
> [!NOTE]
|
||||
> [Prefer using `Uint8Array` instead of `Buffer`.](https://sindresorhus.com/blog/goodbye-nodejs-buffer)
|
||||
|
||||
##### .blob(value)
|
||||
##### .object(value)
|
||||
|
||||
Keep in mind that [functions are objects too](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions).
|
||||
|
||||
##### .numericString(value)
|
||||
|
||||
Returns `true` for a string that represents a number satisfying `is.number`, for example, `'42'` and `'-8.3'`.
|
||||
|
||||
Note: `'NaN'` returns `false`, but `'Infinity'` and `'-Infinity'` return `true`.
|
||||
|
||||
##### .regExp(value)
|
||||
##### .date(value)
|
||||
##### .error(value)
|
||||
|
|
@ -78,10 +167,76 @@ Keep in mind that [functions are objects too](https://developer.mozilla.org/en-U
|
|||
|
||||
Returns `true` for any object with a `.then()` and `.catch()` method. Prefer this one over `.nativePromise()` as you usually want to allow userland promise implementations too.
|
||||
|
||||
##### .generator(value)
|
||||
|
||||
Returns `true` for any object that implements its own `.next()` and `.throw()` methods and has a function definition for `Symbol.iterator`.
|
||||
|
||||
##### .generatorFunction(value)
|
||||
|
||||
##### .asyncFunction(value)
|
||||
|
||||
Returns `true` for any `async` function that can be called with the `await` operator.
|
||||
|
||||
```js
|
||||
is.asyncFunction(async () => {});
|
||||
//=> true
|
||||
|
||||
is.asyncFunction(() => {});
|
||||
//=> false
|
||||
```
|
||||
|
||||
##### .asyncGenerator(value)
|
||||
|
||||
```js
|
||||
is.asyncGenerator(
|
||||
(async function * () {
|
||||
yield 4;
|
||||
})()
|
||||
);
|
||||
//=> true
|
||||
|
||||
is.asyncGenerator(
|
||||
(function * () {
|
||||
yield 4;
|
||||
})()
|
||||
);
|
||||
//=> false
|
||||
```
|
||||
|
||||
##### .asyncGeneratorFunction(value)
|
||||
|
||||
```js
|
||||
is.asyncGeneratorFunction(async function * () {
|
||||
yield 4;
|
||||
});
|
||||
//=> true
|
||||
|
||||
is.asyncGeneratorFunction(function * () {
|
||||
yield 4;
|
||||
});
|
||||
//=> false
|
||||
```
|
||||
|
||||
##### .boundFunction(value)
|
||||
|
||||
Returns `true` for any `bound` function.
|
||||
|
||||
```js
|
||||
is.boundFunction(() => {});
|
||||
//=> true
|
||||
|
||||
is.boundFunction(function () {}.bind(null));
|
||||
//=> true
|
||||
|
||||
is.boundFunction(function () {});
|
||||
//=> false
|
||||
```
|
||||
|
||||
##### .map(value)
|
||||
##### .set(value)
|
||||
##### .weakMap(value)
|
||||
##### .weakSet(value)
|
||||
##### .weakRef(value)
|
||||
|
||||
#### Typed arrays
|
||||
|
||||
|
|
@ -94,6 +249,8 @@ Returns `true` for any object with a `.then()` and `.catch()` method. Prefer thi
|
|||
##### .uint32Array(value)
|
||||
##### .float32Array(value)
|
||||
##### .float64Array(value)
|
||||
##### .bigInt64Array(value)
|
||||
##### .bigUint64Array(value)
|
||||
|
||||
#### Structured data
|
||||
|
||||
|
|
@ -101,22 +258,630 @@ Returns `true` for any object with a `.then()` and `.catch()` method. Prefer thi
|
|||
##### .sharedArrayBuffer(value)
|
||||
##### .dataView(value)
|
||||
|
||||
##### .enumCase(value, enum)
|
||||
|
||||
TypeScript-only. Returns `true` if `value` is a member of `enum`.
|
||||
|
||||
```ts
|
||||
enum Direction {
|
||||
Ascending = 'ascending',
|
||||
Descending = 'descending'
|
||||
}
|
||||
|
||||
is.enumCase('ascending', Direction);
|
||||
//=> true
|
||||
|
||||
is.enumCase('other', Direction);
|
||||
//=> false
|
||||
```
|
||||
|
||||
#### Emptiness
|
||||
|
||||
##### .emptyString(value)
|
||||
|
||||
Returns `true` if the value is a `string` and the `.length` is 0.
|
||||
|
||||
##### .emptyStringOrWhitespace(value)
|
||||
|
||||
Returns `true` if `is.emptyString(value)` or if it's a `string` that is all whitespace.
|
||||
|
||||
##### .nonEmptyString(value)
|
||||
|
||||
Returns `true` if the value is a `string` and the `.length` is more than 0.
|
||||
|
||||
##### .nonEmptyStringAndNotWhitespace(value)
|
||||
|
||||
Returns `true` if the value is a `string` that is not empty and not whitespace.
|
||||
|
||||
```js
|
||||
const values = ['property1', '', null, 'property2', ' ', undefined];
|
||||
|
||||
values.filter(is.nonEmptyStringAndNotWhitespace);
|
||||
//=> ['property1', 'property2']
|
||||
```
|
||||
|
||||
##### .emptyArray(value)
|
||||
|
||||
Returns `true` if the value is an `Array` and the `.length` is 0.
|
||||
|
||||
##### .nonEmptyArray(value)
|
||||
|
||||
Returns `true` if the value is an `Array` and the `.length` is more than 0.
|
||||
|
||||
##### .emptyObject(value)
|
||||
|
||||
Returns `true` if the value is an `Object` and `Object.keys(value).length` is 0.
|
||||
|
||||
Please note that `Object.keys` returns only own enumerable properties. Hence something like this can happen:
|
||||
|
||||
```js
|
||||
const object1 = {};
|
||||
|
||||
Object.defineProperty(object1, 'property1', {
|
||||
value: 42,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
is.emptyObject(object1);
|
||||
//=> true
|
||||
```
|
||||
|
||||
##### .nonEmptyObject(value)
|
||||
|
||||
Returns `true` if the value is an `Object` and `Object.keys(value).length` is more than 0.
|
||||
|
||||
##### .emptySet(value)
|
||||
|
||||
Returns `true` if the value is a `Set` and the `.size` is 0.
|
||||
|
||||
##### .nonEmptySet(Value)
|
||||
|
||||
Returns `true` if the value is a `Set` and the `.size` is more than 0.
|
||||
|
||||
##### .emptyMap(value)
|
||||
|
||||
Returns `true` if the value is a `Map` and the `.size` is 0.
|
||||
|
||||
##### .nonEmptyMap(value)
|
||||
|
||||
Returns `true` if the value is a `Map` and the `.size` is more than 0.
|
||||
|
||||
#### Miscellaneous
|
||||
|
||||
##### .directInstanceOf(value, class)
|
||||
|
||||
Returns `true` if `value` is a direct instance of `class`.
|
||||
|
||||
```js
|
||||
is.directInstanceOf(new Error(), Error);
|
||||
//=> true
|
||||
|
||||
class UnicornError extends Error {}
|
||||
|
||||
is.directInstanceOf(new UnicornError(), Error);
|
||||
//=> false
|
||||
```
|
||||
|
||||
##### .urlInstance(value)
|
||||
|
||||
Returns `true` if `value` is an instance of the [`URL` class](https://developer.mozilla.org/en-US/docs/Web/API/URL).
|
||||
|
||||
```js
|
||||
const url = new URL('https://example.com');
|
||||
|
||||
is.urlInstance(url);
|
||||
//=> true
|
||||
```
|
||||
|
||||
##### .urlString(value)
|
||||
|
||||
Returns `true` if `value` is a URL string.
|
||||
|
||||
Note: this only does basic checking using the [`URL` class](https://developer.mozilla.org/en-US/docs/Web/API/URL) constructor.
|
||||
|
||||
```js
|
||||
const url = 'https://example.com';
|
||||
|
||||
is.urlString(url);
|
||||
//=> true
|
||||
|
||||
is.urlString(new URL(url));
|
||||
//=> false
|
||||
```
|
||||
|
||||
##### .truthy(value)
|
||||
|
||||
Returns `true` for all values that evaluate to true in a boolean context:
|
||||
|
||||
```js
|
||||
is.truthy('🦄');
|
||||
//=> true
|
||||
|
||||
is.truthy(undefined);
|
||||
//=> false
|
||||
```
|
||||
|
||||
##### .falsy(value)
|
||||
|
||||
Returns `true` if `value` is one of: `false`, `0`, `''`, `null`, `undefined`, `NaN`.
|
||||
|
||||
##### .nan(value)
|
||||
##### .nullOrUndefined(value)
|
||||
##### .primitive(value)
|
||||
|
||||
JavaScript primitives are as follows: `null`, `undefined`, `string`, `number`, `boolean`, `symbol`.
|
||||
JavaScript primitives are as follows:
|
||||
|
||||
- `null`
|
||||
- `undefined`
|
||||
- `string`
|
||||
- `number`
|
||||
- `boolean`
|
||||
- `symbol`
|
||||
- `bigint`
|
||||
|
||||
##### .integer(value)
|
||||
|
||||
##### .safeInteger(value)
|
||||
|
||||
Returns `true` if `value` is a [safe integer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger).
|
||||
|
||||
##### .plainObject(value)
|
||||
|
||||
An object is plain if it's created by either `{}`, `new Object()`, or `Object.create(null)`.
|
||||
|
||||
##### .iterable(value)
|
||||
##### .asyncIterable(value)
|
||||
##### .class(value)
|
||||
|
||||
Returns `true` if the value is a class constructor.
|
||||
|
||||
##### .typedArray(value)
|
||||
|
||||
##### .arrayLike(value)
|
||||
|
||||
A `value` is array-like if it is not a function and has a `value.length` that is a safe integer greater than or equal to 0.
|
||||
|
||||
```js
|
||||
is.arrayLike(document.forms);
|
||||
//=> true
|
||||
|
||||
function foo() {
|
||||
is.arrayLike(arguments);
|
||||
//=> true
|
||||
}
|
||||
foo();
|
||||
```
|
||||
|
||||
##### .tupleLike(value, guards)
|
||||
|
||||
A `value` is tuple-like if it matches the provided `guards` array both in `.length` and in types.
|
||||
|
||||
```js
|
||||
is.tupleLike([1], [is.number]);
|
||||
//=> true
|
||||
```
|
||||
|
||||
```js
|
||||
function foo() {
|
||||
const tuple = [1, '2', true];
|
||||
if (is.tupleLike(tuple, [is.number, is.string, is.boolean])) {
|
||||
tuple // [number, string, boolean]
|
||||
}
|
||||
}
|
||||
|
||||
foo();
|
||||
```
|
||||
|
||||
##### .finiteNumber(value)
|
||||
|
||||
Check if `value` is a number and is finite. Excludes `Infinity` and `-Infinity`.
|
||||
|
||||
##### .positiveNumber(value)
|
||||
|
||||
Check if `value` is a number and is more than 0.
|
||||
|
||||
##### .negativeNumber(value)
|
||||
|
||||
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)
|
||||
|
||||
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.
|
||||
|
||||
```js
|
||||
is.inRange(3, [0, 5]);
|
||||
is.inRange(3, [5, 0]);
|
||||
is.inRange(0, [-2, 2]);
|
||||
```
|
||||
|
||||
##### .inRange(value, upperBound)
|
||||
|
||||
Check if `value` (number) is in the range of `0` to `upperBound`.
|
||||
|
||||
```js
|
||||
is.inRange(3, 10);
|
||||
```
|
||||
|
||||
##### .htmlElement(value)
|
||||
|
||||
Returns `true` if `value` is an [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement).
|
||||
|
||||
##### .nodeStream(value)
|
||||
|
||||
Returns `true` if `value` is a Node.js [stream](https://nodejs.org/api/stream.html).
|
||||
|
||||
```js
|
||||
import fs from 'node:fs';
|
||||
|
||||
is.nodeStream(fs.createReadStream('unicorn.png'));
|
||||
//=> true
|
||||
```
|
||||
|
||||
##### .observable(value)
|
||||
|
||||
Returns `true` if `value` is an `Observable`.
|
||||
|
||||
```js
|
||||
import {Observable} from 'rxjs';
|
||||
|
||||
is.observable(new Observable());
|
||||
//=> true
|
||||
```
|
||||
|
||||
##### .infinite(value)
|
||||
|
||||
Check if `value` is `Infinity` or `-Infinity`.
|
||||
|
||||
##### .evenInteger(value)
|
||||
|
||||
Returns `true` if `value` is an even integer.
|
||||
|
||||
##### .oddInteger(value)
|
||||
|
||||
Returns `true` if `value` is an odd integer.
|
||||
|
||||
##### .propertyKey(value)
|
||||
|
||||
Returns `true` if `value` can be used as an object property key (either `string`, `number`, or `symbol`).
|
||||
|
||||
##### .formData(value)
|
||||
|
||||
Returns `true` if `value` is an instance of the [`FormData` class](https://developer.mozilla.org/en-US/docs/Web/API/FormData).
|
||||
|
||||
```js
|
||||
const data = new FormData();
|
||||
|
||||
is.formData(data);
|
||||
//=> true
|
||||
```
|
||||
|
||||
##### .urlSearchParams(value)
|
||||
|
||||
Returns `true` if `value` is an instance of the [`URLSearchParams` class](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams).
|
||||
|
||||
```js
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
is.urlSearchParams(searchParams);
|
||||
//=> true
|
||||
```
|
||||
|
||||
##### .any(predicate | predicate[], ...values)
|
||||
|
||||
Using a single `predicate` argument, returns `true` if **any** of the input `values` returns true in the `predicate`:
|
||||
|
||||
```js
|
||||
is.any(is.string, {}, true, '🦄');
|
||||
//=> true
|
||||
|
||||
is.any(is.boolean, 'unicorns', [], new Map());
|
||||
//=> false
|
||||
```
|
||||
|
||||
Using an array of `predicate[]`, returns `true` if **any** of the input `values` returns true for **any** of the `predicates` provided in an array:
|
||||
|
||||
```js
|
||||
is.any([is.string, is.number], {}, true, '🦄');
|
||||
//=> true
|
||||
|
||||
is.any([is.boolean, is.number], 'unicorns', [], new Map());
|
||||
//=> 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)
|
||||
|
||||
Returns `true` if **all** of the input `values` returns true in the `predicate`:
|
||||
|
||||
```js
|
||||
is.all(is.object, {}, new Map(), new Set());
|
||||
//=> true
|
||||
|
||||
is.all(is.string, '🦄', [], 'unicorns');
|
||||
//=> 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)
|
||||
|
||||
Returns `true` if the value is a valid date.
|
||||
|
||||
All [`Date`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date) objects have an internal timestamp value which is the number of milliseconds since the [Unix epoch](https://developer.mozilla.org/en-US/docs/Glossary/Unix_time). When a new `Date` is constructed with bad inputs, no error is thrown. Instead, a new `Date` object is returned. But the internal timestamp value is set to `NaN`, which is an `'Invalid Date'`. Bad inputs can be an non-parsable date string, a non-numeric value or a number that is outside of the expected range for a date value.
|
||||
|
||||
```js
|
||||
const valid = new Date('2000-01-01');
|
||||
|
||||
is.date(valid);
|
||||
//=> true
|
||||
valid.getTime();
|
||||
//=> 946684800000
|
||||
valid.toUTCString();
|
||||
//=> 'Sat, 01 Jan 2000 00:00:00 GMT'
|
||||
is.validDate(valid);
|
||||
//=> true
|
||||
|
||||
const invalid = new Date('Not a parsable date string');
|
||||
|
||||
is.date(invalid);
|
||||
//=> true
|
||||
invalid.getTime();
|
||||
//=> NaN
|
||||
invalid.toUTCString();
|
||||
//=> 'Invalid Date'
|
||||
is.validDate(invalid);
|
||||
//=> false
|
||||
```
|
||||
|
||||
##### .validLength(value)
|
||||
|
||||
Returns `true` if the value is a safe integer that is greater than or equal to zero.
|
||||
|
||||
This can be useful to confirm that a value is a valid count of something, ie. 0 or more.
|
||||
|
||||
##### .whitespaceString(value)
|
||||
|
||||
Returns `true` if the value is a string with only whitespace characters.
|
||||
|
||||
## Type guards
|
||||
|
||||
When using `is` together with TypeScript, [type guards](http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) are being used extensively to infer the correct type inside if-else statements.
|
||||
|
||||
```ts
|
||||
import is from '@sindresorhus/is';
|
||||
|
||||
const padLeft = (value: string, padding: string | number) => {
|
||||
if (is.number(padding)) {
|
||||
// `padding` is typed as `number`
|
||||
return Array(padding + 1).join(' ') + value;
|
||||
}
|
||||
|
||||
if (is.string(padding)) {
|
||||
// `padding` is typed as `string`
|
||||
return padding + value;
|
||||
}
|
||||
|
||||
throw new TypeError(`Expected 'padding' to be of type 'string' or 'number', got '${is(padding)}'.`);
|
||||
}
|
||||
|
||||
padLeft('🦄', 3);
|
||||
//=> ' 🦄'
|
||||
|
||||
padLeft('🦄', '🌈');
|
||||
//=> '🌈🦄'
|
||||
```
|
||||
|
||||
## Type assertions
|
||||
|
||||
The type guards are also available as [type assertions](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html#assertion-functions), which throw an error for unexpected types. It is a convenient one-line version of the often repetitive "if-not-expected-type-throw" pattern.
|
||||
|
||||
```ts
|
||||
import {assert} from '@sindresorhus/is';
|
||||
|
||||
const handleMovieRatingApiResponse = (response: unknown) => {
|
||||
assert.plainObject(response);
|
||||
// `response` is now typed as a plain `object` with `unknown` properties.
|
||||
|
||||
assert.number(response.rating);
|
||||
// `response.rating` is now typed as a `number`.
|
||||
|
||||
assert.string(response.title);
|
||||
// `response.title` is now typed as a `string`.
|
||||
|
||||
return `${response.title} (${response.rating * 10})`;
|
||||
};
|
||||
|
||||
handleMovieRatingApiResponse({rating: 0.87, title: 'The Matrix'});
|
||||
//=> 'The Matrix (8.7)'
|
||||
|
||||
// This throws an error.
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
Use generic type parameters with caution. They are only checked by the TypeScript compiler, and not checked by `is` at runtime. This can lead to unexpected behavior, where the generic type is _assumed_ at compile-time, but actually is something completely different at runtime. It is best to use `unknown` (default) and type-check the value of the generic type parameter at runtime with `is` or `assert`.
|
||||
|
||||
```ts
|
||||
import {assert} from '@sindresorhus/is';
|
||||
|
||||
async function badNumberAssumption(input: unknown) {
|
||||
// Bad assumption about the generic type parameter fools the compile-time type system.
|
||||
assert.promise<number>(input);
|
||||
// `input` is a `Promise` but only assumed to be `Promise<number>`.
|
||||
|
||||
const resolved = await input;
|
||||
// `resolved` is typed as `number` but was not actually checked at runtime.
|
||||
|
||||
// Multiplication will return NaN if the input promise did not actually contain a number.
|
||||
return 2 * resolved;
|
||||
}
|
||||
|
||||
async function goodNumberAssertion(input: unknown) {
|
||||
assert.promise(input);
|
||||
// `input` is typed as `Promise<unknown>`
|
||||
|
||||
const resolved = await input;
|
||||
// `resolved` is typed as `unknown`
|
||||
|
||||
assert.number(resolved);
|
||||
// `resolved` is typed as `number`
|
||||
|
||||
// Uses runtime checks so only numbers will reach the multiplication.
|
||||
return 2 * resolved;
|
||||
}
|
||||
|
||||
badNumberAssumption(Promise.resolve('An unexpected string'));
|
||||
//=> NaN
|
||||
|
||||
// This correctly throws an error because of the unexpected string value.
|
||||
goodNumberAssertion(Promise.resolve('An unexpected string'));
|
||||
```
|
||||
|
||||
## FAQ
|
||||
|
||||
|
|
@ -135,9 +900,13 @@ For the ones I found, pick 3 of these.
|
|||
|
||||
The most common mistakes I noticed in these modules was using `instanceof` for type checking, forgetting that functions are objects, and omitting `symbol` as a primitive.
|
||||
|
||||
### Why not just use `instanceof` instead of this package?
|
||||
|
||||
`instanceof` does not work correctly for all types and it does not work across [realms](https://stackoverflow.com/a/49832343/64949). Examples of realms are iframes, windows, web workers, and the `vm` module in Node.js.
|
||||
|
||||
## Related
|
||||
|
||||
- [environment](https://github.com/sindresorhus/environment) - Check which JavaScript environment your code is running in at runtime
|
||||
- [is-stream](https://github.com/sindresorhus/is-stream) - Check if something is a Node.js stream
|
||||
- [is-observable](https://github.com/sindresorhus/is-observable) - Check if a value is an Observable
|
||||
- [file-type](https://github.com/sindresorhus/file-type) - Detect the file type of a Buffer/Uint8Array
|
||||
|
|
@ -145,8 +914,11 @@ The most common mistakes I noticed in these modules was using `instanceof` for t
|
|||
- [is-array-sorted](https://github.com/sindresorhus/is-array-sorted) - Check if an Array is sorted
|
||||
- [is-error-constructor](https://github.com/sindresorhus/is-error-constructor) - Check if a value is an error constructor
|
||||
- [is-empty-iterable](https://github.com/sindresorhus/is-empty-iterable) - Check if an Iterable is empty
|
||||
- [is-blob](https://github.com/sindresorhus/is-blob) - Check if a value is a Blob - File-like object of immutable, raw data
|
||||
- [has-emoji](https://github.com/sindresorhus/has-emoji) - Check whether a string has any emoji
|
||||
|
||||
## Maintainers
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Sindre Sorhus](https://sindresorhus.com)
|
||||
- [Sindre Sorhus](https://github.com/sindresorhus)
|
||||
- [Giora Guttsait](https://github.com/gioragutt)
|
||||
- [Brandon Smith](https://github.com/brandon93s)
|
||||
|
|
|
|||
2038
source/index.ts
Normal file
2038
source/index.ts
Normal file
File diff suppressed because it is too large
Load diff
199
source/types.ts
Normal file
199
source/types.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
// Extracted from https://github.com/sindresorhus/type-fest/blob/78019f42ea888b0cdceb41a4a78163868de57555/index.d.ts
|
||||
|
||||
/**
|
||||
Matches any [primitive value](https://developer.mozilla.org/en-US/docs/Glossary/Primitive).
|
||||
*/
|
||||
export type Primitive =
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
||||
| null
|
||||
| undefined
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| symbol
|
||||
| bigint;
|
||||
|
||||
/**
|
||||
Matches a [`class` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
|
||||
*/
|
||||
type Constructor<T, Arguments extends unknown[] = any[]> = new(...arguments_: Arguments) => T;
|
||||
|
||||
/**
|
||||
Matches a [`class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes).
|
||||
*/
|
||||
export type Class<T, Arguments extends unknown[] = any[]> = Constructor<T, Arguments> & {prototype: T};
|
||||
|
||||
/**
|
||||
Matches any [typed array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray), like `Uint8Array` or `Float64Array`.
|
||||
*/
|
||||
export type TypedArray =
|
||||
| Int8Array
|
||||
| Uint8Array
|
||||
| Uint8ClampedArray
|
||||
| Int16Array
|
||||
| Uint16Array
|
||||
| Int32Array
|
||||
| Uint32Array
|
||||
| Float32Array
|
||||
| Float64Array
|
||||
| BigInt64Array
|
||||
| BigUint64Array;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions -- This must be an `interface` so it can be merged.
|
||||
interface SymbolConstructor {
|
||||
readonly observable: symbol;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Matches a value that is like an [Observable](https://github.com/tc39/proposal-observable).
|
||||
*/
|
||||
export type ObservableLike = {
|
||||
subscribe(observer: (value: unknown) => void): void;
|
||||
[Symbol.observable](): ObservableLike;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
||||
export type Falsy = false | 0 | 0n | '' | null | undefined;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-types
|
||||
export type WeakRef<T extends object> = {
|
||||
readonly [Symbol.toStringTag]: 'WeakRef';
|
||||
deref(): T | undefined;
|
||||
};
|
||||
|
||||
export type ArrayLike<T> = {
|
||||
readonly [index: number]: T;
|
||||
readonly length: number;
|
||||
};
|
||||
|
||||
export type NodeStream = {
|
||||
pipe<T extends NodeJS.WritableStream>(destination: T, options?: {end?: boolean}): T;
|
||||
} & NodeJS.EventEmitter;
|
||||
|
||||
export type Predicate = (value: unknown) => boolean;
|
||||
|
||||
export type NonEmptyString = string & {0: string};
|
||||
|
||||
export type Whitespace = ' ';
|
||||
|
||||
type Brand<Key extends string> = Readonly<Record<Key, true>>;
|
||||
|
||||
/**
|
||||
A string that represents a valid URL.
|
||||
|
||||
This is a branded type to prevent incorrect TypeScript type narrowing.
|
||||
*/
|
||||
export type UrlString = string & {readonly __brand: 'UrlString'};
|
||||
|
||||
// Keep numeric guards branded and simple. This intentionally favors correct false-branch narrowing for `number` inputs over perfect success-branch narrowing for numeric literal unions.
|
||||
|
||||
/**
|
||||
The IEEE 754 "Not-a-Number" value, typed as a subtype of `number`.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type NaN = number & Brand<'__nanBrand'>;
|
||||
|
||||
/**
|
||||
A finite number (excludes `NaN`, `Infinity`, and `-Infinity`).
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type FiniteNumber = number & Brand<'__finiteNumberBrand'>;
|
||||
|
||||
/**
|
||||
A number greater than or equal to zero.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type NonNegativeNumber = number & Brand<'__nonNegativeNumberBrand'>;
|
||||
|
||||
/**
|
||||
An integer value (no fractional part).
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type Integer = FiniteNumber & Brand<'__integerBrand'>;
|
||||
|
||||
/**
|
||||
A number greater than zero.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type PositiveNumber = NonNegativeNumber & Brand<'__positiveNumberBrand'>;
|
||||
|
||||
/**
|
||||
A number less than zero.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type NegativeNumber = number & Brand<'__negativeNumberBrand'>;
|
||||
|
||||
/**
|
||||
An integer less than zero.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type NegativeInteger = Integer & NegativeNumber & Brand<'__negativeIntegerBrand'>;
|
||||
|
||||
/**
|
||||
An integer greater than or equal to zero.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type NonNegativeInteger = Integer & NonNegativeNumber & Brand<'__nonNegativeIntegerBrand'>;
|
||||
|
||||
/**
|
||||
An integer greater than zero.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type PositiveInteger = NonNegativeInteger & PositiveNumber & Brand<'__positiveIntegerBrand'>;
|
||||
|
||||
// Note: type-fest uses the `1e999` overflow trick to represent these types (since TypeScript has
|
||||
// no built-in Infinity type), but we use branded types here for consistency and to avoid
|
||||
// relying on numeric overflow behavior.
|
||||
|
||||
/**
|
||||
A positive infinite number (`Infinity`).
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type PositiveInfinity = PositiveNumber & Brand<'__positiveInfinityBrand'>;
|
||||
|
||||
/**
|
||||
A negative infinite number (`-Infinity`).
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type NegativeInfinity = NegativeNumber & Brand<'__negativeInfinityBrand'>;
|
||||
|
||||
/**
|
||||
A safe integer (within the range of `Number.MIN_SAFE_INTEGER` to `Number.MAX_SAFE_INTEGER`).
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type SafeInteger = Integer & Brand<'__safeIntegerBrand'>;
|
||||
|
||||
/**
|
||||
An even integer.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type EvenInteger = Integer & Brand<'__evenIntegerBrand'>;
|
||||
|
||||
/**
|
||||
An odd integer.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type OddInteger = Integer & Brand<'__oddIntegerBrand'>;
|
||||
|
||||
/**
|
||||
A non-negative safe integer, suitable as an array or string length.
|
||||
|
||||
Branded to prevent false-branch narrowing to `never` when the input is `number`.
|
||||
*/
|
||||
export type ValidLength = SafeInteger & NonNegativeInteger & Brand<'__validLengthBrand'>;
|
||||
3
source/utilities.ts
Normal file
3
source/utilities.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
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
|
||||
}
|
||||
295
test.js
295
test.js
|
|
@ -1,295 +0,0 @@
|
|||
import util from 'util';
|
||||
import test from 'ava';
|
||||
import m from '.';
|
||||
|
||||
const isNode8orHigher = Number(process.versions.node.split('.')[0]) >= 8;
|
||||
|
||||
const PromiseSubclassFixture = class extends Promise {};
|
||||
const ErrorSubclassFixture = class extends Error {};
|
||||
|
||||
const types = new Map([
|
||||
['undefined', undefined],
|
||||
['null', null],
|
||||
['string', '🦄'],
|
||||
['number', [
|
||||
6,
|
||||
1.4,
|
||||
0,
|
||||
-0,
|
||||
Infinity,
|
||||
-Infinity
|
||||
]],
|
||||
['boolean', [
|
||||
true,
|
||||
false
|
||||
]],
|
||||
['symbol', Symbol('🦄')],
|
||||
['array', [
|
||||
[1, 2],
|
||||
new Array(2)
|
||||
]],
|
||||
['function', [
|
||||
function foo() {}, // eslint-disable-line func-names
|
||||
function () {},
|
||||
() => {},
|
||||
async function () {},
|
||||
function * () {}
|
||||
]],
|
||||
['buffer', Buffer.from('🦄')],
|
||||
['object', [
|
||||
{x: 1},
|
||||
Object.create({x: 1})
|
||||
]],
|
||||
['regExp', [
|
||||
/\w/,
|
||||
new RegExp('\\w')
|
||||
]],
|
||||
['date', new Date()],
|
||||
['error', [
|
||||
new Error('🦄'),
|
||||
new ErrorSubclassFixture()
|
||||
]],
|
||||
['nativePromise', [
|
||||
Promise.resolve(),
|
||||
PromiseSubclassFixture.resolve()
|
||||
]],
|
||||
['promise', {then() {}, catch() {}}],
|
||||
['map', new Map()],
|
||||
['set', new Set()],
|
||||
['weakMap', new WeakMap()],
|
||||
['int8Array', new Int8Array()],
|
||||
['uint8Array', new Uint8Array()],
|
||||
['uint8ClampedArray', new Uint8ClampedArray()],
|
||||
['uint16Array', new Uint16Array()],
|
||||
['int32Array', new Int32Array()],
|
||||
['uint32Array', new Uint32Array()],
|
||||
['float32Array', new Float32Array()],
|
||||
['float64Array', new Float64Array()],
|
||||
['arrayBuffer', new ArrayBuffer(10)],
|
||||
['nan', [
|
||||
NaN,
|
||||
Number.NaN
|
||||
]],
|
||||
['nullOrUndefined', [
|
||||
null,
|
||||
undefined
|
||||
]],
|
||||
['plainObject', [
|
||||
{x: 1},
|
||||
Object.create(null),
|
||||
new Object() // eslint-disable-line no-new-object
|
||||
]],
|
||||
['integer', 6]
|
||||
]);
|
||||
|
||||
// This ensure a certain method matches only the types
|
||||
// it's supposed to and none of the other methods' types
|
||||
const testType = (t, type, exclude) => {
|
||||
for (const [key, value] of types) {
|
||||
// TODO: Automatically exclude value types in other tests that we have in the current one.
|
||||
// Could reduce the use of `exclude`.
|
||||
if (exclude && exclude.indexOf(key) !== -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const assert = key === type ? t.true.bind(t) : t.false.bind(t);
|
||||
const is = m[type];
|
||||
const fixtures = Array.isArray(value) ? value : [value];
|
||||
|
||||
for (const fixture of fixtures) {
|
||||
assert(is(fixture), `Value: ${util.inspect(fixture)}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
test('is.undefined', t => {
|
||||
testType(t, 'undefined', ['nullOrUndefined']);
|
||||
});
|
||||
|
||||
test('is.null', t => {
|
||||
testType(t, 'null', ['nullOrUndefined']);
|
||||
});
|
||||
|
||||
test('is.string', t => {
|
||||
testType(t, 'string');
|
||||
});
|
||||
|
||||
test('is.number', t => {
|
||||
testType(t, 'number', ['nan', 'integer']);
|
||||
});
|
||||
|
||||
test('is.boolean', t => {
|
||||
testType(t, 'boolean');
|
||||
});
|
||||
|
||||
test('is.symbol', t => {
|
||||
testType(t, 'symbol');
|
||||
});
|
||||
|
||||
test('is.array', t => {
|
||||
testType(t, 'array');
|
||||
});
|
||||
|
||||
test('is.function', t => {
|
||||
testType(t, 'function');
|
||||
});
|
||||
|
||||
test('is.buffer', t => {
|
||||
testType(t, 'buffer');
|
||||
});
|
||||
|
||||
test('is.object', t => {
|
||||
for (const el of types.get('object')) {
|
||||
t.true(m.object(el));
|
||||
}
|
||||
});
|
||||
|
||||
test('is.regExp', t => {
|
||||
testType(t, 'regExp');
|
||||
});
|
||||
|
||||
test('is.date', t => {
|
||||
testType(t, 'date');
|
||||
});
|
||||
|
||||
test('is.error', t => {
|
||||
testType(t, 'error');
|
||||
});
|
||||
|
||||
if (isNode8orHigher) {
|
||||
test('is.nativePromise', t => {
|
||||
testType(t, 'nativePromise');
|
||||
});
|
||||
|
||||
test('is.promise', t => {
|
||||
testType(t, 'promise', ['nativePromise']);
|
||||
});
|
||||
}
|
||||
|
||||
test('is.map', t => {
|
||||
testType(t, 'map');
|
||||
});
|
||||
|
||||
test('is.set', t => {
|
||||
testType(t, 'set');
|
||||
});
|
||||
|
||||
test('is.weakMap', t => {
|
||||
testType(t, 'weakMap');
|
||||
});
|
||||
|
||||
test('is.weakSet', t => {
|
||||
testType(t, 'weakSet');
|
||||
});
|
||||
|
||||
test('is.int8Array', t => {
|
||||
testType(t, 'int8Array');
|
||||
});
|
||||
|
||||
test('is.uint8Array', t => {
|
||||
testType(t, 'uint8Array', ['buffer']);
|
||||
});
|
||||
|
||||
test('is.uint8ClampedArray', t => {
|
||||
testType(t, 'uint8ClampedArray');
|
||||
});
|
||||
|
||||
test('is.int16Array', t => {
|
||||
testType(t, 'int16Array');
|
||||
});
|
||||
|
||||
test('is.uint16Array', t => {
|
||||
testType(t, 'uint16Array');
|
||||
});
|
||||
|
||||
test('is.int32Array', t => {
|
||||
testType(t, 'int32Array');
|
||||
});
|
||||
|
||||
test('is.uint32Array', t => {
|
||||
testType(t, 'uint32Array');
|
||||
});
|
||||
|
||||
test('is.float32Array', t => {
|
||||
testType(t, 'float32Array');
|
||||
});
|
||||
|
||||
test('is.float64Array', t => {
|
||||
testType(t, 'float64Array');
|
||||
});
|
||||
|
||||
test('is.arrayBuffer', t => {
|
||||
testType(t, 'arrayBuffer');
|
||||
});
|
||||
|
||||
test('is.dataView', t => {
|
||||
testType(t, 'arrayBuffer');
|
||||
});
|
||||
|
||||
test('is.nan', t => {
|
||||
testType(t, 'nan');
|
||||
});
|
||||
|
||||
test('is.nullOrUndefined', t => {
|
||||
testType(t, 'nullOrUndefined', ['undefined', 'null']);
|
||||
});
|
||||
|
||||
test('is.primitive', t => {
|
||||
const primitives = [
|
||||
undefined,
|
||||
null,
|
||||
'🦄',
|
||||
6,
|
||||
Infinity,
|
||||
-Infinity,
|
||||
true,
|
||||
false,
|
||||
Symbol('🦄')
|
||||
];
|
||||
|
||||
for (const el of primitives) {
|
||||
t.true(m.primitive(el));
|
||||
}
|
||||
});
|
||||
|
||||
test('is.integer', t => {
|
||||
testType(t, 'integer', ['number']);
|
||||
t.false(m.integer(1.4));
|
||||
});
|
||||
|
||||
test('is.plainObject', t => {
|
||||
testType(t, 'plainObject', ['object', 'promise']);
|
||||
});
|
||||
|
||||
test('is.iterable', t => {
|
||||
t.true(m.iterable(''));
|
||||
t.true(m.iterable([]));
|
||||
t.true(m.iterable(new Map()));
|
||||
t.false(m.iterable(null));
|
||||
t.false(m.iterable(undefined));
|
||||
t.false(m.iterable(0));
|
||||
t.false(m.iterable(NaN));
|
||||
t.false(m.iterable(Infinity));
|
||||
t.false(m.iterable({}));
|
||||
});
|
||||
|
||||
test('is.typedArray', t => {
|
||||
const typedArrays = [
|
||||
new Int8Array(),
|
||||
new Uint8Array(),
|
||||
new Uint8ClampedArray(),
|
||||
new Uint16Array(),
|
||||
new Int32Array(),
|
||||
new Uint32Array(),
|
||||
new Float32Array(),
|
||||
new Float64Array()
|
||||
];
|
||||
|
||||
for (const el of typedArrays) {
|
||||
t.true(m.typedArray(el));
|
||||
}
|
||||
|
||||
t.false(m.typedArray(new ArrayBuffer(1)));
|
||||
t.false(m.typedArray([]));
|
||||
t.false(m.typedArray({}));
|
||||
});
|
||||
2822
test/test.ts
Normal file
2822
test/test.ts
Normal file
File diff suppressed because it is too large
Load diff
12
test/tsconfig.json
Normal file
12
test/tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "..",
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false
|
||||
},
|
||||
"include": [
|
||||
"../source",
|
||||
"type-tests.ts"
|
||||
]
|
||||
}
|
||||
405
test/type-tests.ts
Normal file
405
test/type-tests.ts
Normal 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('🦄');
|
||||
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "@sindresorhus/tsconfig",
|
||||
"compilerOptions": {
|
||||
"types": ["node"],
|
||||
"rootDir": "source",
|
||||
"allowImportingTsExtensions": true,
|
||||
"rewriteRelativeImportExtensions": true
|
||||
},
|
||||
"include": [
|
||||
"source"
|
||||
],
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue