├── .babelrc ├── .github └── workflows │ └── validation.yaml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── __tests__ └── index.test.ts ├── package-lock.json ├── package.json ├── src ├── array.ts ├── index.ts ├── is.ts ├── only.ts └── types.ts └── tsconfig.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ "@babel/preset-env", { "loose": true } ] 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/validation.yaml: -------------------------------------------------------------------------------- 1 | name: Validation 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | validation: 7 | name: Validation Build 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Setup Node.js 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: 12.x 15 | - name: Install dependencies 16 | run: | 17 | npm ci 18 | - name: Build 19 | run: | 20 | npm run build 21 | env: 22 | CI: true 23 | - name: Test 24 | run: | 25 | npm test 26 | env: 27 | CI: true 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/ 2 | tsconfig.json 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Simon Alling 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ts-type-guards 2 | > Curried TypeScript type guards for primitive types and classes 3 | 4 | [![NPM Version][npm-image]][npm-url] 5 | [![Downloads Stats][npm-downloads]][npm-url] 6 | 7 | Simplifies typechecking by providing type guards to check if something is of a certain type or of the same type as something else. Includes tailor-made type guards for the primitive types and a general one for "classy" types. 8 | 9 | 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install ts-type-guards --save 15 | ``` 16 | 17 | 18 | 19 | ## Usage Examples 20 | 21 | ### Basic Usage 22 | 23 | ```javascript 24 | import { is } from "ts-type-guards"; 25 | 26 | const header = document.querySelector("header"); 27 | console.log(header.textContent); // Error: Object is possibly 'null'. 28 | if (is(HTMLElement)(header)) { 29 | console.log(header.textContent); // Compiles and runs safely. 30 | } 31 | ``` 32 | 33 | Because `is`, `only` etc are curried, you can use them like so: 34 | 35 | ```javascript 36 | import { is } from "ts-type-guards"; 37 | 38 | const foos = Array.from(document.querySelectorAll(".foo")); 39 | const fooImages = foos.filter(is(HTMLImageElement)); 40 | const srcs = fooImages.map(img => img.src); // Compiles and runs safely. 41 | ``` 42 | 43 | Equivalent: 44 | 45 | ```javascript 46 | import { only } from "ts-type-guards"; 47 | 48 | const foos = Array.from(document.querySelectorAll(".foo")); 49 | const fooImages = only(HTMLImageElement)(foos); 50 | const srcs = fooImages.map(img => img.src); // Compiles and runs safely. 51 | ``` 52 | 53 | 54 | ### Checking Against Another Value 55 | 56 | Use `isLike` to check if something is of the same type as a reference value: 57 | 58 | ```javascript 59 | import { isLike } from "ts-type-guards"; 60 | 61 | // We want to make sure that this function always returns a T: 62 | function getFromLocalStorage(key: string, fallback: T): T { 63 | const saved: string | null = localStorage.getItem(key); 64 | if (isNull(saved)) { 65 | return fallback; 66 | } 67 | const parsed: any = JSON.parse(saved); 68 | return ( 69 | isLike(fallback)(parsed) 70 | ? parsed // parsed is like fallback, so it is a T! 71 | : fallback // parsed has wrong type, so return fallback. 72 | ); 73 | } 74 | 75 | getFromLocalStorage("volume", 50); // Guaranteed to be a number. 76 | ``` 77 | 78 | (Note that this function can still throw `DOMException` or `SyntaxError`, but that's not a typechecking problem.) 79 | 80 | 81 | ### Subclasses 82 | 83 | `is` is basically a partially applicable `instanceof`. For classy types, `isLike(ref)(x)` is equivalent to `x instanceof ref.constructor`. 84 | 85 | ```javascript 86 | class Animal {} 87 | class Lion extends Animal {} 88 | class Warthog extends Animal {} 89 | 90 | const someone = new Animal(); 91 | const simba = new Lion(); 92 | const nala = new Lion(); 93 | const pumbaa = new Warthog(); 94 | 95 | is(Animal)(simba); // true 96 | is(Lion)(simba); // true 97 | is(Warthog)(simba); // false 98 | is(Lion)(someone); // false 99 | 100 | isLike(someone)(simba); // true 101 | isLike(nala)(simba); // true 102 | isLike(pumbaa)(simba); // false 103 | isLike(nala)(someone); // false 104 | ``` 105 | 106 | 107 | ### Primitive Types 108 | 109 | `is` can only handle classy types, so the primitive ones have their own type guards: 110 | 111 | ```javascript 112 | isUndefined(undefined); // true 113 | isNumber("5"); // false 114 | ``` 115 | 116 | `isLike` supports the primitive types as well: 117 | 118 | ```javascript 119 | isLike(5)(1.0); // true (because all numbers are floating point in JS) 120 | isLike(null)(undefined); // false 121 | ``` 122 | 123 | The non-primitive types `Boolean`, `Number` and `String` share some, but not all, semantics with the primitive types `boolean`, `number` and `string`, respectively. The main difference lies in their equality semantics: 124 | 125 | ```javascript 126 | "foo" === "foo" ; // true 127 | new String("foo") === new String("foo"); // false 128 | ``` 129 | 130 | `ts-type-guards` includes type guards for the cases when you don't care whether a value is of a primitive type or its pseudo-primitive counterpart. For example, to check if a value is either a `string` or a `String`, use `isStringLike`. 131 | 132 | 133 | ### Reusing Type Guards 134 | 135 | Although it may seem clunky to have to write `is(x)(y)` instead of `is(x, y)`, this is a design choice based on the fact that partial application is so awesome. Not only does it get rid of `xs.filter(x => is(T, x))` in favor of `xs.filter(is(T))`, it also lets you save and reuse type guards: 136 | 137 | ```javascript 138 | const isFoo = is(LongModuleName.Foo); 139 | 140 | if (isFoo(x)) { 141 | x.baz(); 142 | } 143 | 144 | xs.filter(isFoo).forEach(x => x.baz()); 145 | ``` 146 | 147 | 148 | ### Arrays 149 | 150 | You can check if something is an array of a certain type: 151 | 152 | ```javascript 153 | isArrayOfNumbers([1, 2, 3]); // true 154 | isArrayOfNumbers([1, 2, "3"]); // false 155 | isArrayOf(Error)([ 156 | new RangeError(), 157 | new TypeError(), 158 | ]); // true 159 | ``` 160 | 161 | 162 | 163 | ## Contributing 164 | 165 | 1. [Fork the repo](https://github.com/SimonAlling/ts-type-guards/fork). 166 | 1. Create your feature branch (`git checkout -b feature/foobar`). 167 | 1. Examine and add your changes (`git diff`, then `git add ...`). 168 | 1. Commit your changes (`git commit -m 'Add some foobar'`). 169 | 1. Push your feature branch (`git push origin feature/foobar`). 170 | 1. [Create a pull request](https://github.com/SimonAlling/ts-type-guards/pulls). 171 | 172 | 173 | 174 | ## License 175 | 176 | [MIT](http://vjpr.mit-license.org) 177 | 178 | 179 | [npm-image]: https://img.shields.io/npm/v/ts-type-guards.svg 180 | [npm-url]: https://npmjs.org/package/ts-type-guards 181 | [npm-downloads]: https://img.shields.io/npm/dm/ts-type-guards.svg 182 | -------------------------------------------------------------------------------- /__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | isBoolean, 3 | isBooleanLike, 4 | isNumber, 5 | isNumberLike, 6 | isString, 7 | isStringLike, 8 | isSymbol, 9 | isNull, 10 | isUndefined, 11 | isNothing, 12 | isSomething, 13 | isPrimitive, 14 | isNonPrimitive, 15 | is, 16 | isLike, 17 | } from "../src/index"; 18 | 19 | const SATISFY = true; 20 | const NOT_SATISFY = false; 21 | 22 | const SOUNDNESS = false; 23 | const COMPLETENESS = true; 24 | 25 | function to(shouldSatisfy: boolean) { 26 | return (received: T, predicate: (x: T) => boolean) => { 27 | const satisfied = predicate(received); 28 | const predicateAsString = (predicate as { name?: string }).name || predicate.toString(); 29 | return { 30 | message: () => `expected ${JSON.stringify(received)} ${satisfied ? "not " : ""}to satisfy ${predicateAsString}`, 31 | pass: shouldSatisfy === satisfied, 32 | }; 33 | }; 34 | } 35 | 36 | interface Expect extends jest.Matchers { 37 | toSatisfy: (x: any) => boolean 38 | toNotSatisfy: (x: any) => boolean 39 | extend: (extensions: { [ k: string ]: any }) => void 40 | (x: any): Expect 41 | } 42 | 43 | declare const expect: Expect 44 | 45 | expect.extend({ 46 | toSatisfy: to(SATISFY), 47 | toNotSatisfy: to(NOT_SATISFY), 48 | }); 49 | 50 | function checkPredicate(shouldSatisfy: boolean) { 51 | return (predicate: (x: any) => boolean, xs: ReadonlyArray): void => { 52 | xs.forEach(x => { 53 | (shouldSatisfy ? expect(x).toSatisfy : expect(x).toNotSatisfy)(predicate); 54 | }); 55 | }; 56 | } 57 | 58 | function check(predicate: (x: any) => boolean, values: { 59 | shouldSatisfy: ReadonlyArray, 60 | shouldNotSatisfy: ReadonlyArray, 61 | }): void { 62 | checkPredicate(SOUNDNESS)(predicate, values.shouldNotSatisfy); 63 | checkPredicate(COMPLETENESS)(predicate, values.shouldSatisfy); 64 | } 65 | 66 | it("isUndefined", () => { 67 | check(isUndefined, { 68 | shouldSatisfy: [ undefined ], 69 | shouldNotSatisfy: [ null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 70 | }); 71 | }); 72 | 73 | it("isNull", () => { 74 | check(isNull, { 75 | shouldSatisfy: [ null ], 76 | shouldNotSatisfy: [ undefined, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 77 | }); 78 | }); 79 | 80 | it("isNothing", () => { 81 | check(isNothing, { 82 | shouldSatisfy: [ null, undefined ], 83 | shouldNotSatisfy: [ true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 84 | }); 85 | }); 86 | 87 | it("isSomething", () => { 88 | check(isSomething, { 89 | shouldSatisfy: [ true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 90 | shouldNotSatisfy: [ null, undefined ], 91 | }); 92 | class Cat { name: string } 93 | class Dog { name: string } 94 | function accessName(x: Cat | Dog | null | undefined) { 95 | if (isSomething(x)) { 96 | x.name; // should compile 97 | } 98 | } 99 | }); 100 | 101 | it("isBoolean", () => { 102 | check(isBoolean, { 103 | shouldSatisfy: [ true, false ], 104 | shouldNotSatisfy: [ undefined, null, 0, 1, "", "foo", Symbol(), _ => 5, [], {}, new Boolean(true) ], 105 | }); 106 | }); 107 | 108 | it("isBooleanLike", () => { 109 | check(isBooleanLike, { 110 | shouldSatisfy: [ true, false, new Boolean(true), new Boolean(false) ], 111 | shouldNotSatisfy: [ undefined, null, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 112 | }); 113 | }); 114 | 115 | it("isNumber", () => { 116 | check(isNumber, { 117 | shouldSatisfy: [ 0, 1, -1, Math.PI, NaN, Infinity, -Infinity ], 118 | shouldNotSatisfy: [ undefined, null, true, false, "", "foo", Symbol(), _ => 5, [], {}, new Number(1) ], 119 | }); 120 | }); 121 | 122 | it("isNumberLike", () => { 123 | check(isNumberLike, { 124 | shouldSatisfy: [ 0, 1, -1, Math.PI, NaN, Infinity, -Infinity, new Number(1) ], 125 | shouldNotSatisfy: [ undefined, null, true, false, "", "foo", Symbol(), _ => 5, [], {} ], 126 | }); 127 | }); 128 | 129 | it("isString", () => { 130 | check(isString, { 131 | shouldSatisfy: [ "", "foo" ], 132 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, Symbol(), _ => 5, [], {}, new String("foo") ], 133 | }); 134 | }); 135 | 136 | it("isStringLike", () => { 137 | check(isStringLike, { 138 | shouldSatisfy: [ "", "foo", new String("foo") ], 139 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, Symbol(), _ => 5, [], {} ], 140 | }); 141 | }); 142 | 143 | it("isSymbol", () => { 144 | check(isSymbol, { 145 | shouldSatisfy: [ Symbol() ], 146 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", _ => 5, [], {} ], 147 | }); 148 | }); 149 | 150 | it("isPrimitive", () => { 151 | check(isPrimitive, { 152 | shouldSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol() ], 153 | shouldNotSatisfy: [ _ => 5, [], {}, new Boolean(true), new Number(1), new String("foo") ], 154 | }); 155 | }); 156 | 157 | it("isNonPrimitive", () => { 158 | check(isNonPrimitive, { 159 | shouldSatisfy: [ _ => 5, [], {}, new Boolean(true), new Number(1), new String("foo") ], 160 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol() ], 161 | }); 162 | }); 163 | 164 | class Animal { constructor(public readonly name: string) {} } 165 | class Lion extends Animal {} 166 | class Warthog extends Animal {} 167 | const someone = new Animal("Someone"); 168 | const simba = new Lion("Simba"); 169 | const nala = new Lion("Nala"); 170 | const pumbaa = new Warthog("Pumbaa"); 171 | 172 | it("is", () => { 173 | check(is(undefined), { 174 | shouldSatisfy: [], 175 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 176 | }); 177 | check(is(null), { 178 | shouldSatisfy: [], 179 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 180 | }); 181 | check(is(Animal), { 182 | shouldSatisfy: [ simba, nala, pumbaa, someone ], 183 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 184 | }); 185 | check(is(Lion), { 186 | shouldSatisfy: [ simba, nala ], 187 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {}, pumbaa, someone ], 188 | }); 189 | check(is(Warthog), { 190 | shouldSatisfy: [ pumbaa ], 191 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {}, simba, nala, someone ], 192 | }); 193 | }); 194 | 195 | it("isLike for basic types and class instances", () => { 196 | check(isLike(undefined), { 197 | shouldSatisfy: [ undefined ], 198 | shouldNotSatisfy: [ null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 199 | }); 200 | check(isLike(null), { 201 | shouldSatisfy: [ null ], 202 | shouldNotSatisfy: [ undefined, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 203 | }); 204 | check(isLike(true), { 205 | shouldSatisfy: [ true, false ], 206 | shouldNotSatisfy: [ undefined, null, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 207 | }); 208 | check(isLike(false), { 209 | shouldSatisfy: [ true, false ], 210 | shouldNotSatisfy: [ undefined, null, 0, 1, "", "foo", Symbol(), _ => 5, [], {} ], 211 | }); 212 | check(isLike(5), { 213 | shouldSatisfy: [ 0, 1, -1, Math.PI, NaN, Infinity, -Infinity ], 214 | shouldNotSatisfy: [ undefined, null, true, false, "", "foo", Symbol(), _ => 5, [], {} ], 215 | }); 216 | check(isLike("foo"), { 217 | shouldSatisfy: [ "", "bar" ], 218 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, Symbol(), _ => 5, [], {} ], 219 | }); 220 | check(isLike(someone), { 221 | shouldSatisfy: [ simba, nala, pumbaa, someone ], 222 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {}, Animal, Lion, Warthog ], 223 | }); 224 | check(isLike(simba), { 225 | shouldSatisfy: [ simba, nala ], 226 | shouldNotSatisfy: [ undefined, null, true, false, 0, 1, "", "foo", Symbol(), _ => 5, [], {}, Animal, Lion, Warthog, pumbaa, someone ], 227 | }); 228 | }); 229 | 230 | const BASICS: ReadonlyArray = [ undefined, null, true, false, 0, 1, "", "foo", Symbol(), _ => 5 ]; 231 | 232 | it("isLike for dictionaries", () => { 233 | check(isLike({ a: "aaa" }), { 234 | shouldSatisfy: [ { a: "aaa" }, { a: "aaa", b: 5 } ], 235 | shouldNotSatisfy: BASICS.concat([ [], {}, { b: "bbb" }, { a: 5 }, { a: undefined } ]), 236 | }); 237 | check(isLike({ a: "aaa", b: "bbb" }), { 238 | shouldSatisfy: [ { a: "aaa", b: "bbb" }, { a: "aaa", b: "bbb", c: 5 } ], 239 | shouldNotSatisfy: BASICS.concat([ [], {}, { c: "ccc" }, { a: "aaa" }, { a: 5, b: "bbb" }, { a: undefined, b: undefined } ]), 240 | }); 241 | }); 242 | 243 | it("isLike for arrays", () => { 244 | check(isLike([ undefined ]), { 245 | shouldSatisfy: [ [], [ undefined ], [ undefined, undefined, undefined ] ], 246 | shouldNotSatisfy: BASICS.concat([ {}, { b: "bbb" }, { a: 5 }, { a: undefined }, [ null ], [ undefined, null ] ]), 247 | }); 248 | check(isLike([ null ]), { 249 | shouldSatisfy: [ [], [ null ], [ null, null, null ] ], 250 | shouldNotSatisfy: BASICS.concat([ {}, { b: "bbb" }, { a: 5 }, { a: null }, [ undefined ], [ null, undefined ] ]), 251 | }); 252 | check(isLike([ true, false ]), { 253 | shouldSatisfy: [ [], [ true ], [ false ], [ true, true, true, false ] ], 254 | shouldNotSatisfy: BASICS.concat([ {}, { b: "bbb" }, { a: 5 }, { a: undefined }, [ 0 ], [ true, 0 ] ]), 255 | }); 256 | check(isLike([ 0, 1, 2 ]), { 257 | shouldSatisfy: [ [], [ 0 ], [ NaN ], [ 0, 1, NaN, Infinity, -Infinity ] ], 258 | shouldNotSatisfy: BASICS.concat([ {}, { b: "bbb" }, { a: 5 }, { a: undefined }, [ true ], [ 0, true ] ]), 259 | }); 260 | check(isLike([ "foo", "bar" ]), { 261 | shouldSatisfy: [ [], [ "" ], [ "foo" ], [ "foo", "bar" ] ], 262 | shouldNotSatisfy: BASICS.concat([ {}, { b: "bbb" }, { a: 5 }, { a: undefined }, [ true ], [ "foo", true ] ]), 263 | }); 264 | check(isLike([ Symbol(), Symbol() ]), { 265 | shouldSatisfy: [ [], [ Symbol() ], [ Symbol(), Symbol() ] ], 266 | shouldNotSatisfy: BASICS.concat([ {}, { b: "bbb" }, { a: 5 }, { a: undefined }, [ true ], [ Symbol(), true ] ]), 267 | }); 268 | }); 269 | 270 | it("isLike for array of dictionaries", () => { 271 | check(isLike([ { a: "aaa" } ]), { 272 | shouldSatisfy: [ [], [ { a: "a" } ], [ { a: "a" }, { a: "aa" } ] ], 273 | shouldNotSatisfy: BASICS.concat([ {}, [ {} ], [ { b: "bbb" } ], [ { a: 5 } ], [ { a: undefined } ] ]), 274 | }); 275 | }); 276 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-type-guards", 3 | "version": "0.7.0", 4 | "description": "Curried TypeScript type guards for primitive types and classes", 5 | "keywords": [ 6 | "TypeScript", 7 | "type guards", 8 | "type", 9 | "guard", 10 | "isBoolean", 11 | "isNumber", 12 | "isString", 13 | "isSymbol", 14 | "isNull", 15 | "isUndefined", 16 | "isPrimitive", 17 | "isNonPrimitive", 18 | "is", 19 | "isLike", 20 | "isArrayOf", 21 | "only" 22 | ], 23 | "author": { 24 | "name": "Simon Alling", 25 | "email": "alling.simon@gmail.com", 26 | "url": "https://simonalling.se" 27 | }, 28 | "license": "MIT", 29 | "homepage": "https://github.com/simonalling/ts-type-guards", 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/simonalling/ts-type-guards" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/simonalling/ts-type-guards" 36 | }, 37 | "sideEffects": false, 38 | "main": "dist/index", 39 | "types": "./dist/index.d.ts", 40 | "scripts": { 41 | "build": "npm run clean && tsc -d -p . && npm run rename && tsc --module CommonJS -p .", 42 | "clean": "rm -rf dist/*", 43 | "prepublishOnly": "npm run build && npm test", 44 | "rename": "renamer --force --find \"/\\.js$/\" --replace \".mjs\" \"dist/**\"", 45 | "test": "jest" 46 | }, 47 | "jest": { 48 | "transform": { 49 | "^.+\\.ts$": "ts-jest" 50 | }, 51 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(js|ts)$", 52 | "moduleFileExtensions": [ 53 | "ts", 54 | "js" 55 | ] 56 | }, 57 | "devDependencies": { 58 | "@types/jest": "^23.3.1", 59 | "jest": "^23.5.0", 60 | "renamer": "^2.0.1", 61 | "ts-jest": "^23.1.4", 62 | "typescript": "^3.3.4000" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/array.ts: -------------------------------------------------------------------------------- 1 | import { primitive, Classy } from "./types"; 2 | import { isBoolean, isNumber, isString, isSymbol, isNull, isUndefined, isPrimitive, isNonPrimitive, is, isLike } from "./is"; 3 | 4 | export function isArrayOfBooleans(x: unknown): x is boolean[] { 5 | return isArrayOfLike(true)(x); 6 | } 7 | 8 | export function isArrayOfNumbers(x: unknown): x is number[] { 9 | return isArrayOfLike(1)(x); 10 | } 11 | 12 | export function isArrayOfStrings(x: unknown): x is string[] { 13 | return isArrayOfLike("")(x); 14 | } 15 | 16 | export function isArrayOfSymbols(x: unknown): x is symbol[] { 17 | return isArrayOfLike(Symbol())(x); 18 | } 19 | 20 | export function isArrayOfNulls(x: unknown): x is null[] { 21 | return isArrayOfLike(null)(x); 22 | } 23 | 24 | export function isArrayOfUndefineds(x: unknown): x is undefined[] { 25 | return isArrayOfLike(undefined)(x); 26 | } 27 | 28 | export function isArrayOfPrimitives(x: unknown): x is primitive[] { 29 | return is(Array)(x) && x.every(isPrimitive); 30 | } 31 | 32 | export function isArrayOfObjects(x: unknown): x is object[] { 33 | return is(Array)(x) && x.every(isNonPrimitive); 34 | } 35 | 36 | export function isArrayOf(type: Classy): (xs: unknown) => xs is T[] { 37 | return (xs: unknown): xs is T[] => is(Array)(xs) && xs.every(is(type)); 38 | } 39 | 40 | export function isArrayOfLike(reference: T): (x: unknown) => x is T[] { 41 | return (x: unknown): x is T[] => is(Array)(x) && x.every(isLike(reference)); 42 | } 43 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | primitive, 3 | Classy, 4 | TypeGuard, 5 | } from "./types"; 6 | 7 | export { 8 | isBoolean, 9 | isBooleanLike, 10 | isNumber, 11 | isNumberLike, 12 | isString, 13 | isStringLike, 14 | isSymbol, 15 | isNull, 16 | isUndefined, 17 | isSomething, 18 | isNothing, 19 | isPrimitive, 20 | isNonPrimitive, 21 | is, 22 | isLike, 23 | } from "./is"; 24 | 25 | export { 26 | isArrayOfBooleans, 27 | isArrayOfNumbers, 28 | isArrayOfStrings, 29 | isArrayOfSymbols, 30 | isArrayOfNulls, 31 | isArrayOfUndefineds, 32 | isArrayOfPrimitives, 33 | isArrayOfObjects, 34 | isArrayOf, 35 | isArrayOfLike, 36 | } from "./array"; 37 | 38 | export { 39 | onlyBooleans, 40 | onlyNumbers, 41 | onlyStrings, 42 | onlySymbols, 43 | onlyNulls, 44 | onlyUndefineds, 45 | onlyPrimitives, 46 | onlyObjects, 47 | only, 48 | onlyLike, 49 | } from "./only"; 50 | -------------------------------------------------------------------------------- /src/is.ts: -------------------------------------------------------------------------------- 1 | import { primitive, Classy, TypeGuard } from "./types"; 2 | 3 | const TYPE_GUARDS_PRIMITIVE = [isBoolean, isNumber, isString, isSymbol, isNull, isUndefined]; 4 | 5 | /** 6 | * Type guard for `boolean`. 7 | * 8 | * @param x 9 | */ 10 | export function isBoolean(x: unknown): x is boolean { 11 | return typeof x === "boolean"; 12 | } 13 | 14 | /** 15 | * Type guard for booleans (primitive ones and objects). 16 | * 17 | * @param x 18 | */ 19 | export function isBooleanLike(x: unknown): x is boolean | Boolean { 20 | return isBoolean(x) || is(Boolean)(x); 21 | } 22 | 23 | /** 24 | * Type guard for `number`. 25 | * 26 | * @param x 27 | */ 28 | export function isNumber(x: unknown): x is number { 29 | return typeof x === "number"; 30 | } 31 | 32 | /** 33 | * Type guard for numbers (primitive ones and objects). 34 | * 35 | * @param x 36 | */ 37 | export function isNumberLike(x: unknown): x is number | Number { 38 | return isNumber(x) || is(Number)(x); 39 | } 40 | 41 | /** 42 | * Type guard for `string`. 43 | * 44 | * @param x 45 | */ 46 | export function isString(x: unknown): x is string { 47 | return typeof x === "string"; 48 | } 49 | 50 | /** 51 | * Type guard for strings (primitive ones and objects). 52 | * 53 | * @param x 54 | */ 55 | export function isStringLike(x: unknown): x is string | String { 56 | return isString(x) || is(String)(x); 57 | } 58 | 59 | /** 60 | * Type guard for `symbol`. 61 | * 62 | * @param x 63 | */ 64 | export function isSymbol(x: unknown): x is symbol { 65 | return typeof x === "symbol"; 66 | } 67 | 68 | /** 69 | * Type guard for `null`. 70 | * 71 | * @param x 72 | */ 73 | export function isNull(x: unknown): x is null { 74 | return x === null; 75 | } 76 | 77 | /** 78 | * Type guard for `undefined`. 79 | * 80 | * @param x 81 | */ 82 | export function isUndefined(x: unknown): x is undefined { 83 | return x === undefined; 84 | } 85 | 86 | /** 87 | * Type guard for `null` or `undefined`. 88 | * 89 | * @param x 90 | */ 91 | export function isNothing(x: T | undefined | null): x is null | undefined { 92 | return x === null || x === undefined; 93 | } 94 | 95 | /** 96 | * Type guard for everything except `null` and `undefined`. 97 | * 98 | * @param x 99 | */ 100 | export function isSomething(x: T | undefined | null): x is T { 101 | return !isNothing(x); 102 | } 103 | 104 | /** 105 | * Determines if something is a primitive. 106 | * 107 | * @param x 108 | * 109 | * @return `true` iff `x` is a `boolean`, `number`, `string`, `symbol`, `null`, or `undefined`. 110 | */ 111 | export function isPrimitive(x: unknown): x is primitive { 112 | return TYPE_GUARDS_PRIMITIVE.some(f => f(x)); 113 | } 114 | 115 | /** 116 | * Determines if something is not a primitive. 117 | * 118 | * @param x 119 | * 120 | * @return `true` iff `x` is not a primitive. 121 | */ 122 | export function isNonPrimitive(x: unknown): x is object { 123 | return !isPrimitive(x); 124 | } 125 | 126 | function namedFunction(name: string, fun: F): F { 127 | return Object.defineProperty(fun, "name", { value: name, writable: false }); 128 | } 129 | 130 | function namedTypeGuard(creator: Function, type: Classy, typeGuard: TypeGuard): TypeGuard { 131 | return namedFunction(`${creator.name}(${type.name})`, typeGuard); 132 | } 133 | 134 | /** 135 | * Curried type guard for non-primitive types. 136 | * 137 | * @param type The class to create a type guard for. 138 | * 139 | * @return A type guard which returns `true` iff its argument `x` satisfies `x instanceof type`. 140 | */ 141 | export function is(type: Classy): TypeGuard { 142 | if (isPrimitive(type)) { 143 | return (_: unknown): _ is T => false; // to resemble the semantics of instanceof 144 | } 145 | return namedTypeGuard(is, type, (x: unknown): x is T => x instanceof type); 146 | } 147 | 148 | /** 149 | * Curried type guard for checking if something is like something else, i.e. of the same type or a subtype. 150 | * 151 | * @param reference An object to use as reference for the type guard. 152 | * 153 | * @return A type guard which returns `true` iff its argument is of the same type as `reference` or is an instance of that type. 154 | */ 155 | export function isLike(reference: T): TypeGuard { 156 | for (const f of TYPE_GUARDS_PRIMITIVE) { 157 | if (f(reference)) { 158 | // This eta abstraction is necessary to please the typechecker, which otherwise complains that "type 'boolean' is not assignable to type 'T'" etc. 159 | return (x: unknown): x is T => f(x); 160 | } 161 | } 162 | if (is(Array)(reference)) { 163 | return (x: unknown): x is T => is(Array)(x) && (reference.length > 0 ? x.every(isLike(reference[0])) : true); 164 | } 165 | if (reference.constructor === Object) { 166 | return (x: any): x is T => ( // x must be of type any because we use x[k] below 167 | isSomething(x) 168 | && 169 | Object.keys(reference).every(k => isLike((reference as any)[k])(x[k])) 170 | ); 171 | } 172 | if (reference.constructor instanceof Function) { 173 | return is(reference.constructor); 174 | } 175 | throw new TypeError(isLike.name + ` cannot use this object as reference because it has no constructor: ` + JSON.stringify(reference)); 176 | } 177 | -------------------------------------------------------------------------------- /src/only.ts: -------------------------------------------------------------------------------- 1 | import { primitive, Classy } from "./types"; 2 | import { isBoolean, isNumber, isString, isSymbol, isNull, isUndefined, isPrimitive, isNonPrimitive, is, isLike } from "./is"; 3 | 4 | export function onlyBooleans(xs: unknown[]): boolean[] { 5 | return xs.filter(isBoolean); 6 | } 7 | 8 | export function onlyNumbers(xs: unknown[]): number[] { 9 | return xs.filter(isNumber); 10 | } 11 | 12 | export function onlyStrings(xs: unknown[]): string[] { 13 | return xs.filter(isString); 14 | } 15 | 16 | export function onlySymbols(xs: unknown[]): symbol[] { 17 | return xs.filter(isSymbol); 18 | } 19 | 20 | export function onlyNulls(xs: unknown[]): null[] { 21 | return xs.filter(isNull); 22 | } 23 | 24 | export function onlyUndefineds(xs: unknown[]): undefined[] { 25 | return xs.filter(isUndefined); 26 | } 27 | 28 | export function onlyPrimitives(xs: unknown[]): primitive[] { 29 | return xs.filter(isPrimitive); 30 | } 31 | 32 | export function onlyObjects(xs: unknown[]): object[] { 33 | return xs.filter(isNonPrimitive); 34 | } 35 | 36 | export function only(type: Classy): (xs: unknown[]) => T[] { 37 | return (xs: unknown[]): T[] => xs.filter(is(type)); 38 | } 39 | 40 | export function onlyLike(reference: T): (xs: unknown[]) => T[] { 41 | return (xs: unknown[]): T[] => xs.filter(isLike(reference)); 42 | } 43 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type primitive = boolean | number | string | symbol | null | undefined; 2 | 3 | export type Classy = Function & { prototype: T }; 4 | 5 | export type TypeGuard = (x: unknown) => x is T; 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "es6", "es2018", "es2015.symbol" 5 | ], 6 | "sourceMap": true, 7 | "target": "es2017", 8 | "declaration": true, 9 | "strictNullChecks": true, 10 | "noImplicitAny": true, 11 | "module": "es2015", 12 | "removeComments": true, 13 | "emitDecoratorMetadata": true, 14 | "experimentalDecorators": true, 15 | "moduleResolution": "node", 16 | "outDir": "dist", 17 | "preserveConstEnums": true 18 | }, 19 | "files": [ 20 | "src/index.ts" 21 | ] 22 | } 23 | --------------------------------------------------------------------------------